Allow configuring starting dir

This commit is contained in:
Mateusz Tymek
2026-01-03 20:28:35 +00:00
parent 794317dbc7
commit c12fa12419
5 changed files with 186 additions and 10 deletions

82
main.js
View File

@@ -34,7 +34,8 @@ var DEFAULT_SETTINGS = {
port: 14096,
hostname: "127.0.0.1",
autoStart: false,
opencodePath: "opencode"
opencodePath: "opencode",
projectDirectory: ""
};
var OPENCODE_VIEW_TYPE = "opencode-view";
@@ -210,9 +211,21 @@ var OpenCodeView = class extends import_obsidian.ItemView {
// src/SettingsTab.ts
var import_obsidian2 = 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_obsidian2.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.validateTimeout = null;
this.plugin = plugin;
}
display() {
@@ -243,6 +256,18 @@ var OpenCodeSettingTab = class extends import_obsidian2.PluginSettingTab {
await this.plugin.saveSettings();
})
);
new import_obsidian2.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_obsidian2.Setting(containerEl).setName("Auto-start server").setDesc(
"Automatically start the OpenCode server when Obsidian opens (not recommended for faster startup)"
@@ -256,6 +281,33 @@ var OpenCodeSettingTab = class extends import_obsidian2.PluginSettingTab {
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_obsidian2.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_obsidian2.Notice("Project directory does not exist");
return;
}
const stat = (0, import_fs.statSync)(expanded);
if (!stat.isDirectory()) {
new import_obsidian2.Notice("Project directory path is not a directory");
return;
}
} catch (error) {
new import_obsidian2.Notice(`Failed to validate path: ${error.message}`);
return;
}
await this.plugin.updateProjectDirectory(expanded);
}
renderServerStatus(container) {
container.empty();
const state = this.plugin.getProcessState();
@@ -343,6 +395,9 @@ var ProcessManager = class {
updateSettings(settings) {
this.settings = settings;
}
updateProjectDirectory(directory) {
this.projectDirectory = directory;
}
getState() {
return this.state;
}
@@ -500,13 +555,14 @@ var OpenCodePlugin = class extends import_obsidian4.Plugin {
console.log("Loading OpenCode plugin");
await this.loadSettings();
const vaultPath = this.getVaultPath();
const projectDirectory = this.getProjectDirectory();
this.processManager = new ProcessManager(
this.settings,
vaultPath,
vaultPath,
projectDirectory,
(state) => this.notifyStateChange(state)
);
console.log("[OpenCode] Configured with vault directory:", vaultPath);
console.log("[OpenCode] Configured with project directory:", projectDirectory);
this.registerView(OPENCODE_VIEW_TYPE, (leaf) => new OpenCodeView(leaf, this));
this.addRibbonIcon("terminal", "OpenCode", () => {
this.activateView();
@@ -561,6 +617,18 @@ var OpenCodePlugin = class extends import_obsidian4.Plugin {
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);
if (this.processManager) {
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);
@@ -636,7 +704,6 @@ var OpenCodePlugin = class extends import_obsidian4.Plugin {
}
}
// Get the vault path - this is the root directory of the Obsidian vault
// which will be passed to OpenCode as the project directory
getVaultPath() {
const adapter = this.app.vault.adapter;
const vaultPath = adapter.basePath || "";
@@ -645,4 +712,11 @@ var OpenCodePlugin = class extends import_obsidian4.Plugin {
}
return vaultPath;
}
// Get the project directory - uses the configured setting if set, otherwise vault path
getProjectDirectory() {
if (this.settings.projectDirectory) {
return this.settings.projectDirectory;
}
return this.getVaultPath();
}
};

View File

@@ -25,10 +25,14 @@ export class ProcessManager {
this.onStateChange = onStateChange;
}
updateSettings(settings: OpenCodeSettings) {
updateSettings(settings: OpenCodeSettings): void {
this.settings = settings;
}
updateProjectDirectory(directory: string): void {
this.projectDirectory = directory;
}
getState(): ProcessState {
return this.state;
}

View File

@@ -1,8 +1,21 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import { App, PluginSettingTab, Setting, Notice } from "obsidian";
import { existsSync, statSync } from "fs";
import { homedir } from "os";
import type OpenCodePlugin from "./main";
function expandTilde(path: string): string {
if (path === "~") {
return homedir();
}
if (path.startsWith("~/")) {
return path.replace("~", homedir());
}
return path;
}
export class OpenCodeSettingTab extends PluginSettingTab {
plugin: OpenCodePlugin;
private validateTimeout: ReturnType<typeof setTimeout> | null = null;
constructor(app: App, plugin: OpenCodePlugin) {
super(app, plugin);
@@ -62,6 +75,26 @@ export class OpenCodeSettingTab extends PluginSettingTab {
})
);
new 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) => {
// Debounce validation to avoid spamming notices on every keypress
if (this.validateTimeout) {
clearTimeout(this.validateTimeout);
}
this.validateTimeout = setTimeout(async () => {
await this.validateAndSetProjectDirectory(value);
}, 500);
})
);
// Behavior settings section
containerEl.createEl("h3", { text: "Behavior" });
@@ -86,6 +119,44 @@ export class OpenCodeSettingTab extends PluginSettingTab {
this.renderServerStatus(statusContainer);
}
private async validateAndSetProjectDirectory(value: string): Promise<void> {
const trimmed = value.trim();
// Empty value is valid - means use vault root
if (!trimmed) {
await this.plugin.updateProjectDirectory("");
return;
}
// Validate absolute path (supports ~, /, and Windows drive letters)
if (!trimmed.startsWith("/") && !trimmed.startsWith("~") && !trimmed.match(/^[A-Za-z]:\\/)) {
new Notice("Project directory must be an absolute path (or start with ~)");
return;
}
// Expand tilde for validation
const expanded = expandTilde(trimmed);
// Validate path exists and is a directory
try {
if (!existsSync(expanded)) {
new Notice("Project directory does not exist");
return;
}
const stat = statSync(expanded);
if (!stat.isDirectory()) {
new Notice("Project directory path is not a directory");
return;
}
} catch (error) {
new Notice(`Failed to validate path: ${(error as Error).message}`);
return;
}
// Store the expanded path
await this.plugin.updateProjectDirectory(expanded);
}
private renderServerStatus(container: HTMLElement): void {
container.empty();

View File

@@ -16,16 +16,18 @@ export default class OpenCodePlugin extends Plugin {
// Get the vault directory path to pass to OpenCode
const vaultPath = this.getVaultPath();
const projectDirectory = this.getProjectDirectory();
// Initialize process manager with vault as the project directory
// Initialize process manager with vault as the working directory
// and either the configured project directory or vault as the project
this.processManager = new ProcessManager(
this.settings,
vaultPath,
vaultPath,
projectDirectory,
(state) => this.notifyStateChange(state)
);
console.log("[OpenCode] Configured with vault directory:", vaultPath);
console.log("[OpenCode] Configured with project directory:", projectDirectory);
// Register the OpenCode view
this.registerView(OPENCODE_VIEW_TYPE, (leaf) => new OpenCodeView(leaf, this));
@@ -105,6 +107,22 @@ export default class OpenCodePlugin extends Plugin {
}
}
// Update project directory and restart server if running
async updateProjectDirectory(directory: string): Promise<void> {
this.settings.projectDirectory = directory;
await this.saveData(this.settings);
if (this.processManager) {
this.processManager.updateProjectDirectory(this.getProjectDirectory());
// Restart server if it's currently running
if (this.getProcessState() === "running") {
this.stopServer();
await this.startServer();
}
}
}
// Get existing view leaf if any
private getExistingLeaf(): WorkspaceLeaf | null {
const leaves = this.app.workspace.getLeavesOfType(OPENCODE_VIEW_TYPE);
@@ -193,7 +211,6 @@ export default class OpenCodePlugin extends Plugin {
}
// Get the vault path - this is the root directory of the Obsidian vault
// which will be passed to OpenCode as the project directory
private getVaultPath(): string {
const adapter = this.app.vault.adapter as any;
const vaultPath = adapter.basePath || "";
@@ -202,4 +219,12 @@ export default class OpenCodePlugin extends Plugin {
}
return vaultPath;
}
// Get the project directory - uses the configured setting if set, otherwise vault path
getProjectDirectory(): string {
if (this.settings.projectDirectory) {
return this.settings.projectDirectory;
}
return this.getVaultPath();
}
}

View File

@@ -3,6 +3,7 @@ export interface OpenCodeSettings {
hostname: string;
autoStart: boolean;
opencodePath: string;
projectDirectory: string;
}
export const DEFAULT_SETTINGS: OpenCodeSettings = {
@@ -10,6 +11,7 @@ export const DEFAULT_SETTINGS: OpenCodeSettings = {
hostname: "127.0.0.1",
autoStart: false,
opencodePath: "opencode",
projectDirectory: "",
};
export const OPENCODE_VIEW_TYPE = "opencode-view";