@neuralnomads/codenomad-dev 0.15.0-dev-20260512-91652990 → 0.15.0-dev-20260512-cf88dc06

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ import { existsSync, readdirSync } from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath, pathToFileURL } from "url";
4
+ import { createLogger } from "./logger";
5
+ const log = createLogger({ component: "opencode-plugin" });
6
+ const pluginPackageName = "@codenomad/codenomad-opencode-plugin";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const resourcesPath = process.resourcesPath;
10
+ const devPluginEntry = path.resolve(__dirname, "../../opencode-plugin/plugin/codenomad.ts");
11
+ const prodPluginDirs = [
12
+ resourcesPath ? path.resolve(resourcesPath, "opencode-plugin") : undefined,
13
+ resourcesPath ? path.resolve(resourcesPath, "server/dist/opencode-plugin") : undefined,
14
+ path.resolve(__dirname, "opencode-plugin"),
15
+ ].filter((dir) => Boolean(dir));
16
+ const isDevBuild = Boolean(process.env.CODENOMAD_DEV ??
17
+ process.env.CLI_UI_DEV_SERVER ??
18
+ process.env.VITE_DEV_SERVER_URL ??
19
+ process.env.ELECTRON_RENDERER_URL);
20
+ const isSourceRun = path.basename(__dirname) === "src" && existsSync(devPluginEntry);
21
+ export function getCodeNomadPluginUrl() {
22
+ if (isDevBuild || isSourceRun) {
23
+ if (!existsSync(devPluginEntry)) {
24
+ throw new Error(`CodeNomad OpenCode plugin entry missing at ${devPluginEntry}`);
25
+ }
26
+ log.debug({ pluginEntry: devPluginEntry }, "Using OpenCode plugin source directly (dev mode)");
27
+ return pathToFileURL(devPluginEntry).href;
28
+ }
29
+ for (const dir of prodPluginDirs) {
30
+ const tarball = findPluginTarball(dir);
31
+ if (tarball) {
32
+ return toNpmFileSpecifier(tarball);
33
+ }
34
+ }
35
+ throw new Error(`CodeNomad OpenCode plugin package missing in ${prodPluginDirs.join(", ")}`);
36
+ }
37
+ export function buildOpencodeConfigContent(existingContent, pluginUrl) {
38
+ const config = existingContent?.trim() ? parseJsoncObject(existingContent) : {};
39
+ const existingPlugins = normalizePluginEntries(config.plugin);
40
+ if (!existingPlugins.includes(pluginUrl)) {
41
+ existingPlugins.push(pluginUrl);
42
+ }
43
+ return JSON.stringify({
44
+ "$schema": typeof config["$schema"] === "string" ? config["$schema"] : "https://opencode.ai/config.json",
45
+ ...config,
46
+ plugin: existingPlugins,
47
+ }, null, 2);
48
+ }
49
+ export function resolveExistingOpencodeConfigContent(userEnvironment) {
50
+ const userValue = normalizeConfigContentValue(userEnvironment.OPENCODE_CONFIG_CONTENT);
51
+ if (userValue) {
52
+ return userValue;
53
+ }
54
+ return normalizeConfigContentValue(process.env.OPENCODE_CONFIG_CONTENT);
55
+ }
56
+ function toNpmFileSpecifier(filePath) {
57
+ return `${pluginPackageName}@file:${filePath.replace(/\\/g, "/")}`;
58
+ }
59
+ function findPluginTarball(dir) {
60
+ if (!existsSync(dir)) {
61
+ return null;
62
+ }
63
+ const tarballs = readdirSync(dir)
64
+ .filter((name) => name.endsWith(".tgz"))
65
+ .sort();
66
+ return tarballs.length > 0 ? path.resolve(dir, tarballs[tarballs.length - 1]) : null;
67
+ }
68
+ function normalizeConfigContentValue(value) {
69
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
70
+ }
71
+ function parseJsoncObject(content) {
72
+ try {
73
+ const parsed = JSON.parse(stripJsonc(content));
74
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
75
+ throw new Error("OPENCODE_CONFIG_CONTENT must be a JSON object");
76
+ }
77
+ return parsed;
78
+ }
79
+ catch (error) {
80
+ const reason = error instanceof Error ? error.message : String(error);
81
+ throw new Error(`Failed to parse OPENCODE_CONFIG_CONTENT: ${reason}`);
82
+ }
83
+ }
84
+ function normalizePluginEntries(value) {
85
+ if (value === undefined) {
86
+ return [];
87
+ }
88
+ if (typeof value === "string") {
89
+ return [value];
90
+ }
91
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
92
+ return [...value];
93
+ }
94
+ throw new Error("OPENCODE_CONFIG_CONTENT plugin field must be a string or string array");
95
+ }
96
+ function stripJsonc(input) {
97
+ let output = "";
98
+ let inString = false;
99
+ let escape = false;
100
+ for (let index = 0; index < input.length; index += 1) {
101
+ const char = input[index];
102
+ const next = input[index + 1];
103
+ if (escape) {
104
+ output += char;
105
+ escape = false;
106
+ continue;
107
+ }
108
+ if (char === "\\" && inString) {
109
+ output += char;
110
+ escape = true;
111
+ continue;
112
+ }
113
+ if (char === '"') {
114
+ output += char;
115
+ inString = !inString;
116
+ continue;
117
+ }
118
+ if (!inString && char === "/" && next === "/") {
119
+ while (index < input.length && input[index] !== "\n") {
120
+ index += 1;
121
+ }
122
+ output += "\n";
123
+ continue;
124
+ }
125
+ if (!inString && char === "/" && next === "*") {
126
+ index += 2;
127
+ while (index < input.length && !(input[index] === "*" && input[index + 1] === "/")) {
128
+ output += input[index] === "\n" ? "\n" : "";
129
+ index += 1;
130
+ }
131
+ index += 1;
132
+ continue;
133
+ }
134
+ if (!inString && char === ",") {
135
+ let lookahead = index + 1;
136
+ while (lookahead < input.length && /\s/.test(input[lookahead])) {
137
+ lookahead += 1;
138
+ }
139
+ if (input[lookahead] === "}" || input[lookahead] === "]") {
140
+ continue;
141
+ }
142
+ }
143
+ output += char;
144
+ }
145
+ return output;
146
+ }
@@ -0,0 +1,28 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { buildOpencodeConfigContent } from "./opencode-plugin";
4
+ describe("buildOpencodeConfigContent", () => {
5
+ it("creates config content with the CodeNomad plugin", () => {
6
+ const content = buildOpencodeConfigContent(undefined, "file:///plugin.tgz");
7
+ assert.deepEqual(JSON.parse(content), {
8
+ "$schema": "https://opencode.ai/config.json",
9
+ plugin: ["file:///plugin.tgz"],
10
+ });
11
+ });
12
+ it("merges with existing JSONC content", () => {
13
+ const content = buildOpencodeConfigContent(`{
14
+ // user plugin
15
+ "plugin": ["npm:user-plugin",],
16
+ "model": "test-model",
17
+ }`, "file:///plugin.tgz");
18
+ assert.deepEqual(JSON.parse(content), {
19
+ "$schema": "https://opencode.ai/config.json",
20
+ plugin: ["npm:user-plugin", "file:///plugin.tgz"],
21
+ model: "test-model",
22
+ });
23
+ });
24
+ it("does not duplicate the CodeNomad plugin", () => {
25
+ const content = buildOpencodeConfigContent('{"plugin":["file:///plugin.tgz"]}', "file:///plugin.tgz");
26
+ assert.deepEqual(JSON.parse(content).plugin, ["file:///plugin.tgz"]);
27
+ });
28
+ });
@@ -34,12 +34,12 @@ describe("buildWindowsSpawnSpec", () => {
34
34
  const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve", "--port", "0"], {
35
35
  cwd: String.raw `\\wsl.localhost\Ubuntu\home\dev\workspace`,
36
36
  env: {
37
- OPENCODE_CONFIG_DIR: String.raw `C:\Users\dev\AppData\Roaming\CodeNomad\opencode-config`,
37
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({ plugin: ["file:///C:/Users/dev/AppData/Roaming/CodeNomad/plugin.tgz"] }),
38
38
  CODENOMAD_INSTANCE_ID: "workspace-123",
39
39
  OPENCODE_SERVER_BASE_URL: "https://127.0.0.1:4321/workspaces/workspace-123/worktrees/root/instance",
40
40
  OPENCODE_SERVER_PASSWORD: "secret",
41
41
  },
42
- propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "CODENOMAD_INSTANCE_ID", "OPENCODE_SERVER_BASE_URL", "OPENCODE_SERVER_PASSWORD"],
42
+ propagateEnvKeys: ["OPENCODE_CONFIG_CONTENT", "CODENOMAD_INSTANCE_ID", "OPENCODE_SERVER_BASE_URL", "OPENCODE_SERVER_PASSWORD"],
43
43
  });
