/* 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_obsidian4 = require("obsidian"); // src/types.ts var DEFAULT_SETTINGS = { port: 14096, hostname: "127.0.0.1", autoStart: false, opencodePath: "opencode", projectDirectory: "", startupTimeout: 15e3, defaultViewLocation: "sidebar" }; 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) { 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() { 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" }); console.log("[OpenCode] Loading iframe with URL:", this.plugin.getServerUrl()); this.iframeEl = iframeContainer.createEl("iframe", { cls: "opencode-iframe", attr: { src: this.plugin.getServerUrl(), frameborder: "0", allow: "clipboard-read; clipboard-write" } }); this.iframeEl.addEventListener("error", () => { console.error("Failed to load OpenCode iframe"); }); } 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: "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 baseUrl = `http://${this.settings.hostname}:${this.settings.port}`; const encodedPath = btoa(this.projectDirectory); return `${baseUrl}/${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 }, 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/main.ts var OpenCodePlugin = class extends import_obsidian4.Plugin { constructor() { super(...arguments); this.settings = DEFAULT_SETTINGS; this.stateChangeCallbacks = []; } 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) ); 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(); }); } 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); } // 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()); if (this.getProcessState() === "running") { this.stopServer(); await this.startServer(); } } // Get existing view leaf if any getExistingLeaf() { const leaves = this.app.workspace.getLeavesOfType(OPENCODE_VIEW_TYPE); return leaves.length > 0 ? leaves[0] : null; } // Activate or create the view 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); } } // Toggle view visibility 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_obsidian4.Notice("OpenCode server started"); } return success; } stopServer() { this.processManager.stop(); new import_obsidian4.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(); } 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); } } 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; } };