@node9/proxy 1.24.3 → 1.25.0

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.
package/dist/cli.js CHANGED
@@ -6463,6 +6463,122 @@ var init_mcp_pin = __esm({
6463
6463
  }
6464
6464
  });
6465
6465
 
6466
+ // src/setup-opencode-shim.ts
6467
+ function renderOpencodeShim(input) {
6468
+ const { node9Argv, version: version2 } = input;
6469
+ return `// Auto-generated by \`node9 init\`. Do not edit \u2014 re-run init to upgrade.
6470
+ // NODE9_SHIM_VERSION = "${version2}"
6471
+ //
6472
+ // node9 protection shim for Opencode. Wires three hooks against the
6473
+ // agent's plugin API and shells out to the node9 CLI for verdicts.
6474
+ //
6475
+ // Block by throwing from a hook handler; Opencode's plugin trigger
6476
+ // (packages/opencode/src/plugin/index.ts:273) propagates the error and
6477
+ // halts the tool call.
6478
+
6479
+ const { spawnSync } = require("node:child_process");
6480
+
6481
+ // argv prefix for invoking the node9 CLI. argv[0] is the executable
6482
+ // (either the npm-installed wrapper script or the node binary); the
6483
+ // remaining entries (if any) are passed as the leading args before
6484
+ // the subcommand name (e.g. ["/path/to/dist/cli.js"] for dev mode).
6485
+ const NODE9_ARGV = ${JSON.stringify(node9Argv)};
6486
+ const HOOK_TIMEOUT_MS = 30000;
6487
+ const LOG_TIMEOUT_MS = 5000;
6488
+
6489
+ function parseReason(stdout) {
6490
+ // node9 check emits {decision, reason, message} JSON on stdout when
6491
+ // blocking. Fall back to a generic string if anything goes wrong.
6492
+ try {
6493
+ const v = JSON.parse(stdout || "");
6494
+ return v && (v.reason || v.message);
6495
+ } catch (e) {
6496
+ return null;
6497
+ }
6498
+ }
6499
+
6500
+ function extractPromptText(parts) {
6501
+ // chat.message gives us parts: Part[]. We only DLP-scan text parts
6502
+ // (image / tool_use parts can't contain pasted secrets in any form
6503
+ // node9's scanner currently recognizes).
6504
+ if (!Array.isArray(parts)) return "";
6505
+ return parts
6506
+ .filter((p) => p && p.type === "text")
6507
+ .map((p) => (p && p.text) || "")
6508
+ .join("\\n");
6509
+ }
6510
+
6511
+ module.exports = {
6512
+ id: "node9",
6513
+ server: async (input) => ({
6514
+ "tool.execute.before": async (ctx, mutable) => {
6515
+ const payload = {
6516
+ hook_event_name: "PreToolUse",
6517
+ tool_name: ctx.tool,
6518
+ tool_input: mutable.args,
6519
+ session_id: ctx.sessionID,
6520
+ cwd: input.directory,
6521
+ meta: { agent: "Opencode" },
6522
+ };
6523
+ const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
6524
+ input: JSON.stringify(payload),
6525
+ encoding: "utf-8",
6526
+ timeout: HOOK_TIMEOUT_MS,
6527
+ });
6528
+ if (r.status === 0) return;
6529
+ const reason = parseReason(r.stdout) || "blocked by node9";
6530
+ throw new Error("[node9] " + reason);
6531
+ },
6532
+
6533
+ "tool.execute.after": async (ctx) => {
6534
+ // Fire-and-forget audit log \u2014 failures here must NEVER throw,
6535
+ // or we'd retroactively "block" a tool call that already ran.
6536
+ const payload = {
6537
+ hook_event_name: "PostToolUse",
6538
+ tool_name: ctx.tool,
6539
+ session_id: ctx.sessionID,
6540
+ cwd: input.directory,
6541
+ meta: { agent: "Opencode" },
6542
+ };
6543
+ try {
6544
+ spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "log"], {
6545
+ input: JSON.stringify(payload),
6546
+ encoding: "utf-8",
6547
+ timeout: LOG_TIMEOUT_MS,
6548
+ });
6549
+ } catch (e) {
6550
+ // Swallow: audit log gaps are preferable to crashing the agent.
6551
+ }
6552
+ },
6553
+
6554
+ "chat.message": async (ctx, mutable) => {
6555
+ const prompt = extractPromptText(mutable.parts);
6556
+ if (!prompt) return;
6557
+ const payload = {
6558
+ hook_event_name: "UserPromptSubmit",
6559
+ prompt,
6560
+ session_id: ctx.sessionID,
6561
+ meta: { agent: "Opencode" },
6562
+ };
6563
+ const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
6564
+ input: JSON.stringify(payload),
6565
+ encoding: "utf-8",
6566
+ timeout: HOOK_TIMEOUT_MS,
6567
+ });
6568
+ if (r.status === 0) return;
6569
+ const reason = parseReason(r.stdout) || "prompt blocked";
6570
+ throw new Error("[node9] " + reason);
6571
+ },
6572
+ }),
6573
+ };
6574
+ `;
6575
+ }
6576
+ var init_setup_opencode_shim = __esm({
6577
+ "src/setup-opencode-shim.ts"() {
6578
+ "use strict";
6579
+ }
6580
+ });
6581
+
6466
6582
  // src/setup.ts
