diff --git a/packages/opencode-config/opencode.jsonc b/packages/opencode-config/opencode.jsonc new file mode 100644 index 00000000..c3eb6a56 --- /dev/null +++ b/packages/opencode-config/opencode.jsonc @@ -0,0 +1,3 @@ +{ + "$schema": "https://opencode.ai/config.json" +} \ No newline at end of file diff --git a/packages/opencode-config/plugin/hello.js b/packages/opencode-config/plugin/hello.js new file mode 100644 index 00000000..f37564d5 --- /dev/null +++ b/packages/opencode-config/plugin/hello.js @@ -0,0 +1,18 @@ +import { tool } from "@opencode-ai/plugin/tool" + +export async function HelloPlugin() { + return { + tool: { + hello: tool({ + description: "Return a friendly greeting", + args: { + name: tool.schema.string().optional().describe("Name to greet"), + }, + async execute(args) { + const target = args.name?.trim() || "CodeNomad" + return `Hello, ${target}!` + }, + }), + }, + } +} diff --git a/packages/server/package.json b/packages/server/package.json index c6892ee6..1257f822 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -16,10 +16,11 @@ "codenomad": "dist/bin.js" }, "scripts": { - "build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json", + "build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && npm run prepare-config", "build:ui": "npm run build --prefix ../ui", "prepare-ui": "node ./scripts/copy-ui-dist.mjs", - "dev": "cross-env CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts", + "prepare-config": "node ./scripts/copy-opencode-config.mjs", + "dev": "cross-env CODENOMAD_DEV=1 CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts", "typecheck": "tsc --noEmit -p tsconfig.json" }, "dependencies": { diff --git a/packages/server/scripts/copy-opencode-config.mjs b/packages/server/scripts/copy-opencode-config.mjs new file mode 100644 index 00000000..b63b8627 --- /dev/null +++ b/packages/server/scripts/copy-opencode-config.mjs @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import { cpSync, existsSync, mkdirSync, rmSync } from "fs" +import path from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const cliRoot = path.resolve(__dirname, "..") +const sourceDir = path.resolve(cliRoot, "../opencode-config") +const targetDir = path.resolve(cliRoot, "dist/opencode-config") + +if (!existsSync(sourceDir)) { + console.error(`[copy-opencode-config] Missing source directory at ${sourceDir}`) + process.exit(1) +} + +rmSync(targetDir, { recursive: true, force: true }) +mkdirSync(path.dirname(targetDir), { recursive: true }) +cpSync(sourceDir, targetDir, { recursive: true }) + +console.log(`[copy-opencode-config] Copied ${sourceDir} -> ${targetDir}`) diff --git a/packages/server/src/opencode-config.ts b/packages/server/src/opencode-config.ts new file mode 100644 index 00000000..8b90651b --- /dev/null +++ b/packages/server/src/opencode-config.ts @@ -0,0 +1,36 @@ +import { cpSync, existsSync, mkdirSync, rmSync } from "fs" +import os from "os" +import path from "path" +import { fileURLToPath } from "url" +import { createLogger } from "./logger" + +const log = createLogger({ component: "opencode-config" }) +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const devTemplateDir = path.resolve(__dirname, "../../opencode-config") +const prodTemplateDir = path.resolve(__dirname, "opencode-config") + +const isDevBuild = Boolean(process.env.CODENOMAD_DEV ?? process.env.CLI_UI_DEV_SERVER) || existsSync(devTemplateDir) +const templateDir = isDevBuild ? devTemplateDir : prodTemplateDir +const userConfigDir = path.join(os.homedir(), ".config", "codenomad", "opencode-config") + +export function getOpencodeConfigDir(): string { + if (!existsSync(templateDir)) { + throw new Error(`CodeNomad Opencode config template missing at ${templateDir}`) + } + + if (isDevBuild) { + log.debug({ templateDir }, "Using Opencode config template directly (dev mode)") + return templateDir + } + + refreshUserConfig() + return userConfigDir +} + +function refreshUserConfig() { + log.debug({ templateDir, userConfigDir }, "Syncing Opencode config template") + rmSync(userConfigDir, { recursive: true, force: true }) + mkdirSync(path.dirname(userConfigDir), { recursive: true }) + cpSync(templateDir, userConfigDir, { recursive: true }) +} diff --git a/packages/server/src/workspaces/manager.ts b/packages/server/src/workspaces/manager.ts index 404e3a10..a23c2178 100644 --- a/packages/server/src/workspaces/manager.ts +++ b/packages/server/src/workspaces/manager.ts @@ -9,6 +9,7 @@ import { clearWorkspaceSearchCache } from "../filesystem/search-cache" import { WorkspaceDescriptor, WorkspaceFileResponse, FileSystemEntry } from "../api-types" import { WorkspaceRuntime } from "./runtime" import { Logger } from "../logger" +import { getOpencodeConfigDir } from "../opencode-config" interface WorkspaceManagerOptions { rootDir: string @@ -23,9 +24,11 @@ interface WorkspaceRecord extends WorkspaceDescriptor {} export class WorkspaceManager { private readonly workspaces = new Map() private readonly runtime: WorkspaceRuntime + private readonly opencodeConfigDir: string constructor(private readonly options: WorkspaceManagerOptions) { this.runtime = new WorkspaceRuntime(this.options.eventBus, this.options.logger) + this.opencodeConfigDir = getOpencodeConfigDir() } list(): WorkspaceDescriptor[] { @@ -97,7 +100,12 @@ export class WorkspaceManager { this.options.eventBus.publish({ type: "workspace.created", workspace: descriptor }) - const environment = this.options.configStore.get().preferences.environmentVariables ?? {} + const preferences = this.options.configStore.get().preferences ?? {} + const userEnvironment = preferences.environmentVariables ?? {} + const environment = { + ...userEnvironment, + OPENCODE_CONFIG_DIR: this.opencodeConfigDir, + } try { const { pid, port } = await this.runtime.launch({