@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.mjs
CHANGED
|
@@ -2251,6 +2251,42 @@ var init_dist = __esm({
|
|
|
2251
2251
|
regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
|
|
2252
2252
|
severity: "block",
|
|
2253
2253
|
keywords: ["age-secret-key-"]
|
|
2254
|
+
},
|
|
2255
|
+
// ── Database connection strings ───────────────────────────────────────────
|
|
2256
|
+
// Universal <scheme>://[user]:<password>@<host> shape. Covers the gap
|
|
2257
|
+
// vendor-prefix patterns (AWS / GitHub / Stripe / …) leave open. Matches
|
|
2258
|
+
// the whole URL so maskSecret produces `<scheme>...:****@...<host>` —
|
|
2259
|
+
// the password value never appears in the redacted sample.
|
|
2260
|
+
//
|
|
2261
|
+
// Schemes covered: redis, rediss (TLS), postgres, postgresql,
|
|
2262
|
+
// mongodb, mongodb+srv, mysql, mariadb, amqp, amqps, kafka,
|
|
2263
|
+
// clickhouse, cassandra. HTTP(S) / FTP / SSH are intentionally
|
|
2264
|
+
// excluded — they're not database URLs and adding them would
|
|
2265
|
+
// create false positives on every basic-auth URL in the wild.
|
|
2266
|
+
//
|
|
2267
|
+
// Requires `:password@` (4+ char password) so user-only URLs like
|
|
2268
|
+
// `redis://user@host` don't match. Stopwords ('your', '${', '<your',
|
|
2269
|
+
// 'placeholder', 'changeme', etc.) keep doc/README scans clean.
|
|
2270
|
+
{
|
|
2271
|
+
name: "Database Connection String",
|
|
2272
|
+
regex: /\b(redis|rediss|postgres|postgresql|mongodb|mongodb\+srv|mysql|mariadb|amqp|amqps|kafka|clickhouse|cassandra):\/\/[^:/\s@]*:[^@\s]{4,}@[^\s/]+/,
|
|
2273
|
+
severity: "block",
|
|
2274
|
+
keywords: [
|
|
2275
|
+
"redis://",
|
|
2276
|
+
"rediss://",
|
|
2277
|
+
"postgres://",
|
|
2278
|
+
"postgresql://",
|
|
2279
|
+
"mongodb://",
|
|
2280
|
+
"mongodb+srv://",
|
|
2281
|
+
"mysql://",
|
|
2282
|
+
"mariadb://",
|
|
2283
|
+
"amqp://",
|
|
2284
|
+
"amqps://",
|
|
2285
|
+
"kafka://",
|
|
2286
|
+
"clickhouse://",
|
|
2287
|
+
"cassandra://"
|
|
2288
|
+
],
|
|
2289
|
+
minEntropy: 3
|
|
2254
2290
|
}
|
|
2255
2291
|
];
|
|
2256
2292
|
DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
@@ -2353,7 +2389,7 @@ var init_dist = __esm({
|
|
|
2353
2389
|
},
|
|
2354
2390
|
{
|
|
2355
2391
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
2356
|
-
//
|
|
2392
|
+
// block-read-env-any-tool) so the AST FS-op path catches the
|
|
2357
2393
|
// same set the regex shield does — including Next.js / Vite's
|
|
2358
2394
|
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
2359
2395
|
// gitignored AND commonly contain real secrets.
|
|
@@ -3099,7 +3135,7 @@ var init_dist = __esm({
|
|
|
3099
3135
|
{
|
|
3100
3136
|
field: "command",
|
|
3101
3137
|
op: "matches",
|
|
3102
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3138
|
+
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[\\/\\\\]",
|
|
3103
3139
|
flags: "i"
|
|
3104
3140
|
}
|
|
3105
3141
|
],
|
|
@@ -3113,7 +3149,7 @@ var init_dist = __esm({
|
|
|
3113
3149
|
{
|
|
3114
3150
|
field: "command",
|
|
3115
3151
|
op: "matches",
|
|
3116
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
3152
|
+
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[\\/\\\\]",
|
|
3117
3153
|
flags: "i"
|
|
3118
3154
|
}
|
|
3119
3155
|
],
|
|
@@ -3127,7 +3163,7 @@ var init_dist = __esm({
|
|
|
3127
3163
|
{
|
|
3128
3164
|
field: "command",
|
|
3129
3165
|
op: "matches",
|
|
3130
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
3166
|
+
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|$|[;&|>)<])",
|
|
3131
3167
|
flags: "i"
|
|
3132
3168
|
}
|
|
3133
3169
|
],
|
|
@@ -3141,7 +3177,7 @@ var init_dist = __esm({
|
|
|
3141
3177
|
{
|
|
3142
3178
|
field: "command",
|
|
3143
3179
|
op: "matches",
|
|
3144
|
-
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)",
|
|
3180
|
+
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)",
|
|
3145
3181
|
flags: "i"
|
|
3146
3182
|
}
|
|
3147
3183
|
],
|
|
@@ -3177,7 +3213,7 @@ var init_dist = __esm({
|
|
|
3177
3213
|
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
3178
3214
|
},
|
|
3179
3215
|
{
|
|
3180
|
-
name: "shield:project-jail:
|
|
3216
|
+
name: "shield:project-jail:block-read-env-any-tool",
|
|
3181
3217
|
tool: "*",
|
|
3182
3218
|
conditions: [
|
|
3183
3219
|
{
|
|
@@ -3187,8 +3223,8 @@ var init_dist = __esm({
|
|
|
3187
3223
|
flags: "i"
|
|
3188
3224
|
}
|
|
3189
3225
|
],
|
|
3190
|
-
verdict: "
|
|
3191
|
-
reason: "Reading .env files
|
|
3226
|
+
verdict: "block",
|
|
3227
|
+
reason: "Reading .env files is blocked by project-jail shield"
|
|
3192
3228
|
},
|
|
3193
3229
|
{
|
|
3194
3230
|
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
@@ -3453,6 +3489,30 @@ function writeShieldOverride(shieldName, ruleName, verdict) {
|
|
|
3453
3489
|
overrides[shieldName] = { ...overrides[shieldName] ?? {}, [ruleName]: verdict };
|
|
3454
3490
|
writeShieldsFile({ ...current, overrides });
|
|
3455
3491
|
}
|
|
3492
|
+
function migrateRenamedRuleKeys() {
|
|
3493
|
+
const current = readShieldsFile();
|
|
3494
|
+
if (!current.overrides || Object.keys(current.overrides).length === 0) return [];
|
|
3495
|
+
const renameMap = new Map(RULE_KEY_MIGRATIONS);
|
|
3496
|
+
const migrated = [];
|
|
3497
|
+
const nextOverrides = {};
|
|
3498
|
+
let anyChange = false;
|
|
3499
|
+
for (const [shield, rules] of Object.entries(current.overrides)) {
|
|
3500
|
+
const nextRules = {};
|
|
3501
|
+
for (const [key, verdict] of Object.entries(rules)) {
|
|
3502
|
+
const newKey = renameMap.get(key);
|
|
3503
|
+
if (newKey) {
|
|
3504
|
+
if (!(newKey in nextRules)) nextRules[newKey] = verdict;
|
|
3505
|
+
migrated.push({ shield, oldKey: key, newKey });
|
|
3506
|
+
anyChange = true;
|
|
3507
|
+
} else {
|
|
3508
|
+
nextRules[key] = verdict;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
if (Object.keys(nextRules).length > 0) nextOverrides[shield] = nextRules;
|
|
3512
|
+
}
|
|
3513
|
+
if (anyChange) writeShieldsFile({ ...current, overrides: nextOverrides });
|
|
3514
|
+
return migrated;
|
|
3515
|
+
}
|
|
3456
3516
|
function clearShieldOverride(shieldName, ruleName) {
|
|
3457
3517
|
const current = readShieldsFile();
|
|
3458
3518
|
if (!current.overrides?.[shieldName]?.[ruleName]) return;
|
|
@@ -3501,7 +3561,7 @@ function installShield(name, shieldJson) {
|
|
|
3501
3561
|
fs2.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
3502
3562
|
fs2.renameSync(tmp, filePath);
|
|
3503
3563
|
}
|
|
3504
|
-
var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE;
|
|
3564
|
+
var USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS;
|
|
3505
3565
|
var init_shields = __esm({
|
|
3506
3566
|
"src/shields.ts"() {
|
|
3507
3567
|
"use strict";
|
|
@@ -3510,6 +3570,14 @@ var init_shields = __esm({
|
|
|
3510
3570
|
USER_SHIELDS_DIR = path2.join(os2.homedir(), ".node9", "shields");
|
|
3511
3571
|
SHIELDS = buildSHIELDS();
|
|
3512
3572
|
SHIELDS_STATE_FILE = path2.join(os2.homedir(), ".node9", "shields.json");
|
|
3573
|
+
RULE_KEY_MIGRATIONS = [
|
|
3574
|
+
// 2026-05-21 — project-jail .env reads promoted review → block. Renamed
|
|
3575
|
+
// so the key honestly describes the verdict. Credential-file rules
|
|
3576
|
+
// (.netrc, .npmrc, .docker, .kube, gcloud) deliberately stay at `review`
|
|
3577
|
+
// — see the rationale in packages/policy-engine/src/shell/index.ts
|
|
3578
|
+
// around the review-read-credentials rule.
|
|
3579
|
+
["shield:project-jail:review-read-env-any-tool", "shield:project-jail:block-read-env-any-tool"]
|
|
3580
|
+
];
|
|
3513
3581
|
}
|
|
3514
3582
|
});
|
|
3515
3583
|
|
|
@@ -6438,6 +6506,122 @@ var init_mcp_pin = __esm({
|
|
|
6438
6506
|
}
|
|
6439
6507
|
});
|
|
6440
6508
|
|
|
6509
|
+
// src/setup-opencode-shim.ts
|
|
6510
|
+
function renderOpencodeShim(input) {
|
|
6511
|
+
const { node9Argv, version: version2 } = input;
|
|
6512
|
+
return `// Auto-generated by \`node9 init\`. Do not edit \u2014 re-run init to upgrade.
|
|
6513
|
+
// NODE9_SHIM_VERSION = "${version2}"
|
|
6514
|
+
//
|
|
6515
|
+
// node9 protection shim for Opencode. Wires three hooks against the
|
|
6516
|
+
// agent's plugin API and shells out to the node9 CLI for verdicts.
|
|
6517
|
+
//
|
|
6518
|
+
// Block by throwing from a hook handler; Opencode's plugin trigger
|
|
6519
|
+
// (packages/opencode/src/plugin/index.ts:273) propagates the error and
|
|
6520
|
+
// halts the tool call.
|
|
6521
|
+
|
|
6522
|
+
const { spawnSync } = require("node:child_process");
|
|
6523
|
+
|
|
6524
|
+
// argv prefix for invoking the node9 CLI. argv[0] is the executable
|
|
6525
|
+
// (either the npm-installed wrapper script or the node binary); the
|
|
6526
|
+
// remaining entries (if any) are passed as the leading args before
|
|
6527
|
+
// the subcommand name (e.g. ["/path/to/dist/cli.js"] for dev mode).
|
|
6528
|
+
const NODE9_ARGV = ${JSON.stringify(node9Argv)};
|
|
6529
|
+
const HOOK_TIMEOUT_MS = 30000;
|
|
6530
|
+
const LOG_TIMEOUT_MS = 5000;
|
|
6531
|
+
|
|
6532
|
+
function parseReason(stdout) {
|
|
6533
|
+
// node9 check emits {decision, reason, message} JSON on stdout when
|
|
6534
|
+
// blocking. Fall back to a generic string if anything goes wrong.
|
|
6535
|
+
try {
|
|
6536
|
+
const v = JSON.parse(stdout || "");
|
|
6537
|
+
return v && (v.reason || v.message);
|
|
6538
|
+
} catch (e) {
|
|
6539
|
+
return null;
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
function extractPromptText(parts) {
|
|
6544
|
+
// chat.message gives us parts: Part[]. We only DLP-scan text parts
|
|
6545
|
+
// (image / tool_use parts can't contain pasted secrets in any form
|
|
6546
|
+
// node9's scanner currently recognizes).
|
|
6547
|
+
if (!Array.isArray(parts)) return "";
|
|
6548
|
+
return parts
|
|
6549
|
+
.filter((p) => p && p.type === "text")
|
|
6550
|
+
.map((p) => (p && p.text) || "")
|
|
6551
|
+
.join("\\n");
|
|
6552
|
+
}
|
|
6553
|
+
|
|
6554
|
+
module.exports = {
|
|
6555
|
+
id: "node9",
|
|
6556
|
+
server: async (input) => ({
|
|
6557
|
+
"tool.execute.before": async (ctx, mutable) => {
|
|
6558
|
+
const payload = {
|
|
6559
|
+
hook_event_name: "PreToolUse",
|
|
6560
|
+
tool_name: ctx.tool,
|
|
6561
|
+
tool_input: mutable.args,
|
|
6562
|
+
session_id: ctx.sessionID,
|
|
6563
|
+
cwd: input.directory,
|
|
6564
|
+
meta: { agent: "Opencode" },
|
|
6565
|
+
};
|
|
6566
|
+
const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
|
|
6567
|
+
input: JSON.stringify(payload),
|
|
6568
|
+
encoding: "utf-8",
|
|
6569
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
6570
|
+
});
|
|
6571
|
+
if (r.status === 0) return;
|
|
6572
|
+
const reason = parseReason(r.stdout) || "blocked by node9";
|
|
6573
|
+
throw new Error("[node9] " + reason);
|
|
6574
|
+
},
|
|
6575
|
+
|
|
6576
|
+
"tool.execute.after": async (ctx) => {
|
|
6577
|
+
// Fire-and-forget audit log \u2014 failures here must NEVER throw,
|
|
6578
|
+
// or we'd retroactively "block" a tool call that already ran.
|
|
6579
|
+
const payload = {
|
|
6580
|
+
hook_event_name: "PostToolUse",
|
|
6581
|
+
tool_name: ctx.tool,
|
|
6582
|
+
session_id: ctx.sessionID,
|
|
6583
|
+
cwd: input.directory,
|
|
6584
|
+
meta: { agent: "Opencode" },
|
|
6585
|
+
};
|
|
6586
|
+
try {
|
|
6587
|
+
spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "log"], {
|
|
6588
|
+
input: JSON.stringify(payload),
|
|
6589
|
+
encoding: "utf-8",
|
|
6590
|
+
timeout: LOG_TIMEOUT_MS,
|
|
6591
|
+
});
|
|
6592
|
+
} catch (e) {
|
|
6593
|
+
// Swallow: audit log gaps are preferable to crashing the agent.
|
|
6594
|
+
}
|
|
6595
|
+
},
|
|
6596
|
+
|
|
6597
|
+
"chat.message": async (ctx, mutable) => {
|
|
6598
|
+
const prompt = extractPromptText(mutable.parts);
|
|
6599
|
+
if (!prompt) return;
|
|
6600
|
+
const payload = {
|
|
6601
|
+
hook_event_name: "UserPromptSubmit",
|
|
6602
|
+
prompt,
|
|
6603
|
+
session_id: ctx.sessionID,
|
|
6604
|
+
meta: { agent: "Opencode" },
|
|
6605
|
+
};
|
|
6606
|
+
const r = spawnSync(NODE9_ARGV[0], [...NODE9_ARGV.slice(1), "check"], {
|
|
6607
|
+
input: JSON.stringify(payload),
|
|
6608
|
+
encoding: "utf-8",
|
|
6609
|
+
timeout: HOOK_TIMEOUT_MS,
|
|
6610
|
+
});
|
|
6611
|
+
if (r.status === 0) return;
|
|
6612
|
+
const reason = parseReason(r.stdout) || "prompt blocked";
|
|
6613
|
+
throw new Error("[node9] " + reason);
|
|
6614
|
+
},
|
|
6615
|
+
}),
|
|
6616
|
+
};
|
|
6617
|
+
`;
|
|
6618
|
+
}
|
|
6619
|
+
var init_setup_opencode_shim = __esm({
|
|
6620
|
+
"src/setup-opencode-shim.ts"() {
|
|
6621
|
+
"use strict";
|
|
6622
|
+
}
|
|
6623
|
+
});
|
|
6624
|
+
|
|
6441
6625
|
// src/setup.ts
|
|
6442
6626
|
import fs13 from "fs";
|
|
6443
6627
|
import path15 from "path";
|
|
@@ -6900,6 +7084,18 @@ function claudeDesktopConfigPath(homeDir2 = os12.homedir()) {
|
|
|
6900
7084
|
}
|
|
6901
7085
|
return null;
|
|
6902
7086
|
}
|
|
7087
|
+
function binaryInPath(binary) {
|
|
7088
|
+
const pathEnv = process.env.PATH ?? "";
|
|
7089
|
+
for (const dir of pathEnv.split(path15.delimiter)) {
|
|
7090
|
+
if (!dir) continue;
|
|
7091
|
+
try {
|
|
7092
|
+
fs13.accessSync(path15.join(dir, binary), fs13.constants.X_OK);
|
|
7093
|
+
return true;
|
|
7094
|
+
} catch {
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
return false;
|
|
7098
|
+
}
|
|
6903
7099
|
function detectAgents(homeDir2 = os12.homedir()) {
|
|
6904
7100
|
const exists = (p) => {
|
|
6905
7101
|
try {
|
|
@@ -6921,7 +7117,10 @@ function detectAgents(homeDir2 = os12.homedir()) {
|
|
|
6921
7117
|
codex: exists(path15.join(homeDir2, ".codex")),
|
|
6922
7118
|
windsurf: exists(path15.join(homeDir2, ".codeium", "windsurf")),
|
|
6923
7119
|
vscode: exists(path15.join(homeDir2, ".vscode")),
|
|
6924
|
-
claudeDesktop: desktopPath !== null && exists(path15.dirname(desktopPath))
|
|
7120
|
+
claudeDesktop: desktopPath !== null && exists(path15.dirname(desktopPath)),
|
|
7121
|
+
// Opencode creates ~/.config/opencode lazily on first launch — fall back
|
|
7122
|
+
// to a PATH lookup so installed-but-never-launched CLIs are still wired.
|
|
7123
|
+
opencode: exists(path15.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode")
|
|
6925
7124
|
};
|
|
6926
7125
|
}
|
|
6927
7126
|
async function setupCursor() {
|
|
@@ -7554,6 +7753,127 @@ function teardownClaudeDesktop() {
|
|
|
7554
7753
|
console.log(chalk.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
|
|
7555
7754
|
}
|
|
7556
7755
|
}
|
|
7756
|
+
function node9ArgvForShim() {
|
|
7757
|
+
if (process.env.NODE9_TESTING === "1") return ["node9"];
|
|
7758
|
+
const nodeExec = process.execPath;
|
|
7759
|
+
const cliScript = process.argv[1];
|
|
7760
|
+
if (cliScript && cliScript.endsWith(".js")) return [nodeExec, cliScript];
|
|
7761
|
+
return [cliScript];
|
|
7762
|
+
}
|
|
7763
|
+
function node9Version() {
|
|
7764
|
+
try {
|
|
7765
|
+
const pkg = JSON.parse(
|
|
7766
|
+
fs13.readFileSync(path15.join(__dirname, "..", "package.json"), "utf-8")
|
|
7767
|
+
);
|
|
7768
|
+
return pkg.version ?? "0.0.0";
|
|
7769
|
+
} catch {
|
|
7770
|
+
return "0.0.0";
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
async function setupOpencode() {
|
|
7774
|
+
seedMcpPinsIfMissing();
|
|
7775
|
+
const homeDir2 = os12.homedir();
|
|
7776
|
+
const configDir = path15.join(homeDir2, ".config", "opencode");
|
|
7777
|
+
const pluginsDir = path15.join(configDir, "plugins");
|
|
7778
|
+
const configPath = path15.join(configDir, "opencode.json");
|
|
7779
|
+
const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
|
|
7780
|
+
try {
|
|
7781
|
+
fs13.mkdirSync(pluginsDir, { recursive: true });
|
|
7782
|
+
} catch (err2) {
|
|
7783
|
+
const code = err2.code;
|
|
7784
|
+
if (code !== "EEXIST") {
|
|
7785
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Could not create ${pluginsDir}: ${code ?? String(err2)}`));
|
|
7786
|
+
return;
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
const shimContent = renderOpencodeShim({
|
|
7790
|
+
node9Argv: node9ArgvForShim(),
|
|
7791
|
+
version: node9Version()
|
|
7792
|
+
});
|
|
7793
|
+
let pluginChanged = false;
|
|
7794
|
+
const existingShim = (() => {
|
|
7795
|
+
try {
|
|
7796
|
+
return fs13.readFileSync(pluginPath, "utf-8");
|
|
7797
|
+
} catch {
|
|
7798
|
+
return null;
|
|
7799
|
+
}
|
|
7800
|
+
})();
|
|
7801
|
+
if (existingShim !== shimContent) {
|
|
7802
|
+
fs13.writeFileSync(pluginPath, shimContent);
|
|
7803
|
+
pluginChanged = true;
|
|
7804
|
+
if (existingShim) {
|
|
7805
|
+
console.log(chalk.yellow(" \u{1F527} Opencode plugin shim updated to current version"));
|
|
7806
|
+
} else {
|
|
7807
|
+
console.log(
|
|
7808
|
+
chalk.green(" \u2705 Opencode plugin installed \u2192 tool.execute.before / after, chat.message")
|
|
7809
|
+
);
|
|
7810
|
+
}
|
|
7811
|
+
}
|
|
7812
|
+
const config = readJson(configPath) ?? {};
|
|
7813
|
+
const mcp = config.mcp ?? {};
|
|
7814
|
+
let configChanged = false;
|
|
7815
|
+
const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
|
|
7816
|
+
const desiredEntry = {
|
|
7817
|
+
type: "local",
|
|
7818
|
+
command: desiredCommand,
|
|
7819
|
+
enabled: true
|
|
7820
|
+
};
|
|
7821
|
+
const existing = mcp["node9"];
|
|
7822
|
+
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;
|
|
7823
|
+
if (!entryMatches) {
|
|
7824
|
+
mcp["node9"] = desiredEntry;
|
|
7825
|
+
config.mcp = mcp;
|
|
7826
|
+
configChanged = true;
|
|
7827
|
+
if (existing) {
|
|
7828
|
+
console.log(chalk.yellow(" \u{1F527} Opencode MCP entry updated (node9)"));
|
|
7829
|
+
} else {
|
|
7830
|
+
console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
if (configChanged) writeJson(configPath, config);
|
|
7834
|
+
if (pluginChanged || configChanged) {
|
|
7835
|
+
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
|
|
7836
|
+
console.log(chalk.gray(" Restart Opencode for changes to take effect."));
|
|
7837
|
+
printDaemonTip();
|
|
7838
|
+
} else {
|
|
7839
|
+
console.log(chalk.blue(" \u2139\uFE0F Node9 is already fully configured for Opencode."));
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
function teardownOpencode() {
|
|
7843
|
+
const homeDir2 = os12.homedir();
|
|
7844
|
+
const configDir = path15.join(homeDir2, ".config", "opencode");
|
|
7845
|
+
const pluginsDir = path15.join(configDir, "plugins");
|
|
7846
|
+
const configPath = path15.join(configDir, "opencode.json");
|
|
7847
|
+
const pluginPath = path15.join(pluginsDir, OPENCODE_PLUGIN_NAME);
|
|
7848
|
+
try {
|
|
7849
|
+
if (fs13.existsSync(pluginPath)) {
|
|
7850
|
+
fs13.unlinkSync(pluginPath);
|
|
7851
|
+
console.log(chalk.green(" \u2705 Removed node9 plugin from ~/.config/opencode/plugins/"));
|
|
7852
|
+
}
|
|
7853
|
+
} catch (err2) {
|
|
7854
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
|
|
7855
|
+
}
|
|
7856
|
+
const config = readJson(configPath);
|
|
7857
|
+
if (!config) {
|
|
7858
|
+
console.log(chalk.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
|
|
7859
|
+
return;
|
|
7860
|
+
}
|
|
7861
|
+
const mcp = config.mcp ?? {};
|
|
7862
|
+
let changed = false;
|
|
7863
|
+
if (mcp["node9"]) {
|
|
7864
|
+
delete mcp["node9"];
|
|
7865
|
+
changed = true;
|
|
7866
|
+
console.log(
|
|
7867
|
+
chalk.green(" \u2705 Removed node9 MCP server entry from ~/.config/opencode/opencode.json")
|
|
7868
|
+
);
|
|
7869
|
+
}
|
|
7870
|
+
if (changed) {
|
|
7871
|
+
config.mcp = mcp;
|
|
7872
|
+
writeJson(configPath, config);
|
|
7873
|
+
} else {
|
|
7874
|
+
console.log(chalk.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7557
7877
|
function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
7558
7878
|
const detected = detectAgents(homeDir2);
|
|
7559
7879
|
const claudeWired = (() => {
|
|
@@ -7636,16 +7956,38 @@ function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
|
7636
7956
|
return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
|
|
7637
7957
|
})(),
|
|
7638
7958
|
mode: detected.claudeDesktop ? "mcp" : null
|
|
7959
|
+
},
|
|
7960
|
+
{
|
|
7961
|
+
name: "opencode",
|
|
7962
|
+
label: "Opencode",
|
|
7963
|
+
installed: detected.opencode,
|
|
7964
|
+
wired: (() => {
|
|
7965
|
+
const pluginPath = path15.join(
|
|
7966
|
+
homeDir2,
|
|
7967
|
+
".config",
|
|
7968
|
+
"opencode",
|
|
7969
|
+
"plugins",
|
|
7970
|
+
OPENCODE_PLUGIN_NAME
|
|
7971
|
+
);
|
|
7972
|
+
if (fs13.existsSync(pluginPath)) return true;
|
|
7973
|
+
const cfg = readJson(
|
|
7974
|
+
path15.join(homeDir2, ".config", "opencode", "opencode.json")
|
|
7975
|
+
);
|
|
7976
|
+
return !!cfg?.mcp?.["node9"];
|
|
7977
|
+
})(),
|
|
7978
|
+
mode: detected.opencode ? "hooks" : null
|
|
7639
7979
|
}
|
|
7640
7980
|
];
|
|
7641
7981
|
}
|
|
7642
|
-
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS;
|
|
7982
|
+
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME;
|
|
7643
7983
|
var init_setup = __esm({
|
|
7644
7984
|
"src/setup.ts"() {
|
|
7645
7985
|
"use strict";
|
|
7646
7986
|
init_mcp_pin();
|
|
7987
|
+
init_setup_opencode_shim();
|
|
7647
7988
|
NODE9_MCP_SERVER_ENTRY = { command: "node9", args: ["mcp-server"] };
|
|
7648
7989
|
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
7990
|
+
OPENCODE_PLUGIN_NAME = "node9.js";
|
|
7649
7991
|
}
|
|
7650
7992
|
});
|
|
7651
7993
|
|
|
@@ -15944,6 +16286,11 @@ function sanitize2(value) {
|
|
|
15944
16286
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
15945
16287
|
}
|
|
15946
16288
|
function detectAiAgent(payload) {
|
|
16289
|
+
const meta = payload.meta;
|
|
16290
|
+
if (meta && typeof meta === "object") {
|
|
16291
|
+
const tagged = meta.agent;
|
|
16292
|
+
if (typeof tagged === "string" && tagged.length > 0) return tagged;
|
|
16293
|
+
}
|
|
15947
16294
|
if (payload.turn_id !== void 0) {
|
|
15948
16295
|
return "Codex";
|
|
15949
16296
|
}
|
|
@@ -18451,14 +18798,18 @@ import path39 from "path";
|
|
|
18451
18798
|
import os34 from "os";
|
|
18452
18799
|
import https4 from "https";
|
|
18453
18800
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "project-jail"];
|
|
18454
|
-
function
|
|
18801
|
+
function buildTelemetryPayload(agents, firstInstall) {
|
|
18802
|
+
return {
|
|
18803
|
+
event: "init_completed",
|
|
18804
|
+
agents_detected: agents,
|
|
18805
|
+
os: process.platform,
|
|
18806
|
+
node9_version: node9Version(),
|
|
18807
|
+
first_install: firstInstall
|
|
18808
|
+
};
|
|
18809
|
+
}
|
|
18810
|
+
function fireTelemetryPing(agents, firstInstall) {
|
|
18455
18811
|
try {
|
|
18456
|
-
const body = JSON.stringify(
|
|
18457
|
-
event: "init_completed",
|
|
18458
|
-
agents_detected: agents,
|
|
18459
|
-
os: process.platform,
|
|
18460
|
-
node9_version: process.env.npm_package_version ?? "unknown"
|
|
18461
|
-
});
|
|
18812
|
+
const body = JSON.stringify(buildTelemetryPayload(agents, firstInstall));
|
|
18462
18813
|
const req = https4.request(
|
|
18463
18814
|
{
|
|
18464
18815
|
hostname: "api.node9.ai",
|
|
@@ -18491,6 +18842,12 @@ function registerInitCommand(program2) {
|
|
|
18491
18842
|
).action(
|
|
18492
18843
|
async (options) => {
|
|
18493
18844
|
console.log(chalk16.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
18845
|
+
{
|
|
18846
|
+
const migrated = migrateRenamedRuleKeys();
|
|
18847
|
+
for (const m of migrated) {
|
|
18848
|
+
console.log(chalk16.dim(` \u{1F527} Rule renamed: ${m.oldKey} \u2192 ${m.newKey}`));
|
|
18849
|
+
}
|
|
18850
|
+
}
|
|
18494
18851
|
let chosenMode = options.mode.toLowerCase();
|
|
18495
18852
|
if (!["standard", "strict", "audit", "observe"].includes(chosenMode)) {
|
|
18496
18853
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -18525,6 +18882,7 @@ function registerInitCommand(program2) {
|
|
|
18525
18882
|
console.log("");
|
|
18526
18883
|
}
|
|
18527
18884
|
const configPath = path39.join(os34.homedir(), ".node9", "config.json");
|
|
18885
|
+
const isFirstInstall = !fs38.existsSync(configPath);
|
|
18528
18886
|
if (fs38.existsSync(configPath) && !options.force) {
|
|
18529
18887
|
try {
|
|
18530
18888
|
const existing = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
|
|
@@ -18582,6 +18940,7 @@ function registerInitCommand(program2) {
|
|
|
18582
18940
|
else if (agent === "windsurf") await setupWindsurf();
|
|
18583
18941
|
else if (agent === "vscode") await setupVSCode();
|
|
18584
18942
|
else if (agent === "claudeDesktop") await setupClaudeDesktop();
|
|
18943
|
+
else if (agent === "opencode") await setupOpencode();
|
|
18585
18944
|
console.log("");
|
|
18586
18945
|
}
|
|
18587
18946
|
if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
|
|
@@ -18627,7 +18986,7 @@ function registerInitCommand(program2) {
|
|
|
18627
18986
|
message: "Send anonymous usage stats to help improve node9? (no code, no args)",
|
|
18628
18987
|
default: true
|
|
18629
18988
|
});
|
|
18630
|
-
if (sendTelemetry) fireTelemetryPing(found);
|
|
18989
|
+
if (sendTelemetry) fireTelemetryPing(found, isFirstInstall);
|
|
18631
18990
|
console.log("");
|
|
18632
18991
|
}
|
|
18633
18992
|
const agentList = found.join(", ");
|
|
@@ -20341,7 +20700,8 @@ var SETUP_FN = {
|
|
|
20341
20700
|
codex: setupCodex,
|
|
20342
20701
|
windsurf: setupWindsurf,
|
|
20343
20702
|
vscode: setupVSCode,
|
|
20344
|
-
claudeDesktop: setupClaudeDesktop
|
|
20703
|
+
claudeDesktop: setupClaudeDesktop,
|
|
20704
|
+
opencode: setupOpencode
|
|
20345
20705
|
};
|
|
20346
20706
|
var TEARDOWN_FN = {
|
|
20347
20707
|
claude: teardownClaude,
|
|
@@ -20350,7 +20710,8 @@ var TEARDOWN_FN = {
|
|
|
20350
20710
|
codex: teardownCodex,
|
|
20351
20711
|
windsurf: teardownWindsurf,
|
|
20352
20712
|
vscode: teardownVSCode,
|
|
20353
|
-
claudeDesktop: teardownClaudeDesktop
|
|
20713
|
+
claudeDesktop: teardownClaudeDesktop,
|
|
20714
|
+
opencode: teardownOpencode
|
|
20354
20715
|
};
|
|
20355
20716
|
var AGENT_NAMES = Object.keys(SETUP_FN);
|
|
20356
20717
|
function registerAgentsCommand(program2) {
|