/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD If you want to view the source, please visit the GitHub repository. */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.ts var main_exports = {}; __export(main_exports, { default: () => OpenCodePlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian5 = require("obsidian"); // src/types.ts var DEFAULT_SETTINGS = { port: 14096, hostname: "127.0.0.1", autoStart: false, opencodePath: "opencode", projectDirectory: "", startupTimeout: 15e3, defaultViewLocation: "sidebar", injectWorkspaceContext: true, maxNotesInContext: 20, maxSelectionLength: 2e3 }; var OPENCODE_VIEW_TYPE = "opencode-view"; // src/OpenCodeView.ts var import_obsidian2 = require("obsidian"); // src/icons.ts var import_obsidian = require("obsidian"); var OPENCODE_ICON_NAME = "opencode-logo"; var OPENCODE_LOGO_SVG = ` `; function registerOpenCodeIcons() { (0, import_obsidian.addIcon)(OPENCODE_ICON_NAME, OPENCODE_LOGO_SVG); } // src/OpenCodeView.ts var OpenCodeView = class extends import_obsidian2.ItemView { constructor(leaf, plugin) { super(leaf); this.iframeEl = null; this.currentState = "stopped"; this.unsubscribeStateChange = null; this.plugin = plugin; } getViewType() { return OPENCODE_VIEW_TYPE; } getDisplayText() { return "OpenCode"; } getIcon() { return OPENCODE_ICON_NAME; } async onOpen() { this.contentEl.empty(); this.contentEl.addClass("opencode-container"); this.unsubscribeStateChange = this.plugin.onProcessStateChange((state) => { this.currentState = state; this.updateView(); }); this.currentState = this.plugin.getProcessState(); this.updateView(); if (this.currentState === "stopped") { this.plugin.startServer(); } } async onClose() { if (this.unsubscribeStateChange) { this.unsubscribeStateChange(); this.unsubscribeStateChange = null; } if (this.iframeEl) { const iframeUrl = this.iframeEl.src; if (iframeUrl.includes("/session/")) { this.plugin.setCachedIframeUrl(iframeUrl); } this.iframeEl.src = "about:blank"; this.iframeEl = null; } } updateView() { switch (this.currentState) { case "stopped": this.renderStoppedState(); break; case "starting": this.renderStartingState(); break; case "running": this.renderRunningState(); break; case "error": this.renderErrorState(); break; } } renderStoppedState() { this.contentEl.empty(); const statusContainer = this.contentEl.createDiv({ cls: "opencode-status-container" }); const iconEl = statusContainer.createDiv({ cls: "opencode-status-icon" }); (0, import_obsidian2.setIcon)(iconEl, "power-off"); statusContainer.createEl("h3", { text: "OpenCode is stopped" }); statusContainer.createEl("p", { text: "Click the button below to start the OpenCode server.", cls: "opencode-status-message" }); const startButton = statusContainer.createEl("button", { text: "Start OpenCode", cls: "mod-cta" }); startButton.addEventListener("click", () => { this.plugin.startServer(); }); } renderStartingState() { this.contentEl.empty(); const statusContainer = this.contentEl.createDiv({ cls: "opencode-status-container" }); const loadingEl = statusContainer.createDiv({ cls: "opencode-loading" }); loadingEl.createDiv({ cls: "opencode-spinner" }); statusContainer.createEl("h3", { text: "Starting OpenCode..." }); statusContainer.createEl("p", { text: "Please wait while the server starts up.", cls: "opencode-status-message" }); } renderRunningState() { var _a; this.contentEl.empty(); const headerEl = this.contentEl.createDiv({ cls: "opencode-header" }); const titleSection = headerEl.createDiv({ cls: "opencode-header-title" }); const iconEl = titleSection.createSpan(); (0, import_obsidian2.setIcon)(iconEl, OPENCODE_ICON_NAME); titleSection.createSpan({ text: "OpenCode" }); const actionsEl = headerEl.createDiv({ cls: "opencode-header-actions" }); const reloadButton = actionsEl.createEl("button", { attr: { "aria-label": "Reload" } }); (0, import_obsidian2.setIcon)(reloadButton, "refresh-cw"); reloadButton.addEventListener("click", () => { this.reloadIframe(); }); const stopButton = actionsEl.createEl("button", { attr: { "aria-label": "Stop server" } }); (0, import_obsidian2.setIcon)(stopButton, "square"); stopButton.addEventListener("click", () => { this.plugin.stopServer(); }); const iframeContainer = this.contentEl.createDiv({ cls: "opencode-iframe-container" }); const iframeUrl = (_a = this.plugin.getStoredIframeUrl()) != null ? _a : this.plugin.getServerUrl(); console.log("[OpenCode] Loading iframe with URL:", iframeUrl); this.iframeEl = iframeContainer.createEl("iframe", { cls: "opencode-iframe", attr: { src: iframeUrl, frameborder: "0", allow: "clipboard-read; clipboard-write" } }); this.iframeEl.addEventListener("error", () => { console.error("Failed to load OpenCode iframe"); }); this.iframeEl.addEventListener("focus", () => { this.plugin.refreshContextForView(this); }); this.iframeEl.addEventListener("pointerdown", () => { this.plugin.refreshContextForView(this); }); void this.plugin.ensureSessionUrl(this); } getIframeUrl() { var _a, _b; return (_b = (_a = this.iframeEl) == null ? void 0 : _a.src) != null ? _b : null; } setIframeUrl(url) { if (this.iframeEl && this.iframeEl.src !== url) { this.iframeEl.src = url; } } renderErrorState() { this.contentEl.empty(); const statusContainer = this.contentEl.createDiv({ cls: "opencode-status-container opencode-error" }); const iconEl = statusContainer.createDiv({ cls: "opencode-status-icon" }); (0, import_obsidian2.setIcon)(iconEl, "alert-circle"); statusContainer.createEl("h3", { text: "Failed to start OpenCode" }); const errorMessage = this.plugin.getLastError(); if (errorMessage) { statusContainer.createEl("p", { text: errorMessage, cls: "opencode-status-message opencode-error-message" }); } else { statusContainer.createEl("p", { text: "There was an error starting the OpenCode server.", cls: "opencode-status-message" }); } const buttonContainer = statusContainer.createDiv({ cls: "opencode-button-group" }); const retryButton = buttonContainer.createEl("button", { text: "Retry", cls: "mod-cta" }); retryButton.addEventListener("click", () => { this.plugin.startServer(); }); const settingsButton = buttonContainer.createEl("button", { text: "Open Settings" }); settingsButton.addEventListener("click", () => { this.app.setting.open(); this.app.setting.openTabById("obsidian-opencode"); }); } reloadIframe() { if (this.iframeEl) { const src = this.iframeEl.src; this.iframeEl.src = "about:blank"; setTimeout(() => { if (this.iframeEl) { this.iframeEl.src = src; } }, 100); } } }; // src/SettingsTab.ts var import_obsidian3 = require("obsidian"); var import_fs = require("fs"); var import_os = require("os"); function expandTilde(path) { if (path === "~") { return (0, import_os.homedir)(); } if (path.startsWith("~/")) { return path.replace("~", (0, import_os.homedir)()); } return path; } var OpenCodeSettingTab = class extends import_obsidian3.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.validateTimeout = null; this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h2", { text: "OpenCode Settings" }); containerEl.createEl("h3", { text: "Server Configuration" }); new import_obsidian3.Setting(containerEl).setName("Port").setDesc("Port number for the OpenCode web server").addText( (text) => text.setPlaceholder("14096").setValue(this.plugin.settings.port.toString()).onChange(async (value) => { const port = parseInt(value, 10); if (!isNaN(port) && port > 0 && port < 65536) { this.plugin.settings.port = port; await this.plugin.saveSettings(); } }) ); new import_obsidian3.Setting(containerEl).setName("Hostname").setDesc("Hostname to bind the server to (usually 127.0.0.1)").addText( (text) => text.setPlaceholder("127.0.0.1").setValue(this.plugin.settings.hostname).onChange(async (value) => { this.plugin.settings.hostname = value || "127.0.0.1"; await this.plugin.saveSettings(); }) ); new import_obsidian3.Setting(containerEl).setName("OpenCode path").setDesc( "Path to the OpenCode executable. Leave as 'opencode' if it's in your PATH." ).addText( (text) => text.setPlaceholder("opencode").setValue(this.plugin.settings.opencodePath).onChange(async (value) => { this.plugin.settings.opencodePath = value || "opencode"; await this.plugin.saveSettings(); }) ); new import_obsidian3.Setting(containerEl).setName("Project directory").setDesc( "Override the starting directory for OpenCode. Leave empty to use the vault root. Supports ~ for home directory." ).addText( (text) => text.setPlaceholder("/path/to/project or ~/project").setValue(this.plugin.settings.projectDirectory).onChange((value) => { if (this.validateTimeout) { clearTimeout(this.validateTimeout); } this.validateTimeout = setTimeout(async () => { await this.validateAndSetProjectDirectory(value); }, 500); }) ); containerEl.createEl("h3", { text: "Behavior" }); new import_obsidian3.Setting(containerEl).setName("Auto-start server").setDesc( "Automatically start the OpenCode server when Obsidian opens (not recommended for faster startup)" ).addToggle( (toggle) => toggle.setValue(this.plugin.settings.autoStart).onChange(async (value) => { this.plugin.settings.autoStart = value; await this.plugin.saveSettings(); }) ); new import_obsidian3.Setting(containerEl).setName("Default view location").setDesc( "Where to open the OpenCode panel: sidebar opens in the right panel, main opens as a tab in the editor area" ).addDropdown( (dropdown) => dropdown.addOption("sidebar", "Sidebar").addOption("main", "Main window").setValue(this.plugin.settings.defaultViewLocation).onChange(async (value) => { this.plugin.settings.defaultViewLocation = value; await this.plugin.saveSettings(); }) ); containerEl.createEl("h3", { text: "Workspace Context" }); new import_obsidian3.Setting(containerEl).setName("Inject workspace context").setDesc( "Includes open note paths and selected text in OpenCode when the view is focused" ).addToggle( (toggle) => toggle.setValue(this.plugin.settings.injectWorkspaceContext).onChange(async (value) => { this.plugin.settings.injectWorkspaceContext = value; await this.plugin.saveSettings(); }) ); new import_obsidian3.Setting(containerEl).setName("Max notes in context").setDesc("Limit how many open notes are included").addSlider( (slider) => slider.setLimits(1, 50, 1).setValue(this.plugin.settings.maxNotesInContext).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.maxNotesInContext = value; await this.plugin.saveSettings(); }) ); new import_obsidian3.Setting(containerEl).setName("Max selection length").setDesc("Truncate selected text to avoid oversized context").addSlider( (slider) => slider.setLimits(500, 5e3, 100).setValue(this.plugin.settings.maxSelectionLength).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.maxSelectionLength = value; await this.plugin.saveSettings(); }) ); containerEl.createEl("h3", { text: "Server Status" }); const statusContainer = containerEl.createDiv({ cls: "opencode-settings-status" }); this.renderServerStatus(statusContainer); } async validateAndSetProjectDirectory(value) { const trimmed = value.trim(); if (!trimmed) { await this.plugin.updateProjectDirectory(""); return; } if (!trimmed.startsWith("/") && !trimmed.startsWith("~") && !trimmed.match(/^[A-Za-z]:\\/)) { new import_obsidian3.Notice("Project directory must be an absolute path (or start with ~)"); return; } const expanded = expandTilde(trimmed); try { if (!(0, import_fs.existsSync)(expanded)) { new import_obsidian3.Notice("Project directory does not exist"); return; } const stat = (0, import_fs.statSync)(expanded); if (!stat.isDirectory()) { new import_obsidian3.Notice("Project directory path is not a directory"); return; } } catch (error) { new import_obsidian3.Notice(`Failed to validate path: ${error.message}`); return; } await this.plugin.updateProjectDirectory(expanded); } renderServerStatus(container) { container.empty(); const state = this.plugin.getProcessState(); const statusText = { stopped: "Stopped", starting: "Starting...", running: "Running", error: "Error" }; const statusClass = { stopped: "status-stopped", starting: "status-starting", running: "status-running", error: "status-error" }; const statusEl = container.createDiv({ cls: "opencode-status-line" }); statusEl.createSpan({ text: "Status: " }); statusEl.createSpan({ text: statusText[state], cls: `opencode-status-badge ${statusClass[state]}` }); if (state === "running") { const urlEl = container.createDiv({ cls: "opencode-status-line" }); urlEl.createSpan({ text: "URL: " }); const linkEl = urlEl.createEl("a", { text: this.plugin.getServerUrl(), href: this.plugin.getServerUrl() }); linkEl.addEventListener("click", (e) => { e.preventDefault(); window.open(this.plugin.getServerUrl(), "_blank"); }); } const buttonContainer = container.createDiv({ cls: "opencode-settings-buttons" }); if (state === "stopped" || state === "error") { const startButton = buttonContainer.createEl("button", { text: "Start Server", cls: "mod-cta" }); startButton.addEventListener("click", async () => { await this.plugin.startServer(); this.renderServerStatus(container); }); } if (state === "running") { const stopButton = buttonContainer.createEl("button", { text: "Stop Server" }); stopButton.addEventListener("click", () => { this.plugin.stopServer(); this.renderServerStatus(container); }); const restartButton = buttonContainer.createEl("button", { text: "Restart Server", cls: "mod-warning" }); restartButton.addEventListener("click", async () => { this.plugin.stopServer(); await this.plugin.startServer(); this.renderServerStatus(container); }); } if (state === "starting") { buttonContainer.createSpan({ text: "Please wait...", cls: "opencode-status-waiting" }); } } }; // src/ProcessManager.ts var import_child_process = require("child_process"); var ProcessManager = class { constructor(settings, projectDirectory, onStateChange) { this.process = null; this.state = "stopped"; this.lastError = null; this.earlyExitCode = null; this.settings = settings; this.projectDirectory = projectDirectory; this.onStateChange = onStateChange; } updateSettings(settings) { this.settings = settings; } updateProjectDirectory(directory) { this.projectDirectory = directory; } getState() { return this.state; } getLastError() { return this.lastError; } getUrl() { const encodedPath = btoa(this.projectDirectory); return `http://${this.settings.hostname}:${this.settings.port}/${encodedPath}`; } async start() { var _a, _b; if (this.state === "running" || this.state === "starting") { return true; } this.setState("starting"); this.lastError = null; this.earlyExitCode = null; if (!this.projectDirectory) { return this.setError("Project directory (vault) not configured"); } if (await this.checkServerHealth()) { console.log("[OpenCode] Server already running on port", this.settings.port); this.setState("running"); return true; } console.log("[OpenCode] Starting server:", { opencodePath: this.settings.opencodePath, port: this.settings.port, hostname: this.settings.hostname, cwd: this.projectDirectory, projectDirectory: this.projectDirectory }); this.process = (0, import_child_process.spawn)( this.settings.opencodePath, [ "serve", "--port", this.settings.port.toString(), "--hostname", this.settings.hostname, "--cors", "app://obsidian.md" ], { cwd: this.projectDirectory, env: { ...process.env, NODE_USE_SYSTEM_CA: "1" }, stdio: ["ignore", "pipe", "pipe"], detached: false } ); console.log("[OpenCode] Process spawned with PID:", this.process.pid); (_a = this.process.stdout) == null ? void 0 : _a.on("data", (data) => { console.log("[OpenCode]", data.toString().trim()); }); (_b = this.process.stderr) == null ? void 0 : _b.on("data", (data) => { console.error("[OpenCode Error]", data.toString().trim()); }); this.process.on("exit", (code, signal) => { console.log(`[OpenCode] Process exited with code ${code}, signal ${signal}`); this.process = null; if (this.state === "starting" && code !== null && code !== 0) { this.earlyExitCode = code; } if (this.state === "running") { this.setState("stopped"); } }); this.process.on("error", (err) => { console.error("[OpenCode] Failed to start process:", err); this.process = null; if (err.code === "ENOENT") { this.setError(`Executable not found at '${this.settings.opencodePath}'`); } else { this.setError(`Failed to start: ${err.message}`); } }); const ready = await this.waitForServerOrExit(this.settings.startupTimeout); if (ready) { this.setState("running"); return true; } if (this.state === "error") { return false; } this.stop(); if (this.earlyExitCode !== null) { return this.setError(`Process exited unexpectedly (exit code ${this.earlyExitCode})`); } if (!this.process) { return this.setError("Process exited before server became ready"); } return this.setError("Server failed to start within timeout"); } stop() { if (!this.process) { this.setState("stopped"); return; } const proc = this.process; console.log("[OpenCode] Stopping process with PID:", proc.pid); this.setState("stopped"); this.process = null; proc.kill("SIGTERM"); setTimeout(() => { if (proc.exitCode === null && proc.signalCode === null) { console.log("[OpenCode] Process still running, sending SIGKILL"); proc.kill("SIGKILL"); } }, 2e3); } setState(state) { this.state = state; this.onStateChange(state); } setError(message) { this.lastError = message; console.error("[OpenCode Error]", message); this.setState("error"); return false; } async checkServerHealth() { try { const response = await fetch(`${this.getUrl()}/global/health`, { method: "GET", signal: AbortSignal.timeout(2e3) }); return response.ok; } catch (e) { return false; } } async waitForServerOrExit(timeoutMs) { const startTime = Date.now(); const pollInterval = 500; while (Date.now() - startTime < timeoutMs) { if (!this.process) { console.log("[OpenCode] Process exited before server became ready"); return false; } if (await this.checkServerHealth()) { return true; } await this.sleep(pollInterval); } return false; } sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } }; // src/OpenCodeClient.ts var OpenCodeClient = class { constructor(apiBaseUrl, uiBaseUrl, projectDirectory) { this.trackedSessionId = null; this.lastPart = null; this.apiBaseUrl = this.normalizeBaseUrl(apiBaseUrl); this.uiBaseUrl = this.normalizeBaseUrl(uiBaseUrl); this.projectDirectory = projectDirectory; } updateBaseUrl(apiBaseUrl, uiBaseUrl, projectDirectory) { const nextApiUrl = this.normalizeBaseUrl(apiBaseUrl); const nextUiUrl = this.normalizeBaseUrl(uiBaseUrl); if (nextApiUrl !== this.apiBaseUrl || nextUiUrl !== this.uiBaseUrl || projectDirectory !== this.projectDirectory) { this.apiBaseUrl = nextApiUrl; this.uiBaseUrl = nextUiUrl; this.projectDirectory = projectDirectory; this.resetTracking(); } } resetTracking() { this.trackedSessionId = null; this.lastPart = null; } getSessionUrl(sessionId) { return `${this.uiBaseUrl}/session/${sessionId}`; } resolveSessionId(iframeUrl) { var _a; const match = iframeUrl.match(/\/session\/([^/?#]+)/); return (_a = match == null ? void 0 : match[1]) != null ? _a : null; } async createSession() { var _a; const result = await this.request("POST", "/session", { title: "Obsidian" }); const session = this.unwrap(result); return (_a = session == null ? void 0 : session.id) != null ? _a : null; } async updateContext(params) { var _a, _b, _c; const { sessionId, contextText } = params; if (this.trackedSessionId && this.trackedSessionId !== sessionId) { this.resetTracking(); } this.trackedSessionId = sessionId; if (!contextText) { await this.ignorePreviousPart(); return; } if (this.lastPart) { const updated = await this.updatePart(this.lastPart, { text: contextText }); if (updated) { return; } await this.ignorePreviousPart(); } const message = await this.sendPrompt(sessionId, contextText); if ((_a = message == null ? void 0 : message.info) == null ? void 0 : _a.id) { this.lastPart = (_c = (_b = message.parts) == null ? void 0 : _b[0]) != null ? _c : null; } } async sendPrompt(sessionId, contextText) { const result = await this.request( "POST", `/session/${sessionId}/message`, { noReply: true, parts: [{ type: "text", text: contextText }] } ); console.log("[OpenCode] Injected context message"); console.log(contextText); const message = this.unwrap(result); if (!message) { console.error("[OpenCode] Failed to inject context message"); } return message; } async updatePart(part, updates) { const result = await this.request( "PATCH", `/session/${part.sessionID}/message/${part.messageID}/part/${part.id}`, { ...part, ...updates } ); const updated = this.unwrap(result); if (updated) { this.lastPart = updated; return true; } return false; } async ignorePreviousPart() { if (!this.lastPart) { return false; } const ignored = await this.updatePart(this.lastPart, { ignored: true }); if (!ignored) { return false; } this.lastPart = null; return true; } async request(method, path, body) { try { const url = `${this.apiBaseUrl}${path}`; const response = await fetch(url, { method, headers: { "Content-Type": "application/json", "x-opencode-directory": this.projectDirectory }, body: body ? JSON.stringify(body) : void 0 }); if (!response.ok) { console.error("[OpenCode] API request failed", { path, status: response.status }); return null; } const json = await response.json().catch(() => null); return json; } catch (error) { console.error("[OpenCode] API request error", error); return null; } } unwrap(result) { if (!result) { return null; } if (typeof result === "object") { const payload = result; if (payload.data) { return payload.data; } if (payload.message) { return payload.message; } } return result; } normalizeBaseUrl(baseUrl) { return baseUrl.replace(/\/+$/, ""); } }; // src/WorkspaceContext.ts var import_obsidian4 = require("obsidian"); var WorkspaceContext = class { constructor(app) { this.lastSelection = null; this.lastMarkdownView = null; this.app = app; } trackViewSelection(view) { var _a, _b, _c; if (view) { this.lastMarkdownView = view; } const sourcePath = (_a = view == null ? void 0 : view.file) == null ? void 0 : _a.path; const selection = (_c = (_b = view == null ? void 0 : view.editor) == null ? void 0 : _b.getSelection()) != null ? _c : ""; if (sourcePath && selection.trim()) { this.lastSelection = { text: selection, sourcePath }; } } gatherContext(maxNotes, maxSelectionLength) { var _a, _b, _c, _d, _e; const leaves = this.app.workspace.getLeavesOfType("markdown"); const paths = /* @__PURE__ */ new Set(); for (const leaf of leaves) { const view2 = leaf.view; const path = (_a = view2.file) == null ? void 0 : _a.path; if (path) { paths.add(path); } } const openNotePaths = Array.from(paths).slice(0, Math.max(0, maxNotes)); const view = (_b = this.app.workspace.getActiveViewOfType(import_obsidian4.MarkdownView)) != null ? _b : this.lastMarkdownView; this.trackViewSelection(view); const sourcePath = (_c = view == null ? void 0 : view.file) == null ? void 0 : _c.path; const selection = (_e = (_d = view == null ? void 0 : view.editor) == null ? void 0 : _d.getSelection()) != null ? _e : ""; let selectionContext = null; if (sourcePath && selection.trim()) { selectionContext = { text: selection, sourcePath }; this.lastSelection = selectionContext; } else if (this.lastSelection) { selectionContext = this.lastSelection; } if (selectionContext && selectionContext.text.length > maxSelectionLength) { selectionContext = { ...selectionContext, text: selectionContext.text.slice(0, maxSelectionLength) + "... [truncated]" }; } let contextText = null; if (openNotePaths.length > 0 || selectionContext) { const lines = [""]; if (openNotePaths.length > 0) { lines.push("Currently open notes in Obsidian:"); for (const path of openNotePaths) { lines.push(`- ${path}`); } } if (selectionContext) { lines.push(""); lines.push(`Selected text (from ${selectionContext.sourcePath}):`); lines.push('"""'); lines.push(selectionContext.text); lines.push('"""'); } lines.push(""); contextText = lines.join("\n"); } return { openNotePaths, selection: selectionContext, contextText }; } }; // src/main.ts var OpenCodePlugin = class extends import_obsidian5.Plugin { constructor() { super(...arguments); this.settings = DEFAULT_SETTINGS; this.stateChangeCallbacks = []; this.cachedIframeUrl = null; this.lastBaseUrl = null; this.contextEventRefs = []; this.contextRefreshTimer = null; } async onload() { console.log("Loading OpenCode plugin"); registerOpenCodeIcons(); await this.loadSettings(); const projectDirectory = this.getProjectDirectory(); this.processManager = new ProcessManager( this.settings, projectDirectory, (state) => this.notifyStateChange(state) ); this.openCodeClient = new OpenCodeClient(this.getApiBaseUrl(), this.getServerUrl(), projectDirectory); this.workspaceContext = new WorkspaceContext(this.app); this.lastBaseUrl = this.getServerUrl(); console.log("[OpenCode] Configured with project directory:", projectDirectory); this.registerView(OPENCODE_VIEW_TYPE, (leaf) => new OpenCodeView(leaf, this)); this.addSettingTab(new OpenCodeSettingTab(this.app, this)); this.addRibbonIcon(OPENCODE_ICON_NAME, "OpenCode", () => { this.activateView(); }); this.addCommand({ id: "toggle-opencode-view", name: "Toggle OpenCode panel", callback: () => { this.toggleView(); }, hotkeys: [ { modifiers: ["Mod", "Shift"], key: "o" } ] }); this.addCommand({ id: "start-opencode-server", name: "Start OpenCode server", callback: () => { this.startServer(); } }); this.addCommand({ id: "stop-opencode-server", name: "Stop OpenCode server", callback: () => { this.stopServer(); } }); if (this.settings.autoStart) { this.app.workspace.onLayoutReady(async () => { await this.startServer(); }); } this.updateContextListeners(); this.onProcessStateChange((state) => { if (state === "running") { void this.handleServerRunning(); } }); console.log("OpenCode plugin loaded"); } async onunload() { this.stopServer(); this.app.workspace.detachLeavesOfType(OPENCODE_VIEW_TYPE); } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } async saveSettings() { await this.saveData(this.settings); this.processManager.updateSettings(this.settings); this.refreshClientState(); this.updateContextListeners(); } // Update project directory and restart server if running async updateProjectDirectory(directory) { this.settings.projectDirectory = directory; await this.saveData(this.settings); this.processManager.updateProjectDirectory(this.getProjectDirectory()); this.refreshClientState(); if (this.getProcessState() === "running") { this.stopServer(); await this.startServer(); } } getExistingLeaf() { const leaves = this.app.workspace.getLeavesOfType(OPENCODE_VIEW_TYPE); return leaves.length > 0 ? leaves[0] : null; } async activateView() { const existingLeaf = this.getExistingLeaf(); if (existingLeaf) { this.app.workspace.revealLeaf(existingLeaf); return; } let leaf = null; if (this.settings.defaultViewLocation === "main") { leaf = this.app.workspace.getLeaf("tab"); } else { leaf = this.app.workspace.getRightLeaf(false); } if (leaf) { await leaf.setViewState({ type: OPENCODE_VIEW_TYPE, active: true }); this.app.workspace.revealLeaf(leaf); } } async toggleView() { const existingLeaf = this.getExistingLeaf(); if (existingLeaf) { const isInSidebar = existingLeaf.getRoot() === this.app.workspace.rightSplit; if (isInSidebar) { const rightSplit = this.app.workspace.rightSplit; if (rightSplit && !rightSplit.collapsed) { existingLeaf.detach(); } else { this.app.workspace.revealLeaf(existingLeaf); } } else { existingLeaf.detach(); } } else { await this.activateView(); } } async startServer() { const success = await this.processManager.start(); if (success) { new import_obsidian5.Notice("OpenCode server started"); } return success; } stopServer() { this.processManager.stop(); new import_obsidian5.Notice("OpenCode server stopped"); } getProcessState() { var _a, _b; return (_b = (_a = this.processManager) == null ? void 0 : _a.getState()) != null ? _b : "stopped"; } getLastError() { var _a; return (_a = this.processManager.getLastError()) != null ? _a : null; } getServerUrl() { return this.processManager.getUrl(); } getApiBaseUrl() { return `http://${this.settings.hostname}:${this.settings.port}`; } getStoredIframeUrl() { return this.cachedIframeUrl; } setCachedIframeUrl(url) { this.cachedIframeUrl = url; } async ensureSessionUrl(view) { var _a; if (this.getProcessState() !== "running") { return; } const existingUrl = (_a = this.cachedIframeUrl) != null ? _a : view.getIframeUrl(); if (existingUrl && this.openCodeClient.resolveSessionId(existingUrl)) { this.cachedIframeUrl = existingUrl; return; } const sessionId = await this.openCodeClient.createSession(); if (!sessionId) { return; } const sessionUrl = this.openCodeClient.getSessionUrl(sessionId); this.cachedIframeUrl = sessionUrl; view.setIframeUrl(sessionUrl); if (this.app.workspace.activeLeaf === view.leaf) { await this.updateOpenCodeContext(view.leaf); } } refreshContextForView(view) { if (!this.settings.injectWorkspaceContext) { return; } void this.updateOpenCodeContext(view.leaf); } onProcessStateChange(callback) { this.stateChangeCallbacks.push(callback); return () => { const index = this.stateChangeCallbacks.indexOf(callback); if (index > -1) { this.stateChangeCallbacks.splice(index, 1); } }; } notifyStateChange(state) { for (const callback of this.stateChangeCallbacks) { callback(state); } } refreshClientState() { const nextUiBaseUrl = this.getServerUrl(); const nextApiBaseUrl = this.getApiBaseUrl(); const projectDirectory = this.getProjectDirectory(); this.openCodeClient.updateBaseUrl(nextApiBaseUrl, nextUiBaseUrl, projectDirectory); if (this.lastBaseUrl && this.lastBaseUrl !== nextUiBaseUrl) { this.cachedIframeUrl = null; } this.lastBaseUrl = nextUiBaseUrl; } updateContextListeners() { if (!this.settings.injectWorkspaceContext) { this.clearContextListeners(); return; } if (this.contextEventRefs.length > 0) { return; } const activeLeafRef = this.app.workspace.on("active-leaf-change", (leaf) => { if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian5.MarkdownView) { this.workspaceContext.trackViewSelection(leaf.view); } this.scheduleContextRefresh(0); }); const fileOpenRef = this.app.workspace.on("file-open", () => { this.scheduleContextRefresh(); }); const fileCloseRef = this.app.workspace.on("file-close", () => { this.scheduleContextRefresh(); }); const layoutChangeRef = this.app.workspace.on("layout-change", () => { this.scheduleContextRefresh(); }); const editorChangeRef = this.app.workspace.on("editor-change", (_editor, view) => { if (view instanceof import_obsidian5.MarkdownView) { this.workspaceContext.trackViewSelection(view); } this.scheduleContextRefresh(500); }); const selectionChangeRef = this.app.workspace.on( "editor-selection-change", (_editor, view) => { if (view instanceof import_obsidian5.MarkdownView) { this.workspaceContext.trackViewSelection(view); } this.scheduleContextRefresh(200); } ); this.contextEventRefs = [ activeLeafRef, fileOpenRef, fileCloseRef, layoutChangeRef, editorChangeRef, selectionChangeRef ]; this.contextEventRefs.forEach((ref) => this.registerEvent(ref)); } clearContextListeners() { for (const ref of this.contextEventRefs) { this.app.workspace.offref(ref); } this.contextEventRefs = []; if (this.contextRefreshTimer !== null) { window.clearTimeout(this.contextRefreshTimer); this.contextRefreshTimer = null; } } scheduleContextRefresh(delayMs = 300) { const leaf = this.getOpenCodeLeafForRefresh(); if (!leaf) { return; } if (this.contextRefreshTimer !== null) { window.clearTimeout(this.contextRefreshTimer); } this.contextRefreshTimer = window.setTimeout(() => { this.contextRefreshTimer = null; void this.updateOpenCodeContext(leaf); }, delayMs); } getOpenCodeLeafForRefresh() { const activeLeaf = this.app.workspace.activeLeaf; if ((activeLeaf == null ? void 0 : activeLeaf.view.getViewType()) === OPENCODE_VIEW_TYPE) { return activeLeaf; } return this.getVisibleSidebarOpenCodeLeaf(); } getVisibleSidebarOpenCodeLeaf() { const leaves = this.app.workspace.getLeavesOfType(OPENCODE_VIEW_TYPE); if (leaves.length === 0) { return null; } const rightSplit = this.app.workspace.rightSplit; if (!rightSplit || rightSplit.collapsed) { return null; } const leaf = leaves[0]; return leaf.getRoot() === rightSplit ? leaf : null; } async handleServerRunning() { const activeLeaf = this.app.workspace.activeLeaf; if ((activeLeaf == null ? void 0 : activeLeaf.view.getViewType()) === OPENCODE_VIEW_TYPE) { await this.updateOpenCodeContext(activeLeaf); } } async updateOpenCodeContext(leaf) { var _a; if (!this.settings.injectWorkspaceContext) { return; } if (this.getProcessState() !== "running") { return; } const view = leaf.view instanceof OpenCodeView ? leaf.view : null; const iframeUrl = (_a = this.cachedIframeUrl) != null ? _a : view == null ? void 0 : view.getIframeUrl(); if (!iframeUrl) { return; } const sessionId = this.openCodeClient.resolveSessionId(iframeUrl); if (!sessionId) { return; } this.cachedIframeUrl = iframeUrl; const { contextText } = this.workspaceContext.gatherContext( this.settings.maxNotesInContext, this.settings.maxSelectionLength ); await this.openCodeClient.updateContext({ sessionId, contextText }); } getProjectDirectory() { if (this.settings.projectDirectory) { console.log("[OpenCode] Using project directory from settings:", this.settings.projectDirectory); return this.settings.projectDirectory; } const adapter = this.app.vault.adapter; const vaultPath = adapter.basePath || ""; if (!vaultPath) { console.warn("[OpenCode] Warning: Could not determine vault path"); } console.log("[OpenCode] Using vault path as project directory:", vaultPath); return vaultPath; } };