44
44
  assert.equal(spec.command, "wsl.exe");
45
45
  assert.deepEqual(spec.args, [
@@ -54,17 +54,35 @@ describe("buildWindowsSpawnSpec", () => {
54
54
  "0",
55
55
  ]);
56
56
  assert.equal(spec.cwd, undefined);
57
- assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:CODENOMAD_INSTANCE_ID:OPENCODE_SERVER_BASE_URL:OPENCODE_SERVER_PASSWORD");
57
+ assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_CONTENT:CODENOMAD_INSTANCE_ID:OPENCODE_SERVER_BASE_URL:OPENCODE_SERVER_PASSWORD");
58
58
  });
59
- it("upgrades existing WSLENV path entries to include /p", () => {
59
+ it("preserves non-path OPENCODE_CONFIG_CONTENT WSLENV entries", () => {
60
60
  const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve"], {
61
61
  env: {
62
- OPENCODE_CONFIG_DIR: String.raw `C:\Users\dev\AppData\Roaming\CodeNomad\opencode-config`,
63
- WSLENV: "OPENCODE_CONFIG_DIR:CODENOMAD_INSTANCE_ID/u",
62
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({ plugin: ["file:///C:/Users/dev/AppData/Roaming/CodeNomad/plugin.tgz"] }),
63
+ WSLENV: "OPENCODE_CONFIG_CONTENT:CODENOMAD_INSTANCE_ID/u",
64
64
  },
65
- propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "CODENOMAD_INSTANCE_ID"],
65
+ propagateEnvKeys: ["OPENCODE_CONFIG_CONTENT", "CODENOMAD_INSTANCE_ID"],
66
66
  });
67
- assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:CODENOMAD_INSTANCE_ID/u");
67
+ assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_CONTENT:CODENOMAD_INSTANCE_ID/u");
68
+ });
69
+ it("rewrites packaged plugin paths for WSL before launching", () => {
70
+ const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve"], {
71
+ env: {
72
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
73
+ plugin: [
74
+ "@codenomad/codenomad-opencode-plugin@file:C:/Users/dev/AppData/Roaming/CodeNomad/codenomad-opencode-plugin.tgz",
75
+ ],
76
+ }),
77
+ },
78
+ propagateEnvKeys: ["OPENCODE_CONFIG_CONTENT"],
79
+ });
80
+ assert.equal(spec.command, "wsl.exe");
81
+ assert.equal(spec.env?.CODENOMAD_OPENCODE_PLUGIN_WSL_PATH, String.raw `C:\Users\dev\AppData\Roaming\CodeNomad\codenomad-opencode-plugin.tgz`);
82
+ assert.match(spec.env?.OPENCODE_CONFIG_CONTENT ?? "", /__CODENOMAD_OPENCODE_PLUGIN_WSL_PATH__/);
83
+ assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_CONTENT:CODENOMAD_OPENCODE_PLUGIN_WSL_PATH/p");
84
+ assert.deepEqual(spec.args.slice(0, 4), ["--distribution", "Ubuntu", "--exec", "sh"]);
85
+ assert.match(spec.args[5] ?? "", /CODENOMAD_OPENCODE_PLUGIN_WSL_PATH/);
68
86
  });
