@node9/proxy 1.24.3 → 1.26.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 +382 -21
- package/dist/cli.mjs +382 -21
- package/dist/dashboard.mjs +52 -8
- package/dist/index.js +44 -8
- package/dist/index.mjs +44 -8
- package/dist/scan-ink.mjs +43 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2273,6 +2273,42 @@ var init_dist = __esm({
|
|
|
2273
2273
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
2274
2274
|
severity: "block",
|
|
2275
2275
|
keywords: ["age-secret-key-"]
|
|
2276
|
+
},
|
|
2277
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
2278
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
2279
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
2280
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
2281
|
+
// the password value never appears in the redacted sample.
|
|
2282
|
+
//
|
|
2283
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
2284
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
2285
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
2286
|
+
// excluded — they're not database URLs and adding them would
|
|
2287
|
+
// create false positives on every basic-auth URL in the wild.
|
|
2288
|
+
//
|
|
2289
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
2290
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
2291
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
2292
|
+
{
|
|
2293
|
+
name: "Database Connection String",
|
|
2294
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
2295
|
+
severity: "block",
|
|
2296
|
+
keywords: [
|
|
2297
|
+
"redis://",
|
|
2298
|
+
"rediss://",
|
|
2299
|
+
"postgres://",
|
|
2300
|
+
"postgresql://",
|
|
2301
|
+
"mongodb://",
|
|
2302
|
+
"mongodb+srv://",
|
|
2303
|
+
"mysql://",
|
|
2304
|
+
"mariadb://",
|
|
2305
|
+
"amqp://",
|
|
2306
|
+
"amqps://",
|
|
2307
|
+
"kafka://",
|
|
2308
|
+
"clickhouse://",
|
|
2309
|
+
"cassandra://"
|
|
2310
|
+
],
|
|
2311
|
+
minEntropy: 3
|
|
2276
2312
|
}
|
|
2277
2313
|
];
|
|
2278
2314
|
DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -2375,7 +2411,7 @@ var init_dist = __esm({
|
|
|
2375
2411
|
},
|
|
2376
2412
|
{
|
|
2377
2413
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2378
|
-
//
|
|
2414
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
2379
2415
|
// same set the regex shield does — including Next.js / Vite's
|
|
2380
2416
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2381
2417
|
// gitignored AND commonly contain real secrets.
|
|
@@ -3121,7 +3157,7 @@ var init_dist = __esm({
|
|
|
3121
3157
|
{
|
|
3122
3158
|
field: "command",
|
|
3123
3159
|
op: "matches",
|
|
3124
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3160
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3125
3161
|
flags: "i"
|
|
3126
3162
|
}
|
|
3127
3163
|
],
|
|
@@ -3135,7 +3171,7 @@ var init_dist = __esm({
|
|
|
3135
3171
|
{
|
|
3136
3172
|
field: "command",
|
|
3137
3173
|
op: "matches",
|
|
3138
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3174
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
3139
3175
|
flags: "i"
|
|
3140
3176
|
}
|
|
3141
3177
|
],
|
|
@@ -3149,7 +3185,7 @@ var init_dist = __esm({
|
|
|
3149
3185
|
{
|
|
3150
3186
|
field: "command",
|
|
3151
3187
|
op: "matches",
|
|
3152
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3188
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
3153
3189
|
flags: "i"
|
|
3154
3190
|
}
|
|
3155
3191
|
],
|
|
@@ -3163,7 +3199,7 @@ var init_dist = __esm({
|
|
|
3163
3199
|
{
|
|
3164
3200
|
field: "command",
|
|
3165
3201
|
op: "matches",
|
|
3166
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3202
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3167
3203
|
flags: "i"
|
|
3168
3204
|
}
|
|
3169
3205
|
],
|
|
@@ -3199,7 +3235,7 @@ var init_dist = __esm({
|
|
|
3199
3235
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3200
3236
|
},
|
|
3201
3237
|
{
|
|
3202
|
-
name: "shield:project-jail:
|
|
3238
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
3203
3239
|
tool: "*",
|
|
3204
3240
|
conditions: [
|
|
3205
3241
|
{
|
|
@@ -3209,8 +3245,8 @@ var init_dist = __esm({
|
|
|
3209
3245
|
flags: "i"
|
|
3210
3246
|
}
|
|
3211
3247
|
],
|
|
3212
|
-
verdict: "
|
|
3213
|
-
reason: "Reading .env files
|
|
3248
|
+
verdict: "block",
|
|
3249
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
3214
3250
|
},
|
|
3215
3251
|
{
|
|
3216
3252
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
@@ -3471,6 +3507,30 @@ function writeShieldOverride(shieldName, ruleName, verdict) {
|
|
|
3471
3507
|
overrides[shieldName] = { ...overrides[shieldName] ?? {}, [ruleName]: verdict };
|
|
3472
3508
|
writeShieldsFile({ ...current, overrides });
|
|
3473
3509
|
}
|
|
3510
|
+
function migrateRenamedRuleKeys() {
|
|
3511
|
+
const current = readShieldsFile();
|
|
3512
|
+
if (!current.overrides || Object.keys(current.overrides).length === 0) return [];
|
|
3513
|
+
const renameMap = new Map(RULE_KEY_MIGRATIONS);
|
|
3514
|
+
const migrated = [];
|
|
3515
|
+
const nextOverrides = {};
|
|
3516
|
+
let anyChange = false;
|
|
3517
|
+
for (const [shield, rules] of Object.entries(current.overrides)) {
|
|
3518
|
+
const nextRules = {};
|
|
3519
|
+
for (const [key, verdict] of Object.entries(rules)) {
|
|
3520
|
+
const newKey = renameMap.get(key);
|
|
3521
|
+
if (newKey) {
|
|
3522
|
+
if (!(newKey in nextRules)) nextRules[newKey] = verdict;
|
|
3523
|
+
migrated.push({ shield, oldKey: key, newKey });
|
|
3524
|
+
anyChange = true;
|
|
3525
|
+
} else {
|
|
3526
|
+
nextRules[key] = verdict;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
if (Object.keys(nextRules).length > 0) nextOverrides[shield] = nextRules;
|
|
3530
|
+
}
|
|
3531
|
+
if (anyChange) writeShieldsFile({ ...current, overrides: nextOverrides });
|
|
3532
|
+
return migrated;
|
|
3533
|
+
}
|
|
3474
3534
|
function clearShieldOverride(shieldName, ruleName) {
|
|
3475
3535
|
const current = readShieldsFile();
|
|
3476
3536
|
if (!current.overrides?.[shieldName]?.[ruleName]) return;
|
|
@@ -3519,7 +3579,7 @@ function installShield(name, shieldJson) {
|
|
|
3519
3579
|
import_fs2.default.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
3520
3580
|
import_fs2.default.renameSync(tmp, filePath);
|
|
3521
3581
|
}
|
|
3522
|
-
var import_fs2, import_path2, import_os2, import_crypto3, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE;
|
|
3582
|
+
var import_fs2, import_path2, import_os2, import_crypto3, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS;
|
|
3523
3583
|
var init_shields = __esm({
|
|
3524
3584
|
"src/shields.ts"() {
|
|
3525
3585
|
"use strict";
|
|
@@ -3532,6 +3592,14 @@ var init_shields = __esm({
|
|
|
3532
3592
|
USER_SHIELDS_DIR = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields");
|
|
3533
3593
|
SHIELDS = buildSHIELDS();
|
|
3534
3594
|
SHIELDS_STATE_FILE = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields.json");
|
|
3595
|
+
RULE_KEY_MIGRATIONS = [
|
|
3596
|
+
// 2026-05-21 — project-jail .env reads promoted review → block. Renamed
|
|
3597
|
+
// so the key honestly describes the verdict. Credential-file rules
|
|
3598
|
+
// (.netrc, .npmrc, .docker, .kube, gcloud) deliberately stay at `review`
|
|
3599
|
+
// — see the rationale in packages/policy-engine/src/shell/index.ts
|
|
3600
|
+
// around the review-read-credentials rule.
|
|
3601
|
+
["shield:project-jail:review-read-env-any-tool", "shield:project-jail:block-read-env-any-tool"]
|
|
3602
|
+
];
|
|
3535
3603
|
}
|
|
3536
3604
|
});
|
|
3537
3605
|
|
|
@@ -6463,6 +6531,122 @@ var init_mcp_pin = __esm({
|
|
|
6463
6531
|
}
|
|
6464
6532
|
});
|
|
6465
6533
|
|
|
6534
|
+
// src/setup-opencode-shim.ts
|
|
6535
|
+
function renderOpencodeShim(input) {
|
|
6536
|
+
const { node9Argv, version: version2 } = input;
|
|
6537
|
+
return `// Auto-generated by \`node9 init\`. Do not edit \u2014 re-run init to upgrade.
|
|
6538
|
+
// NODE9_SHIM_VERSION = "${version2}"
|
|
6539
|
+
//
|
|
6540
|
+
// node9 protection shim for Opencode. Wires three hooks against the
|
|
6541
|
+
// agent's plugin API and shells out to the node9 CLI for verdicts.
|
|
6542
|
+
//
|
|
6543
|
+
// Block by throwing from a hook handler; Opencode's plugin trigger
|
|
6544
|
+
// (packages/opencode/src/plugin/index.ts:273) propagates the error and
|
|
6545
|
+
// halts the tool call.
|
|
6546
|
+
|
|
6547
|
+
const { spawnSync } = require("node:child_process");
|
|
6548
|
+
|
|
6549
|
+
// argv prefix for invoking the node9 CLI. argv[0] is the executable
|
|
6550
|
+
// (either the npm-installed wrapper script or the node binary); the
|
|
6551
|
+
// remaining entries (if any) are passed as the leading args before
|
|
6552
|
+
// the subcommand name (e.g. ["/path/to/dist/cli.js"] for dev mode).
|
|
6553
|
+
const NODE9_ARGV = ${JSON.stringify(node9Argv)};
|
|
6554
|
+
const HOOK_TIMEOUT_MS = 30000;
|
|
6555
|
+
const LOG_TIMEOUT_MS = 5000;
|
|
6556
|
+
|
|
6557
|
+
function parseReason(stdout) {
|
|
6558
|
+
// node9 check emits {decision, reason, message} JSON on stdout when
|
|
6559
|
+
// blocking. Fall back to a generic string if anything goes wrong.
|
|
6560
|
+
try {
|
|
6561
|
+
const v = JSON.parse(stdout || "");
|
|
6562
|
+
return v && (v.reason || v.message);
|
|
6563
|
+
} catch (e) {
|
|
6564
|
+
return null;
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
function extractPromptText(parts) {
|
|
6569
|
+
// chat.message gives us parts: Part[]. We only DLP-scan text parts
|
|
6570
|
+
// (image / tool_use parts can't contain pasted secrets in any form
|
|
6571
|
+
// node9's scanner currently recognizes).
|
|
6572
|
+
if (!Array.isArray(parts)) return "";
|
|
6573
|
+
return parts
|
|
6574
|
+
.filter((p) => p && p.type === "text")
|
|
6575
|
+
.map((p) => (p && p.text) || "")
|
|
6576
|
+
.join("\\n");
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6579
|
+
module.exports = {
|
|
6580
|
+
id: "node9",
|
|
6581
|
+
server: async (input) => ({
|
|
6582
|
+
"tool.execute.before": async (ctx, mutable) => {
|
|
6583
|
+
const payload = {
|
|
6584
|
+
hook_event_name: "PreToolUse",
|
|
6585
|
+
tool_name: ctx.tool,
|
|
6586
|
+
tool_input: mutable.args,
|
|
6587
|
+
session_id: ctx.sessionID,
|
|
6588
|
+
cwd: input.directory,
|
|
6589
|
+
meta: { agent: "Opencode" },
|
|
6590
|
+
};
|
|
6591
|
+
const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
|
|
6592
|
+
input: JSON.stringify(payload),
|
|
6593
|
+
encoding: "utf-8",
|
|
6594
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
6595
|
+
});
|
|
6596
|
+
if (r.status === 0) return;
|
|
6597
|
+
const reason = parseReason(r.stdout) || "blocked by node9";
|
|
6598
|
+
throw new Error("[node9] " + reason);
|
|
6599
|
+
},
|
|
6600
|
+
|
|
6601
|
+
"tool.execute.after": async (ctx) => {
|
|
6602
|
+
// Fire-and-forget audit log \u2014 failures here must NEVER throw,
|
|
6603
|
+
// or we'd retroactively "block" a tool call that already ran.
|
|
6604
|
+
const payload = {
|
|
6605
|
+
hook_event_name: "PostToolUse",
|
|
6606
|
+
tool_name: ctx.tool,
|
|
6607
|
+
session_id: ctx.sessionID,
|
|
6608
|
+
cwd: input.directory,
|
|
6609
|
+
meta: { agent: "Opencode" },
|
|
6610
|
+
};
|
|
6611
|
+
try {
|
|
6612
|
+
spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "log"], {
|
|
6613
|
+
input: JSON.stringify(payload),
|
|
6614
|
+
encoding: "utf-8",
|
|
6615
|
+
timeout: LOG_TIMEOUT_MS,
|
|
6616
|
+
});
|
|
6617
|
+
} catch (e) {
|
|
6618
|
+
// Swallow: audit log gaps are preferable to crashing the agent.
|
|
6619
|
+
}
|
|
6620
|
+
},
|
|
6621
|
+
|
|
6622
|
+
"chat.message": async (ctx, mutable) => {
|
|
6623
|
+
const prompt = extractPromptText(mutable.parts);
|
|
6624
|
+
if (!prompt) return;
|
|
6625
|
+
const payload = {
|
|
6626
|
+
hook_event_name: "UserPromptSubmit",
|
|
6627
|
+
prompt,
|
|
6628
|
+
session_id: ctx.sessionID,
|
|
6629
|
+
meta: { agent: "Opencode" },
|
|
6630
|
+
};
|
|
6631
|
+
const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
|
|
6632
|
+
input: JSON.stringify(payload),
|
|
6633
|
+
encoding: "utf-8",
|
|
6634
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
6635
|
+
});
|
|
6636
|
+
if (r.status === 0) return;
|
|
6637
|
+
const reason = parseReason(r.stdout) || "prompt blocked";
|
|
6638
|
+
throw new Error("[node9] " + reason);
|
|
6639
|
+
},
|
|
6640
|
+
}),
|
|
6641
|
+
};
|
|
6642
|
+
`;
|
|
6643
|
+
}
|
|
6644
|
+
var init_setup_opencode_shim = __esm({
|
|
6645
|
+
"src/setup-opencode-shim.ts"() {
|
|
6646
|
+
"use strict";
|
|
6647
|
+
}
|
|
6648
|
+
});
|
|
6649
|
+
|
|
6466
6650
|
// src/setup.ts
|
|
6467
6651
|
function hasNode9McpServer(servers) {
|
|
6468
6652
|
const entry = servers["node9"];
|
|
@@ -6919,6 +7103,18 @@ function claudeDesktopConfigPath(homeDir2 = import_os12.default.homedir()) {
|
|
|
6919
7103
|
}
|
|
6920
7104
|
return null;
|
|
6921
7105
|
}
|
|
7106
|
+
function binaryInPath(binary) {
|
|
7107
|
+
const pathEnv = process.env.PATH ?? "";
|
|
7108
|
+
for (const dir of pathEnv.split(import_path15.default.delimiter)) {
|
|
7109
|
+
if (!dir) continue;
|
|
7110
|
+
try {
|
|
7111
|
+
import_fs13.default.accessSync(import_path15.default.join(dir, binary), import_fs13.default.constants.X_OK);
|
|
7112
|
+
return true;
|
|
7113
|
+
} catch {
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
return false;
|
|
7117
|
+
}
|
|
6922
7118
|
function detectAgents(homeDir2 = import_os12.default.homedir()) {
|
|
6923
7119
|
const exists = (p) => {
|
|
6924
7120
|
try {
|
|
@@ -6940,7 +7136,10 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
|
|
|
6940
7136
|
codex: exists(import_path15.default.join(homeDir2, ".codex")),
|
|
6941
7137
|
windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
|
|
6942
7138
|
vscode: exists(import_path15.default.join(homeDir2, ".vscode")),
|
|
6943
|
-
claudeDesktop: desktopPath !== null && exists(import_path15.default.dirname(desktopPath))
|
|
7139
|
+
claudeDesktop: desktopPath !== null && exists(import_path15.default.dirname(desktopPath)),
|
|
7140
|
+
// Opencode creates ~/.config/opencode lazily on first launch — fall back
|
|
7141
|
+
// to a PATH lookup so installed-but-never-launched CLIs are still wired.
|
|
7142
|
+
opencode: exists(import_path15.default.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode")
|
|
6944
7143
|
};
|
|
6945
7144
|
}
|
|
6946
7145
|
async function setupCursor() {
|
|
@@ -7573,6 +7772,127 @@ function teardownClaudeDesktop() {
|
|
|
7573
7772
|
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
|
|
7574
7773
|
}
|
|
7575
7774
|
}
|
|
7775
|
+
function node9ArgvForShim() {
|
|
7776
|
+
if (process.env.NODE9_TESTING === "1") return ["node9"];
|
|
7777
|
+
const nodeExec = process.execPath;
|
|
7778
|
+
const cliScript = process.argv[1];
|
|
7779
|
+
if (cliScript && cliScript.endsWith(".js")) return [nodeExec, cliScript];
|
|
7780
|
+
return [cliScript];
|
|
7781
|
+
}
|
|
7782
|
+
function node9Version() {
|
|
7783
|
+
try {
|
|
7784
|
+
const pkg = JSON.parse(
|
|
7785
|
+
import_fs13.default.readFileSync(import_path15.default.join(__dirname, "..", "package.json"), "utf-8")
|
|
7786
|
+
);
|
|
7787
|
+
return pkg.version ?? "0.0.0";
|
|
7788
|
+
} catch {
|
|
7789
|
+
return "0.0.0";
|
|
7790
|
+
}
|
|
7791
|
+
}
|
|
7792
|
+
async function setupOpencode() {
|
|
7793
|
+
seedMcpPinsIfMissing();
|
|
7794
|
+
const homeDir2 = import_os12.default.homedir();
|
|
7795
|
+
const configDir = import_path15.default.join(homeDir2, ".config", "opencode");
|
|
7796
|
+
const pluginsDir = import_path15.default.join(configDir, "plugins");
|
|
7797
|
+
const configPath = import_path15.default.join(configDir, "opencode.json");
|
|
7798
|
+
const pluginPath = import_path15.default.join(pluginsDir, OPENCODE_PLUGIN_NAME);
|
|
7799
|
+
try {
|
|
7800
|
+
import_fs13.default.mkdirSync(pluginsDir, { recursive: true });
|
|
7801
|
+
} catch (err2) {
|
|
7802
|
+
const code = err2.code;
|
|
7803
|
+
if (code !== "EEXIST") {
|
|
7804
|
+
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not create ${pluginsDir}: ${code ?? String(err2)}`));
|
|
7805
|
+
return;
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7808
|
+
const shimContent = renderOpencodeShim({
|
|
7809
|
+
node9Argv: node9ArgvForShim(),
|
|
7810
|
+
version: node9Version()
|
|
7811
|
+
});
|
|
7812
|
+
let pluginChanged = false;
|
|
7813
|
+
const existingShim = (() => {
|
|
7814
|
+
try {
|
|
7815
|
+
return import_fs13.default.readFileSync(pluginPath, "utf-8");
|
|
7816
|
+
} catch {
|
|
7817
|
+
return null;
|
|
7818
|
+
}
|
|
7819
|
+
})();
|
|
7820
|
+
if (existingShim !== shimContent) {
|
|
7821
|
+
import_fs13.default.writeFileSync(pluginPath, shimContent);
|
|
7822
|
+
pluginChanged = true;
|
|
7823
|
+
if (existingShim) {
|
|
7824
|
+
console.log(import_chalk.default.yellow(" \u{1F527} Opencode plugin shim updated to current version"));
|
|
7825
|
+
} else {
|
|
7826
|
+
console.log(
|
|
7827
|
+
import_chalk.default.green(" \u2705 Opencode plugin installed \u2192 tool.execute.before / after, chat.message")
|
|
7828
|
+
);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
const config = readJson(configPath) ?? {};
|
|
7832
|
+
const mcp = config.mcp ?? {};
|
|
7833
|
+
let configChanged = false;
|
|
7834
|
+
const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
|
|
7835
|
+
const desiredEntry = {
|
|
7836
|
+
type: "local",
|
|
7837
|
+
command: desiredCommand,
|
|
7838
|
+
enabled: true
|
|
7839
|
+
};
|
|
7840
|
+
const existing = mcp["node9"];
|
|
7841
|
+
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;
|
|
7842
|
+
if (!entryMatches) {
|
|
7843
|
+
mcp["node9"] = desiredEntry;
|
|
7844
|
+
config.mcp = mcp;
|
|
7845
|
+
configChanged = true;
|
|
7846
|
+
if (existing) {
|
|
7847
|
+
console.log(import_chalk.default.yellow(" \u{1F527} Opencode MCP entry updated (node9)"));
|
|
7848
|
+
} else {
|
|
7849
|
+
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
7850
|
+
}
|
|
7851
|
+
}
|
|
7852
|
+
if (configChanged) writeJson(configPath, config);
|
|
7853
|
+
if (pluginChanged || configChanged) {
|
|
7854
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
|
|
7855
|
+
console.log(import_chalk.default.gray(" Restart Opencode for changes to take effect."));
|
|
7856
|
+
printDaemonTip();
|
|
7857
|
+
} else {
|
|
7858
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F Node9 is already fully configured for Opencode."));
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
function teardownOpencode() {
|
|
7862
|
+
const homeDir2 = import_os12.default.homedir();
|
|
7863
|
+
const configDir = import_path15.default.join(homeDir2, ".config", "opencode");
|
|
7864
|
+
const pluginsDir = import_path15.default.join(configDir, "plugins");
|
|
7865
|
+
const configPath = import_path15.default.join(configDir, "opencode.json");
|
|
7866
|
+
const pluginPath = import_path15.default.join(pluginsDir, OPENCODE_PLUGIN_NAME);
|
|
7867
|
+
try {
|
|
7868
|
+
if (import_fs13.default.existsSync(pluginPath)) {
|
|
7869
|
+
import_fs13.default.unlinkSync(pluginPath);
|
|
7870
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 plugin from ~/.config/opencode/plugins/"));
|
|
7871
|
+
}
|
|
7872
|
+
} catch (err2) {
|
|
7873
|
+
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
|
|
7874
|
+
}
|
|
7875
|
+
const config = readJson(configPath);
|
|
7876
|
+
if (!config) {
|
|
7877
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
|
|
7878
|
+
return;
|
|
7879
|
+
}
|
|
7880
|
+
const mcp = config.mcp ?? {};
|
|
7881
|
+
let changed = false;
|
|
7882
|
+
if (mcp["node9"]) {
|
|
7883
|
+
delete mcp["node9"];
|
|
7884
|
+
changed = true;
|
|
7885
|
+
console.log(
|
|
7886
|
+
import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.config/opencode/opencode.json")
|
|
7887
|
+
);
|
|
7888
|
+
}
|
|
7889
|
+
if (changed) {
|
|
7890
|
+
config.mcp = mcp;
|
|
7891
|
+
writeJson(configPath, config);
|
|
7892
|
+
} else {
|
|
7893
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
|
|
7894
|
+
}
|
|
7895
|
+
}
|
|
7576
7896
|
function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
|
|
7577
7897
|
const detected = detectAgents(homeDir2);
|
|
7578
7898
|
const claudeWired = (() => {
|
|
@@ -7655,10 +7975,30 @@ function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
|
|
|
7655
7975
|
return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
|
|
7656
7976
|
})(),
|
|
7657
7977
|
mode: detected.claudeDesktop ? "mcp" : null
|
|
7978
|
+
},
|
|
7979
|
+
{
|
|
7980
|
+
name: "opencode",
|
|
7981
|
+
label: "Opencode",
|
|
7982
|
+
installed: detected.opencode,
|
|
7983
|
+
wired: (() => {
|
|
7984
|
+
const pluginPath = import_path15.default.join(
|
|
7985
|
+
homeDir2,
|
|
7986
|
+
".config",
|
|
7987
|
+
"opencode",
|
|
7988
|
+
"plugins",
|
|
7989
|
+
OPENCODE_PLUGIN_NAME
|
|
7990
|
+
);
|
|
7991
|
+
if (import_fs13.default.existsSync(pluginPath)) return true;
|
|
7992
|
+
const cfg = readJson(
|
|
7993
|
+
import_path15.default.join(homeDir2, ".config", "opencode", "opencode.json")
|
|
7994
|
+
);
|
|
7995
|
+
return !!cfg?.mcp?.["node9"];
|
|
7996
|
+
})(),
|
|
7997
|
+
mode: detected.opencode ? "hooks" : null
|
|
7658
7998
|
}
|
|
7659
7999
|
];
|
|
7660
8000
|
}
|
|
7661
|
-
var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS;
|
|
8001
|
+
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
8002
|
var init_setup = __esm({
|
|
7663
8003
|
"src/setup.ts"() {
|
|
7664
8004
|
"use strict";
|
|
@@ -7669,8 +8009,10 @@ var init_setup = __esm({
|
|
|
7669
8009
|
import_prompts = require("@inquirer/prompts");
|
|
7670
8010
|
import_smol_toml = require("smol-toml");
|
|
7671
8011
|
init_mcp_pin();
|
|
8012
|
+
init_setup_opencode_shim();
|
|
7672
8013
|
NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
|
|
7673
8014
|
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
8015
|
+
OPENCODE_PLUGIN_NAME = "node9.js";
|
|
7674
8016
|
}
|
|
7675
8017
|
});
|
|
7676
8018
|
|
|
@@ -15972,6 +16314,11 @@ function sanitize2(value) {
|
|
|
15972
16314
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
15973
16315
|
}
|
|
15974
16316
|
function detectAiAgent(payload) {
|
|
16317
|
+
const meta = payload.meta;
|
|
16318
|
+
if (meta && typeof meta === "object") {
|
|
16319
|
+
const tagged = meta.agent;
|
|
16320
|
+
if (typeof tagged === "string" && tagged.length > 0) return tagged;
|
|
16321
|
+
}
|
|
15975
16322
|
if (payload.turn_id !== void 0) {
|
|
15976
16323
|
return "Codex";
|
|
15977
16324
|
}
|
|
@@ -18479,14 +18826,18 @@ init_setup();
|
|
|
18479
18826
|
init_shields();
|
|
18480
18827
|
init_service();
|
|
18481
18828
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
|
|
18482
|
-
function
|
|
18829
|
+
function buildTelemetryPayload(agents, firstInstall) {
|
|
18830
|
+
return {
|
|
18831
|
+
event: "init_completed",
|
|
18832
|
+
agents_detected: agents,
|
|
18833
|
+
os: process.platform,
|
|
18834
|
+
node9_version: node9Version(),
|
|
18835
|
+
first_install: firstInstall
|
|
18836
|
+
};
|
|
18837
|
+
}
|
|
18838
|
+
function fireTelemetryPing(agents, firstInstall) {
|
|
18483
18839
|
try {
|
|
18484
|
-
const body = JSON.stringify(
|
|
18485
|
-
event: "init_completed",
|
|
18486
|
-
agents_detected: agents,
|
|
18487
|
-
os: process.platform,
|
|
18488
|
-
node9_version: process.env.npm_package_version ?? "unknown"
|
|
18489
|
-
});
|
|
18840
|
+
const body = JSON.stringify(buildTelemetryPayload(agents, firstInstall));
|
|
18490
18841
|
const req = import_https4.default.request(
|
|
18491
18842
|
{
|
|
18492
18843
|
hostname: "api.node9.ai",
|
|
@@ -18519,6 +18870,12 @@ function registerInitCommand(program2) {
|
|
|
18519
18870
|
).action(
|
|
18520
18871
|
async (options) => {
|
|
18521
18872
|
console.log(import_chalk16.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
18873
|
+
{
|
|
18874
|
+
const migrated = migrateRenamedRuleKeys();
|
|
18875
|
+
for (const m of migrated) {
|
|
18876
|
+
console.log(import_chalk16.default.dim(` \u{1F527} Rule renamed: ${m.oldKey} \u2192 ${m.newKey}`));
|
|
18877
|
+
}
|
|
18878
|
+
}
|
|
18522
18879
|
let chosenMode = options.mode.toLowerCase();
|
|
18523
18880
|
if (!["standard", "strict", "audit", "observe"].includes(chosenMode)) {
|
|
18524
18881
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -18553,6 +18910,7 @@ function registerInitCommand(program2) {
|
|
|
18553
18910
|
console.log("");
|
|
18554
18911
|
}
|
|
18555
18912
|
const configPath = import_path39.default.join(import_os34.default.homedir(), ".node9", "config.json");
|
|
18913
|
+
const isFirstInstall = !import_fs38.default.existsSync(configPath);
|
|
18556
18914
|
if (import_fs38.default.existsSync(configPath) && !options.force) {
|
|
18557
18915
|
try {
|
|
18558
18916
|
const existing = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
|
|
@@ -18610,6 +18968,7 @@ function registerInitCommand(program2) {
|
|
|
18610
18968
|
else if (agent === "windsurf") await setupWindsurf();
|
|
18611
18969
|
else if (agent === "vscode") await setupVSCode();
|
|
18612
18970
|
else if (agent === "claudeDesktop") await setupClaudeDesktop();
|
|
18971
|
+
else if (agent === "opencode") await setupOpencode();
|
|
18613
18972
|
console.log("");
|
|
18614
18973
|
}
|
|
18615
18974
|
if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
|
|
@@ -18655,7 +19014,7 @@ function registerInitCommand(program2) {
|
|
|
18655
19014
|
message: "Send anonymous usage stats to help improve node9? (no code, no args)",
|
|
18656
19015
|
default: true
|
|
18657
19016
|
});
|
|
18658
|
-
if (sendTelemetry) fireTelemetryPing(found);
|
|
19017
|
+
if (sendTelemetry) fireTelemetryPing(found, isFirstInstall);
|
|
18659
19018
|
console.log("");
|
|
18660
19019
|
}
|
|
18661
19020
|
const agentList = found.join(", ");
|
|
@@ -20369,7 +20728,8 @@ var SETUP_FN = {
|
|
|
20369
20728
|
codex: setupCodex,
|
|
20370
20729
|
windsurf: setupWindsurf,
|
|
20371
20730
|
vscode: setupVSCode,
|
|
20372
|
-
claudeDesktop: setupClaudeDesktop
|
|
20731
|
+
claudeDesktop: setupClaudeDesktop,
|
|
20732
|
+
opencode: setupOpencode
|
|
20373
20733
|
};
|
|
20374
20734
|
var TEARDOWN_FN = {
|
|
20375
20735
|
claude: teardownClaude,
|
|
@@ -20378,7 +20738,8 @@ var TEARDOWN_FN = {
|
|
|
20378
20738
|
codex: teardownCodex,
|
|
20379
20739
|
windsurf: teardownWindsurf,
|
|
20380
20740
|
vscode: teardownVSCode,
|
|
20381
|
-
claudeDesktop: teardownClaudeDesktop
|
|
20741
|
+
claudeDesktop: teardownClaudeDesktop,
|
|
20742
|
+
opencode: teardownOpencode
|
|
20382
20743
|
};
|
|
20383
20744
|
var AGENT_NAMES = Object.keys(SETUP_FN);
|
|
20384
20745
|
function registerAgentsCommand(program2) {
|