6467
6583
  function hasNode9McpServer(servers) {
6468
6584
  const entry = servers["node9"];
@@ -6940,7 +7056,8 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
6940
7056
  codex: exists(import_path15.default.join(homeDir2, ".codex")),
6941
7057
  windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
6942
7058
  vscode: exists(import_path15.default.join(homeDir2, ".vscode")),
6943
- claudeDesktop: desktopPath !== null && exists(import_path15.default.dirname(desktopPath))
7059
+ claudeDesktop: desktopPath !== null && exists(import_path15.default.dirname(desktopPath)),
7060
+ opencode: exists(import_path15.default.join(homeDir2, ".config", "opencode"))
6944
7061
  };
6945
7062
  }
6946
7063
  async function setupCursor() {
@@ -7573,6 +7690,127 @@ function teardownClaudeDesktop() {
7573
7690
  console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
7574
7691
  }
7575
7692
  }
7693
+ function node9ArgvForShim() {
7694
+ if (process.env.NODE9_TESTING === "1") return ["node9"];
7695
+ const nodeExec = process.execPath;
7696
+ const cliScript = process.argv[1];
7697
+ if (cliScript && cliScript.endsWith(".js")) return [nodeExec, cliScript];
7698
+ return [cliScript];
7699
+ }
7700
+ function node9Version() {
7701
+ try {
7702
+ const pkg = JSON.parse(
7703
+ import_fs13.default.readFileSync(import_path15.default.join(__dirname, "..", "package.json"), "utf-8")
7704
+ );
7705
+ return pkg.version ?? "0.0.0";
7706
+ } catch {
7707
+ return "0.0.0";
7708
+ }
7709
+ }
7710
+ async function setupOpencode() {
7711
+ seedMcpPinsIfMissing();
7712
+ const homeDir2 = import_os12.default.homedir();
7713
+ const configDir = import_path15.default.join(homeDir2, ".config", "opencode");
7714
+ const pluginsDir = import_path15.default.join(configDir, "plugins");
7715
+ const configPath = import_path15.default.join(configDir, "opencode.json");
7716
+ const pluginPath = import_path15.default.join(pluginsDir, OPENCODE_PLUGIN_NAME);
7717
+ try {
7718
+ import_fs13.default.mkdirSync(pluginsDir, { recursive: true });
7719
+ } catch (err2) {
7720
+ const code = err2.code;
7721
+ if (code !== "EEXIST") {
7722
+ console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not create ${pluginsDir}: ${code ?? String(err2)}`));
7723
+ return;
7724
+ }
7725
+ }
7726
+ const shimContent = renderOpencodeShim({
7727
+ node9Argv: node9ArgvForShim(),
7728
+ version: node9Version()
7729
+ });
7730
+ let pluginChanged = false;
7731
+ const existingShim = (() => {
7732
+ try {
7733
+ return import_fs13.default.readFileSync(pluginPath, "utf-8");
7734
+ } catch {
7735
+ return null;
7736
+ }
7737
+ })();
7738
+ if (existingShim !== shimContent) {
7739
+ import_fs13.default.writeFileSync(pluginPath, shimContent);
7740
+ pluginChanged = true;
7741
+ if (existingShim) {
7742
+ console.log(import_chalk.default.yellow(" \u{1F527} Opencode plugin shim updated to current version"));
7743
+ } else {
7744
+ console.log(
7745
+ import_chalk.default.green(" \u2705 Opencode plugin installed \u2192 tool.execute.before / after, chat.message")
7746
+ );
7747
+ }
7748
+ }
7749
+ const config = readJson(configPath) ?? {};
7750
+ const mcp = config.mcp ?? {};
7751
+ let configChanged = false;
7752
+ const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
7753
+ const desiredEntry = {
7754
+ type: "local",
7755
+ command: desiredCommand,
7756
+ enabled: true
7757
+ };
7758
+ const existing = mcp["node9"];
7759
+ const entryMatches = existing && existing.type === "local" && Array.isArray(existing.command) && existing.command.length === desiredCommand.length && existing.command.every((c, i) => c === desiredCommand[i]) && existing.enabled !== false;
7760
+ if (!entryMatches) {
7761
+ mcp["node9"] = desiredEntry;
7762
+ config.mcp = mcp;
7763
+ configChanged = true;
7764
+ if (existing) {
7765
+ console.log(import_chalk.default.yellow(" \u{1F527} Opencode MCP entry updated (node9)"));
7766
+ } else {
7767
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
7768
+ }
7769
+ }
7770
+ if (configChanged) writeJson(configPath, config);
7771
+ if (pluginChanged || configChanged) {
7772
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
7773
+ console.log(import_chalk.default.gray(" Restart Opencode for changes to take effect."));
7774
+ printDaemonTip();
7775
+ } else {
7776
+ console.log(import_chalk.default.blue(" \u2139\uFE0F Node9 is already fully configured for Opencode."));
7777
+ }
7778
+ }
7779
+ function teardownOpencode() {
7780
+ const homeDir2 = import_os12.default.homedir();
7781
+ const configDir = import_path15.default.join(homeDir2, ".config", "opencode");
7782
+ const pluginsDir = import_path15.default.join(configDir, "plugins");
7783
+ const configPath = import_path15.default.join(configDir, "opencode.json");
7784
+ const pluginPath = import_path15.default.join(pluginsDir, OPENCODE_PLUGIN_NAME);
7785
+ try {
7786
+ if (import_fs13.default.existsSync(pluginPath)) {
7787
+ import_fs13.default.unlinkSync(pluginPath);
7788
+ console.log(import_chalk.default.green(" \u2705 Removed node9 plugin from ~/.config/opencode/plugins/"));
7789
+ }
7790
+ } catch (err2) {
7791
+ console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
7792
+ }
7793
+ const config = readJson(configPath);
7794
+ if (!config) {
7795
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
7796
+ return;
7797
+ }
7798
+ const mcp = config.mcp ?? {};
7799
+ let changed = false;
7800
+ if (mcp["node9"]) {
7801
+ delete mcp["node9"];
7802
+ changed = true;
7803
+ console.log(
7804
+ import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.config/opencode/opencode.json")
7805
+ );
7806
+ }
7807
+ if (changed) {
7808
+ config.mcp = mcp;
7809
+ writeJson(configPath, config);
7810
+ } else {
7811
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
7812
+ }
7813
+ }
7576
7814
  function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
7577
7815
  const detected = detectAgents(homeDir2);
7578
7816
  const claudeWired = (() => {
@@ -7655,10 +7893,30 @@ function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
7655
7893
  return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
7656
7894
  })(),
7657
7895
  mode: detected.claudeDesktop ? "mcp" : null
7896
+ },
7897
+ {
7898
+ name: "opencode",
7899
+ label: "Opencode",
7900
+ installed: detected.opencode,
7901
+ wired: (() => {
7902
+ const pluginPath = import_path15.default.join(
7903
+ homeDir2,
7904
+ ".config",
7905
+ "opencode",
7906
+ "plugins",
7907
+ OPENCODE_PLUGIN_NAME
7908
+ );
7909
+ if (import_fs13.default.existsSync(pluginPath)) return true;
7910
+ const cfg = readJson(
7911
+ import_path15.default.join(homeDir2, ".config", "opencode", "opencode.json")
7912
+ );
7913
+ return !!cfg?.mcp?.["node9"];
7914
+ })(),
7915
+ mode: detected.opencode ? "hooks" : null
7658
7916
  }
7659
7917
  ];
7660
7918
  }
7661
- var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS;
7919
+ var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME;
7662
7920
  var init_setup = __esm({
7663
7921
  "src/setup.ts"() {
7664
7922
  "use strict";
@@ -7669,8 +7927,10 @@ var init_setup = __esm({
7669
7927
  import_prompts = require("@inquirer/prompts");
7670
7928
  import_smol_toml = require("smol-toml");
7671
7929
  init_mcp_pin();
7930
+ init_setup_opencode_shim();
7672
7931
  NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
7673
7932
  CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
7933
+ OPENCODE_PLUGIN_NAME = "node9.js";
7674
7934
  }
7675
7935
  });
7676
7936
 
@@ -15972,6 +16232,11 @@ function sanitize2(value) {
15972
16232
  return value.replace(/[\x00-\x1F\x7F]/g, "");
15973
16233
  }
15974
16234
  function detectAiAgent(payload) {
16235
+ const meta = payload.meta;
16236
+ if (meta && typeof meta === "object") {
16237
+ const tagged = meta.agent;
16238
+ if (typeof tagged === "string" && tagged.length > 0) return tagged;
16239
+ }
15975
16240
  if (payload.turn_id !== void 0) {
15976
16241
  return "Codex";
15977
16242
  }
@@ -18610,6 +18875,7 @@ function registerInitCommand(program2) {
18610
18875
  else if (agent === "windsurf") await setupWindsurf();
18611
18876
  else if (agent === "vscode") await setupVSCode();
18612
18877
  else if (agent === "claudeDesktop") await setupClaudeDesktop();
18878
+ else if (agent === "opencode") await setupOpencode();
18613
18879
  console.log("");
18614
18880
  }
18615
18881
  if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
@@ -20369,7 +20635,8 @@ var SETUP_FN = {
20369
20635
  codex: setupCodex,
20370
20636
  windsurf: setupWindsurf,
20371
20637
  vscode: setupVSCode,
20372
- claudeDesktop: setupClaudeDesktop
20638
+ claudeDesktop: setupClaudeDesktop,
20639
+ opencode: setupOpencode
20373
20640
  };
20374
20641
  var TEARDOWN_FN = {
20375
20642
  claude: teardownClaude,
@@ -20378,7 +20645,8 @@ var TEARDOWN_FN = {
20378
20645
  codex: teardownCodex,
20379
20646
  windsurf: teardownWindsurf,
20380
20647
  vscode: teardownVSCode,
20381
- claudeDesktop: teardownClaudeDesktop
20648
+ claudeDesktop: teardownClaudeDesktop,
20649
+ opencode: teardownOpencode
20382
20650
  };
20383
20651
  var AGENT_NAMES = Object.keys(SETUP_FN);
20384
20652
  function registerAgentsCommand(program2) {
package/dist/cli.mjs CHANGED
@@ -6438,6 +6438,122 @@ var init_mcp_pin = __esm({
6438
6438
  }
6439
6439
  });
6440
6440
 
6441
+ // src/setup-opencode-shim.ts
6442
+ function renderOpencodeShim(input) {
6443
+ const { node9Argv, version: version2 } = input;
6444
+ return `// Auto-generated by \`node9 init\`. Do not edit \u2014 re-run init to upgrade.
6445
+ // NODE9_SHIM_VERSION = "${version2}"
6446
+ //
6447
+ // node9 protection shim for Opencode. Wires three hooks against the
6448
+ // agent's plugin API and shells out to the node9 CLI for verdicts.
6449
+ //
6450
+ // Block by throwing from a hook handler; Opencode's plugin trigger
6451
+ // (packages/opencode/src/plugin/index.ts:273) propagates the error and
6452
+ // halts the tool call.
6453
+
6454
+ const { spawnSync } = require("node:child_process");
6455
+
6456
+ // argv prefix for invoking the node9 CLI. argv[0] is the executable
6457
+ // (either the npm-installed wrapper script or the node binary); the
6458
+ // remaining entries (if any) are passed as the leading args before
6459
+ // the subcommand name (e.g. ["/path/to/dist/cli.js"] for dev mode).
6460
+ const NODE9_ARGV = ${JSON.stringify(node9Argv)};
6461
+ const HOOK_TIMEOUT_MS = 30000;
6462
+ const LOG_TIMEOUT_MS = 5000;
6463
+
6464
+ function parseReason(stdout) {
6465
+ // node9 check emits {decision, reason, message} JSON on stdout when
6466
+ // blocking. Fall back to a generic string if anything goes wrong.
6467
+ try {
6468
+ const v = JSON.parse(stdout || "");
6469
+ return v && (v.reason || v.message);
6470
+ } catch (e) {
6471
+ return null;
6472
+ }
6473
+ }
6474
+
6475
+ function extractPromptText(parts) {
6476
+ // chat.message gives us parts: Part[]. We only DLP-scan text parts
6477
+ // (image / tool_use parts can't contain pasted secrets in any form
6478
+ // node9's scanner currently recognizes).
6479
+ if (!Array.isArray(parts)) return "";
6480
+ return parts
6481
+ .filter((p) => p && p.type === "text")
6482
+ .map((p) => (p && p.text) || "")
6483
+ .join("\\n");
6484
+ }
6485
+
6486
+ module.exports = {
6487
+ id: "node9",
6488
+ server: async (input) => ({
6489
+ "tool.execute.before": async (ctx, mutable) => {
6490
+ const payload = {
6491
+ hook_event_name: "PreToolUse",
6492
+ tool_name: ctx.tool,
6493
+ tool_input: mutable.args,
6494
+ session_id: ctx.sessionID,
6495
+ cwd: input.directory,
6496
+ meta: { agent: "Opencode" },
6497
+ };
6498
+ const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
6499
+ input: JSON.stringify(payload),
6500
+ encoding: "utf-8",
6501
+ timeout: HOOK_TIMEOUT_MS,
6502
+ });
6503
+ if (r.status === 0) return;
6504
+ const reason = parseReason(r.stdout) || "blocked by node9";
6505
+ throw new Error("[node9] " + reason);
6506
+ },
6507
+
6508
+ "tool.execute.after": async (ctx) => {
6509
+ // Fire-and-forget audit log \u2014 failures here must NEVER throw,
6510
+ // or we'd retroactively "block" a tool call that already ran.
6511
+ const payload = {
6512
+ hook_event_name: "PostToolUse",
6513
+ tool_name: ctx.tool,
6514
+ session_id: ctx.sessionID,
6515
+ cwd: input.directory,
6516
+ meta: { agent: "Opencode" },
6517
+ };
6518
+ try {
6519
+ spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "log"], {
6520
+ input: JSON.stringify(payload),
6521
+ encoding: "utf-8",
6522
+ timeout: LOG_TIMEOUT_MS,
6523
+ });
6524
+ } catch (e) {
6525
+ // Swallow: audit log gaps are preferable to crashing the agent.
6526
+ }
6527
+ },
6528
+
6529
+ "chat.message": async (ctx, mutable) => {
6530
+ const prompt = extractPromptText(mutable.parts);
6531
+ if (!prompt) return;
6532
+ const payload = {
6533
+ hook_event_name: "UserPromptSubmit",
6534
+ prompt,
6535
+ session_id: ctx.sessionID,
6536
+ meta: { agent: "Opencode" },
6537
+ };
6538
+ const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
6539
+ input: JSON.stringify(payload),
6540
+ encoding: "utf-8",
6541
+ timeout: HOOK_TIMEOUT_MS,
6542
+ });
6543
+ if (r.status === 0) return;
6544
+ const reason = parseReason(r.stdout) || "prompt blocked";
6545
+ throw new Error("[node9] " + reason);
6546
+ },
6547
+ }),
6548
+ };
6549
+ `;
6550
+ }
6551
+ var init_setup_opencode_shim = __esm({
6552
+ "src/setup-opencode-shim.ts"() {
6553
+ "use strict";
6554
+ }
6555
+ });
6556
+
6441
6557
  // src/setup.ts
6442
6558
  import fs13 from "fs";
6443
6559
  import path15 from "path";
@@ -6921,7 +7037,8 @@ function detectAgents(homeDir2 = os12.homedir()) {
6921
7037
  codex: exists(path15.join(homeDir2, ".codex")),
6922
7038
  windsurf: exists(path15.join(homeDir2, ".codeium", "windsurf")),
6923
7039
  vscode: exists(path15.join(homeDir2, ".vscode")),
6924
- claudeDesktop: desktopPath !== null && exists(path15.dirname(desktopPath))
7040
+ claudeDesktop: desktopPath !== null && exists(path15.dirname(desktopPath)),
7041
+ opencode: exists(path15.join(homeDir2, ".config", "opencode"))
6925
7042
  };
6926
7043
  }
6927
7044
  async function setupCursor() {
@@ -7554,6 +7671,127 @@ function teardownClaudeDesktop() {
7554
7671
  console.log(chalk.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
7555
7672
  }
7556
7673
  }
7674
+ function node9ArgvForShim() {
7675
+ if (process.env.NODE9_TESTING === "1") return ["node9"];
7676
+ const nodeExec = process.execPath;
7677
+ const cliScript = process.argv[1];
7678
+ if (cliScript && cliScript.endsWith(".js")) return [nodeExec, cliScript];
7679
+ return [cliScript];
7680
+ }
7681
+ function node9Version() {
7682
+ try {
7683
+ const pkg = JSON.parse(
7684
+ fs13.readFileSync(path15.join(__dirname, "..", "package.json"), "utf-8")
7685
+ );
7686
+ return pkg.version ?? "0.0.0";
7687
+ } catch {
7688
+ return "0.0.0";
7689
+ }
7690
+ }
7691
+ async function setupOpencode() {
7692
+ seedMcpPinsIfMissing();
7693
+ const homeDir2 = os12.homedir();
7694
+ const configDir = path15.join(homeDir2, ".config", "opencode");
7695
+ const pluginsDir = path15.join(configDir, "plugins");
7696
+ const configPath = path15.join(configDir, "opencode.json");
7697
+ const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
7698
+ try {
7699
+ fs13.mkdirSync(pluginsDir, { recursive: true });
7700
+ } catch (err2) {
7701
+ const code = err2.code;
7702
+ if (code !== "EEXIST") {
7703
+ console.log(chalk.yellow(` \u26A0\uFE0F Could not create ${pluginsDir}: ${code ?? String(err2)}`));
7704
+ return;
7705
+ }
7706
+ }
7707
+ const shimContent = renderOpencodeShim({
7708
+ node9Argv: node9ArgvForShim(),
7709
+ version: node9Version()
7710
+ });
7711
+ let pluginChanged = false;
7712
+ const existingShim = (() => {
7713
+ try {
7714
+ return fs13.readFileSync(pluginPath, "utf-8");
7715
+ } catch {
7716
+ return null;
7717
+ }
7718
+ })();
7719
+ if (existingShim !== shimContent) {
7720
+ fs13.writeFileSync(pluginPath, shimContent);
7721
+ pluginChanged = true;
7722
+ if (existingShim) {
7723
+ console.log(chalk.yellow(" \u{1F527} Opencode plugin shim updated to current version"));
7724
+ } else {
7725
+ console.log(
7726
+ chalk.green(" \u2705 Opencode plugin installed \u2192 tool.execute.before / after, chat.message")
7727
+ );
7728
+ }
7729
+ }
7730
+ const config = readJson(configPath) ?? {};
7731
+ const mcp = config.mcp ?? {};
7732
+ let configChanged = false;
7733
+ const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
7734
+ const desiredEntry = {
7735
+ type: "local",
7736
+ command: desiredCommand,
7737
+ enabled: true
7738
+ };
7739
+ const existing = mcp["node9"];
7740
+ const entryMatches = existing && existing.type === "local" && Array.isArray(existing.command) && existing.command.length === desiredCommand.length && existing.command.every((c, i) => c === desiredCommand[i]) && existing.enabled !== false;
7741
+ if (!entryMatches) {
7742
+ mcp["node9"] = desiredEntry;
7743
+ config.mcp = mcp;
7744
+ configChanged = true;
7745
+ if (existing) {
7746
+ console.log(chalk.yellow(" \u{1F527} Opencode MCP entry updated (node9)"));
7747
+ } else {
7748
+ console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
7749
+ }
7750
+ }
7751
+ if (configChanged) writeJson(configPath, config);
7752
+ if (pluginChanged || configChanged) {
7753
+ console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
7754
+ console.log(chalk.gray(" Restart Opencode for changes to take effect."));
7755
+ printDaemonTip();
7756
+ } else {
7757
+ console.log(chalk.blue(" \u2139\uFE0F Node9 is already fully configured for Opencode."));
7758
+ }
7759
+ }
7760
+ function teardownOpencode() {
7761
+ const homeDir2 = os12.homedir();
7762
+ const configDir = path15.join(homeDir2, ".config", "opencode");
7763
+ const pluginsDir = path15.join(configDir, "plugins");
7764
+ const configPath = path15.join(configDir, "opencode.json");
7765
+ const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
7766
+ try {
7767
+ if (fs13.existsSync(pluginPath)) {
7768
+ fs13.unlinkSync(pluginPath);
7769
+ console.log(chalk.green(" \u2705 Removed node9 plugin from ~/.config/opencode/plugins/"));
7770
+ }
7771
+ } catch (err2) {
7772
+ console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
7773
+ }
7774
+ const config = readJson(configPath);
7775
+ if (!config) {
7776
+ console.log(chalk.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
7777
+ return;
7778
+ }
7779
+ const mcp = config.mcp ?? {};
7780
+ let changed = false;
7781
+ if (mcp["node9"]) {
7782
+ delete mcp["node9"];
7783
+ changed = true;
7784
+ console.log(
7785
+ chalk.green(" \u2705 Removed node9 MCP server entry from ~/.config/opencode/opencode.json")
7786
+ );
7787
+ }
7788
+ if (changed) {
7789
+ config.mcp = mcp;
7790
+ writeJson(configPath, config);
7791
+ } else {
7792
+ console.log(chalk.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
7793
+ }
7794
+ }
7557
7795
  function getAgentsStatus(homeDir2 = os12.homedir()) {
7558
7796
  const detected = detectAgents(homeDir2);
7559
7797
  const claudeWired = (() => {
@@ -7636,16 +7874,38 @@ function getAgentsStatus(homeDir2 = os12.homedir()) {
7636
7874
  return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
7637
7875
  })(),
7638
7876
  mode: detected.claudeDesktop ? "mcp" : null
7877
+ },
7878
+ {
7879
+ name: "opencode",
7880
+ label: "Opencode",
7881
+ installed: detected.opencode,
7882
+ wired: (() => {
7883
+ const pluginPath = path15.join(
7884
+ homeDir2,
7885
+ ".config",
7886
+ "opencode",
7887
+ "plugins",
7888
+ OPENCODE_PLUGIN_NAME
7889
+ );
7890
+ if (fs13.existsSync(pluginPath)) return true;
7891
+ const cfg = readJson(
7892
+ path15.join(homeDir2, ".config", "opencode", "opencode.json")
7893
+ );
7894
+ return !!cfg?.mcp?.["node9"];
7895
+ })(),
7896
+ mode: detected.opencode ? "hooks" : null
7639
7897
  }
7640
7898
  ];
7641
7899
  }
7642
- var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS;
7900
+ var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME;
7643
7901
  var init_setup = __esm({
7644
7902
  "src/setup.ts"() {
7645
7903
  "use strict";
7646
7904
  init_mcp_pin();
7905
+ init_setup_opencode_shim();
7647
7906
  NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
7648
7907
  CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
7908
+ OPENCODE_PLUGIN_NAME = "node9.js";
7649
7909
  }
7650
7910
  });
7651
7911
 
@@ -15944,6 +16204,11 @@ function sanitize2(value) {
15944
16204
  return value.replace(/[\x00-\x1F\x7F]/g, "");
15945
16205
  }
15946
16206
  function detectAiAgent(payload) {
16207
+ const meta = payload.meta;
16208
+ if (meta && typeof meta === "object") {
16209
+ const tagged = meta.agent;
16210
+ if (typeof tagged === "string" && tagged.length > 0) return tagged;
16211
+ }
15947
16212
  if (payload.turn_id !== void 0) {
15948
16213
  return "Codex";
15949
16214
  }
@@ -18582,6 +18847,7 @@ function registerInitCommand(program2) {
18582
18847
  else if (agent === "windsurf") await setupWindsurf();
18583
18848
  else if (agent === "vscode") await setupVSCode();
18584
18849
  else if (agent === "claudeDesktop") await setupClaudeDesktop();
18850
+ else if (agent === "opencode") await setupOpencode();
18585
18851
  console.log("");
18586
18852
  }
18587
18853
  if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
@@ -20341,7 +20607,8 @@ var SETUP_FN = {
20341
20607
  codex: setupCodex,
20342
20608
  windsurf: setupWindsurf,
20343
20609
  vscode: setupVSCode,
20344
- claudeDesktop: setupClaudeDesktop
20610
+ claudeDesktop: setupClaudeDesktop,
20611
+ opencode: setupOpencode
20345
20612
  };
20346
20613
  var TEARDOWN_FN = {
20347
20614
  claude: teardownClaude,
@@ -20350,7 +20617,8 @@ var TEARDOWN_FN = {
20350
20617
  codex: teardownCodex,
20351
20618
  windsurf: teardownWindsurf,
20352
20619
  vscode: teardownVSCode,
20353
- claudeDesktop: teardownClaudeDesktop
20620
+ claudeDesktop: teardownClaudeDesktop,
20621
+ opencode: teardownOpencode
20354
20622
  };
20355
20623
  var AGENT_NAMES = Object.keys(SETUP_FN);
20356
20624
  function registerAgentsCommand(program2) {
@@ -3340,6 +3340,13 @@ var init_mcp_pin = __esm({
3340
3340
  }
3341
3341
  });
3342
3342
 
3343
+ // src/setup-opencode-shim.ts
3344
+ var init_setup_opencode_shim = __esm({
3345
+ "src/setup-opencode-shim.ts"() {
3346
+ "use strict";
3347
+ }
3348
+ });
3349
+
3343
3350
  // src/setup.ts
3344
3351
  import chalk2 from "chalk";
3345
3352
  import { confirm } from "@inquirer/prompts";
@@ -3348,6 +3355,7 @@ var init_setup = __esm({
3348
3355
  "src/setup.ts"() {
3349
3356
  "use strict";
3350
3357
  init_mcp_pin();
3358
+ init_setup_opencode_shim();
3351
3359
  }
3352
3360
  });
3353
3361
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.24.3",
3
+ "version": "1.25.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",