69
87
  it("propagates inherited known path variables even when they are not explicitly requested", () => {
70
88
  const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve"], {
@@ -5,7 +5,7 @@ import { FileSystemBrowser } from "../filesystem/browser";
5
5
  import { searchWorkspaceFiles } from "../filesystem/search";
6
6
  import { clearWorkspaceSearchCache } from "../filesystem/search-cache";
7
7
  import { WorkspaceRuntime } from "./runtime";
8
- import { getOpencodeConfigDir } from "../opencode-config.js";
8
+ import { buildOpencodeConfigContent, getCodeNomadPluginUrl, resolveExistingOpencodeConfigContent, } from "../opencode-plugin.js";
9
9
  import { OPENCODE_SERVER_BASE_URL_ENV, buildOpencodeBasicAuthHeader, OPENCODE_SERVER_PASSWORD_ENV, OPENCODE_SERVER_USERNAME_ENV, resolveOpencodeServerAuth, } from "./opencode-auth";
10
10
  const STARTUP_STABILITY_DELAY_MS = 1500;
11
11
  export class WorkspaceManager {
@@ -14,7 +14,7 @@ export class WorkspaceManager {
14
14
  this.workspaces = new Map();
15
15
  this.opencodeAuth = new Map();
16
16
  this.runtime = new WorkspaceRuntime(this.options.eventBus, this.options.logger);
17
- this.opencodeConfigDir = getOpencodeConfigDir();
17
+ this.codeNomadPluginUrl = getCodeNomadPluginUrl();
18
18
  }
19
19
  list() {
20
20
  return Array.from(this.workspaces.values());
@@ -79,6 +79,7 @@ export class WorkspaceManager {
79
79
  const serverConfig = this.options.settings.getOwner("config", "server");
80
80
  const envVars = serverConfig?.environmentVariables;
81
81
  const userEnvironment = envVars && typeof envVars === "object" && !Array.isArray(envVars) ? envVars : {};
82
+ const opencodeConfigContent = buildOpencodeConfigContent(resolveExistingOpencodeConfigContent(userEnvironment), this.codeNomadPluginUrl);
82
83
  const serverBaseUrl = this.options.getServerBaseUrl();
83
84
  const normalizedServerBaseUrl = serverBaseUrl.replace(/\/+$/, "");
84
85
  const { username: opencodeUsername, password: opencodePassword } = resolveOpencodeServerAuth({
@@ -92,7 +93,7 @@ export class WorkspaceManager {
92
93
  this.opencodeAuth.set(id, { username: opencodeUsername, password: opencodePassword, authorization });
93
94
  const environment = {
94
95
  ...userEnvironment,
95
- OPENCODE_CONFIG_DIR: this.opencodeConfigDir,
96
+ OPENCODE_CONFIG_CONTENT: opencodeConfigContent,
96
97
  CODENOMAD_INSTANCE_ID: id,
97
98
  CODENOMAD_BASE_URL: serverBaseUrl,
98
99
  ...(this.options.nodeExtraCaCertsPath ? { NODE_EXTRA_CA_CERTS: this.options.nodeExtraCaCertsPath } : {}),
@@ -4,7 +4,11 @@ export const WINDOWS_CMD_EXTENSIONS = new Set([".cmd", ".bat"]);
4
4
  export const WINDOWS_POWERSHELL_EXTENSIONS = new Set([".ps1"]);
5
5
  const VERSION_REGEX = /([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/;
6
6
  const WSL_UNC_PATH_REGEX = /^\\\\wsl(?:\.localhost|\$)\\([^\\/]+)(?:[\\/](.*))?$/i;
7
- const WSL_PATH_ENV_KEYS = new Set(["OPENCODE_CONFIG_DIR", "NODE_EXTRA_CA_CERTS"]);
7
+ const CODENOMAD_PLUGIN_PACKAGE_NAME = "@codenomad/codenomad-opencode-plugin";
8
+ const WSL_PLUGIN_PATH_ENV = "CODENOMAD_OPENCODE_PLUGIN_WSL_PATH";
9
+ const WSL_PLUGIN_PATH_PLACEHOLDER = "__CODENOMAD_OPENCODE_PLUGIN_WSL_PATH__";
10
+ const CODENOMAD_PLUGIN_FILE_SPEC_REGEX = new RegExp(`(${escapeRegex(CODENOMAD_PLUGIN_PACKAGE_NAME)}@file:)([A-Za-z]:[^"\\r\\n]+?\\.tgz)`);
11
+ const WSL_PATH_ENV_KEYS = new Set(["NODE_EXTRA_CA_CERTS", WSL_PLUGIN_PATH_ENV]);
8
12
  export function parseWslUncPath(input) {
9
13
  const normalized = input.trim().replace(/\//g, "\\");
10
14
  const match = normalized.match(WSL_UNC_PATH_REGEX);
@@ -129,16 +133,18 @@ export function probeBinaryVersion(binaryPath) {
129
133
  }
130
134
  function buildWslSpawnSpec(wslPath, args, options) {
131
135
  const workingDirectory = options.cwd ? resolveWslWorkingDirectory(options.cwd, wslPath.distro) : undefined;
136
+ const env = buildWslEnvironment(options.env, options.propagateEnvKeys);
137
+ const shouldTranslatePluginPath = Boolean(env?.[WSL_PLUGIN_PATH_ENV]);
132
138
  if (options.cwd && !workingDirectory) {
133
139
  throw new Error(`Unable to translate workspace folder for WSL binary in distro "${wslPath.distro}": ${options.cwd}`);
134
140
  }
135
141
  const wslArgs = ["--distribution", wslPath.distro];
136
- const shouldWrapWithShell = Boolean(options.wslPidMarker) || workingDirectory?.kind === "windows";
142
+ const shouldWrapWithShell = Boolean(options.wslPidMarker) || workingDirectory?.kind === "windows" || shouldTranslatePluginPath;
137
143
  if (!shouldWrapWithShell && workingDirectory?.kind === "linux") {
138
144
  wslArgs.push("--cd", workingDirectory.path);
139
145
  }
140
146
  if (shouldWrapWithShell) {
141
- const launchScript = buildWslLaunchScript(workingDirectory ?? undefined, options.wslPidMarker);
147
+ const launchScript = buildWslLaunchScript(workingDirectory ?? undefined, options.wslPidMarker, shouldTranslatePluginPath);
142
148
  wslArgs.push("--exec", "sh", "-lc", launchScript, "codenomad-wsl-launch");
143
149
  if (workingDirectory) {
144
150
  wslArgs.push(workingDirectory.path);
@@ -152,11 +158,11 @@ function buildWslSpawnSpec(wslPath, args, options) {
152
158
  command: "wsl.exe",
153
159
  args: wslArgs,
154
160
  options: {},
155
- env: buildWslEnvironment(options.env, options.propagateEnvKeys),
161
+ env,
156
162
  wsl: { distro: wslPath.distro, pidMarker: options.wslPidMarker },
157
163
  };
158
164
  }
159
- function buildWslLaunchScript(workingDirectory, pidMarker) {
165
+ function buildWslLaunchScript(workingDirectory, pidMarker, translatePluginPath) {
160
166
  const steps = [];
161
167
  if (pidMarker) {
162
168
  steps.push(`printf '%s%s\\n' '${pidMarker}' "$$"`);
@@ -169,6 +175,9 @@ function buildWslLaunchScript(workingDirectory, pidMarker) {
169
175
  steps.push('cd "$(wslpath -au "$1")"');
170
176
  steps.push("shift");
171
177
  }
178
+ if (translatePluginPath) {
179
+ steps.push(`if [ -n "$${WSL_PLUGIN_PATH_ENV}" ] && [ -n "$OPENCODE_CONFIG_CONTENT" ]; then escaped_plugin_path=$(printf '%s' "$${WSL_PLUGIN_PATH_ENV}" | sed 's/[\\&|]/\\\\&/g'); OPENCODE_CONFIG_CONTENT=$(printf '%s' "$OPENCODE_CONFIG_CONTENT" | sed "s|${WSL_PLUGIN_PATH_PLACEHOLDER}|$escaped_plugin_path|g"); export OPENCODE_CONFIG_CONTENT; unset ${WSL_PLUGIN_PATH_ENV}; fi`);
180
+ }
172
181
  steps.push('exec "$@"');
173
182
  return steps.join(" && ");
174
183
  }
@@ -186,14 +195,15 @@ function buildWslEnvironment(env, propagateEnvKeys) {
186
195
  if (!env) {
187
196
  return env;
188
197
  }
198
+ const next = { ...env };
199
+ rewriteOpencodePluginPathForWsl(next);
189
200
  const keysToPropagate = Array.from(new Set([
190
- ...(propagateEnvKeys ?? []).filter((key) => env[key] !== undefined),
191
- ...Array.from(WSL_PATH_ENV_KEYS).filter((key) => env[key] !== undefined),
201
+ ...(propagateEnvKeys ?? []).filter((key) => next[key] !== undefined),
202
+ ...Array.from(WSL_PATH_ENV_KEYS).filter((key) => next[key] !== undefined),
192
203
  ]));
193
204
  if (keysToPropagate.length === 0) {
194
- return env;
205
+ return next;
195
206
  }
196
- const next = { ...env };
197
207
  const entries = (next.WSLENV ?? "").split(":").filter((entry) => entry.length > 0);
198
208
  const byName = new Map(entries.map((entry) => [entry.split("/")[0] ?? entry, entry]));
199
209
  for (const key of keysToPropagate) {
@@ -207,6 +217,19 @@ function buildWslEnvironment(env, propagateEnvKeys) {
207
217
  next.WSLENV = Array.from(byName.values()).join(":");
208
218
  return next;
209
219
  }
220
+ function rewriteOpencodePluginPathForWsl(env) {
221
+ const content = env.OPENCODE_CONFIG_CONTENT;
222
+ if (!content) {
223
+ return;
224
+ }
225
+ const match = content.match(CODENOMAD_PLUGIN_FILE_SPEC_REGEX);
226
+ const hostPath = match?.[2];
227
+ if (!hostPath) {
228
+ return;
229
+ }
230
+ env.OPENCODE_CONFIG_CONTENT = content.replace(hostPath, WSL_PLUGIN_PATH_PLACEHOLDER);
231
+ env[WSL_PLUGIN_PATH_ENV] = path.win32.normalize(hostPath);
232
+ }
210
233
  function ensureWslenvEntry(entry, requiresPathTranslation) {
211
234
  if (!requiresPathTranslation) {
212
235
  return entry;
@@ -217,3 +240,6 @@ function ensureWslenvEntry(entry, requiresPathTranslation) {
217
240
  }
218
241
  return rawFlags.length > 0 ? `${name}/${rawFlags}p` : `${name}/p`;
219
242
  }
243
+ function escapeRegex(input) {
244
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
245
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuralnomads/codenomad-dev",
3
- "version": "0.15.0-dev-20260512-91652990",
3
+ "version": "0.15.0-dev-20260512-cf88dc06",
4
4
  "description": "CodeNomad Server",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -17,10 +17,10 @@
17
17
  "codenomad": "dist/bin.js"
18
18
  },
19
19
  "scripts": {
20
- "build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && node ./scripts/copy-auth-pages.mjs && npm run prepare-config",
20
+ "build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && node ./scripts/copy-auth-pages.mjs && npm run prepare-plugin",
21
21
  "build:ui": "npm run build --prefix ../ui",
22
22
  "prepare-ui": "node ./scripts/copy-ui-dist.mjs",
23
- "prepare-config": "node ./scripts/copy-opencode-config.mjs",
23
+ "prepare-plugin": "node ./scripts/package-opencode-plugin.mjs",
24
24
  "dev": "cross-env CODENOMAD_DEV=1 CODENOMAD_SERVER_PASSWORD=codenomad-dev CLI_UI_DEV_SERVER=http://localhost:3000 CLI_HTTPS=false CLI_HTTP=true tsx src/index.ts",
25
25
  "typecheck": "tsc --noEmit -p tsconfig.json"
26
26
  },
package/public/index.html CHANGED
@@ -30,8 +30,8 @@
30
30
  <link rel="modulepreload" crossorigin href="/assets/git-diff-vendor-CSgooKT_.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/monaco-viewer-B3iAKbO0.js">
32
32
  <link rel="modulepreload" crossorigin href="/assets/index-Bo2Zanex.js">
33
- <link rel="stylesheet" crossorigin href="/assets/git-diff-vendor-HAZkIolJ.css">
34
33
  <link rel="stylesheet" crossorigin href="/assets/index-C2gIF7lT.css">
34
+ <link rel="stylesheet" crossorigin href="/assets/git-diff-vendor-HAZkIolJ.css">
35
35
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script>
36
36
  <meta name="theme-color" content="#1a1a1a">
37
37
  <link rel="icon" href="/favicon.ico" sizes="48x48">
@@ -20,8 +20,8 @@
20
20
  <link rel="modulepreload" crossorigin href="/assets/git-diff-vendor-CSgooKT_.js">
21
21
  <link rel="modulepreload" crossorigin href="/assets/monaco-viewer-B3iAKbO0.js">
22
22
  <link rel="modulepreload" crossorigin href="/assets/index-Bo2Zanex.js">
23
- <link rel="stylesheet" crossorigin href="/assets/index-C2gIF7lT.css">
24
23
  <link rel="stylesheet" crossorigin href="/assets/git-diff-vendor-HAZkIolJ.css">
24
+ <link rel="stylesheet" crossorigin href="/assets/index-C2gIF7lT.css">
25
25
  <link rel="stylesheet" crossorigin href="/assets/loading-CmEVQgyj.css">
26
26
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script>
27
27
  <meta name="theme-color" content="#1a1a1a">
@@ -1,32 +0,0 @@
1
- # opencode-config
2
-
3
- ## TLDR
4
- Template config + plugins injected into every OpenCode instance that CodeNomad launches. It provides a CodeNomad bridge plugin for local event exchange between the CLI server and opencode.
5
-
6
- ## What it is
7
- A packaged config directory that CodeNomad copies into `~/.config/codenomad/opencode-config` for production builds or uses directly in dev. OpenCode autoloads any `plugin/*.ts` or `plugin/*.js` from this directory.
8
-
9
- ## How it works
10
- - CodeNomad sets `OPENCODE_CONFIG_DIR` when spawning each opencode instance (`packages/server/src/workspaces/manager.ts`).
11
- - This template is synced from `packages/opencode-config` (`packages/server/src/opencode-config.ts`, `packages/server/scripts/copy-opencode-config.mjs`).
12
- - OpenCode autoloads plugins from `plugin/` (`packages/opencode-config/plugin/codenomad.ts`).
13
- - The `CodeNomadPlugin` reads `CODENOMAD_INSTANCE_ID` + `CODENOMAD_BASE_URL`, connects to `GET /workspaces/:id/plugin/events`, and posts to `POST /workspaces/:id/plugin/event` (`packages/opencode-config/plugin/lib/client.ts`).
14
- - The server exposes the plugin routes and maps events into the UI SSE pipeline (`packages/server/src/server/routes/plugin.ts`, `packages/server/src/plugins/handlers.ts`).
15
-
16
- ## Expectations
17
- - Local-only bridge (no auth/token yet).
18
- - Plugin must fail startup if it cannot connect after 3 retries.
19
- - Keep plugin entrypoints thin; put shared logic under `plugin/lib/` to avoid autoloaded helpers.
20
- - Keep event shapes small and explicit; use `type` + `properties` only.
21
-
22
- ## Ideas
23
- - Add feature modules under `plugin/lib/features/` (tool lifecycle, permission prompts, custom commands).
24
- - Expand `/workspaces/:id/plugin/*` with dedicated endpoints as needed.
25
- - Promote stable event shapes and version tags once the protocol settles.
26
-
27
- ## Pointers
28
- - Plugin entry: `packages/opencode-config/plugin/codenomad.ts`
29
- - Plugin client: `packages/opencode-config/plugin/lib/client.ts`
30
- - Plugin server routes: `packages/server/src/server/routes/plugin.ts`
31
- - Plugin event handling: `packages/server/src/plugins/handlers.ts`
32
- - Workspace env injection: `packages/server/src/workspaces/manager.ts`
@@ -1,3 +0,0 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json"
3
- }