@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 +272 -4
- package/dist/cli.mjs +272 -4
- package/dist/dashboard.mjs +8 -0
- package/package.json +1 -1
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) {
|
package/dist/dashboard.mjs
CHANGED
|
@@ -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
|
|