@node9/proxy 1.10.3 → 1.11.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/README.md +5 -0
- package/dist/cli.js +802 -359
- package/dist/cli.mjs +800 -357
- package/dist/index.js +58 -14
- package/dist/index.mjs +58 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
const lines = result.error.issues.map((issue) => {
|
|
171
|
-
const
|
|
172
|
-
return ` \u2022 ${
|
|
171
|
+
const path41 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
172
|
+
return ` \u2022 ${path41}: ${issue.message}`;
|
|
173
173
|
});
|
|
174
174
|
return {
|
|
175
175
|
sanitized,
|
|
@@ -276,6 +276,11 @@ var init_config_schema = __esm({
|
|
|
276
276
|
enabled: import_zod.z.boolean().optional(),
|
|
277
277
|
threshold: import_zod.z.number().min(2).optional(),
|
|
278
278
|
windowSeconds: import_zod.z.number().min(10).optional()
|
|
279
|
+
}).optional(),
|
|
280
|
+
skillPinning: import_zod.z.object({
|
|
281
|
+
enabled: import_zod.z.boolean().optional(),
|
|
282
|
+
mode: import_zod.z.enum(["warn", "block"]).optional(),
|
|
283
|
+
roots: import_zod.z.array(import_zod.z.string()).optional()
|
|
279
284
|
}).optional()
|
|
280
285
|
}).optional(),
|
|
281
286
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -571,7 +576,11 @@ function getConfig(cwd) {
|
|
|
571
576
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
572
577
|
},
|
|
573
578
|
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
574
|
-
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
579
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
|
|
580
|
+
skillPinning: {
|
|
581
|
+
...DEFAULT_CONFIG.policy.skillPinning,
|
|
582
|
+
roots: [...DEFAULT_CONFIG.policy.skillPinning.roots]
|
|
583
|
+
}
|
|
575
584
|
};
|
|
576
585
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
577
586
|
const applyLayer = (source) => {
|
|
@@ -624,6 +633,16 @@ function getConfig(cwd) {
|
|
|
624
633
|
if (ld.windowSeconds !== void 0)
|
|
625
634
|
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
626
635
|
}
|
|
636
|
+
if (p.skillPinning && typeof p.skillPinning === "object") {
|
|
637
|
+
const sp = p.skillPinning;
|
|
638
|
+
if (sp.enabled !== void 0) mergedPolicy.skillPinning.enabled = sp.enabled;
|
|
639
|
+
if (sp.mode !== void 0) mergedPolicy.skillPinning.mode = sp.mode;
|
|
640
|
+
if (Array.isArray(sp.roots)) {
|
|
641
|
+
for (const r of sp.roots) {
|
|
642
|
+
if (typeof r === "string" && r.length > 0) mergedPolicy.skillPinning.roots.push(r);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
627
646
|
const envs = source.environments || {};
|
|
628
647
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
629
648
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -675,6 +694,7 @@ function getConfig(cwd) {
|
|
|
675
694
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
676
695
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
677
696
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
697
|
+
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
678
698
|
mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
|
|
679
699
|
mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
|
|
680
700
|
mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
|
|
@@ -942,7 +962,8 @@ var init_config = __esm({
|
|
|
942
962
|
}
|
|
943
963
|
],
|
|
944
964
|
dlp: { enabled: true, scanIgnoredTools: true },
|
|
945
|
-
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
965
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
|
|
966
|
+
skillPinning: { enabled: false, mode: "warn", roots: [] }
|
|
946
967
|
},
|
|
947
968
|
environments: {}
|
|
948
969
|
};
|
|
@@ -1746,9 +1767,9 @@ function matchesPattern(text, patterns) {
|
|
|
1746
1767
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1747
1768
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1748
1769
|
}
|
|
1749
|
-
function getNestedValue(obj,
|
|
1770
|
+
function getNestedValue(obj, path41) {
|
|
1750
1771
|
if (!obj || typeof obj !== "object") return null;
|
|
1751
|
-
return
|
|
1772
|
+
return path41.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1752
1773
|
}
|
|
1753
1774
|
function shouldSnapshot(toolName, args, config) {
|
|
1754
1775
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2343,6 +2364,15 @@ var init_policy = __esm({
|
|
|
2343
2364
|
});
|
|
2344
2365
|
|
|
2345
2366
|
// src/auth/state.ts
|
|
2367
|
+
function extractCommandPattern(toolName, args) {
|
|
2368
|
+
const lower = toolName.toLowerCase();
|
|
2369
|
+
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
2370
|
+
const a = args;
|
|
2371
|
+
const cmd = typeof a?.["command"] === "string" ? a["command"].trim() : "";
|
|
2372
|
+
if (!cmd) return void 0;
|
|
2373
|
+
const words = cmd.split(/\s+/);
|
|
2374
|
+
return words.slice(0, 2).join(" ");
|
|
2375
|
+
}
|
|
2346
2376
|
function checkPause() {
|
|
2347
2377
|
try {
|
|
2348
2378
|
if (!import_fs8.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
@@ -2376,7 +2406,7 @@ function resumeNode9() {
|
|
|
2376
2406
|
} catch {
|
|
2377
2407
|
}
|
|
2378
2408
|
}
|
|
2379
|
-
function getActiveTrustSession(toolName) {
|
|
2409
|
+
function getActiveTrustSession(toolName, args) {
|
|
2380
2410
|
try {
|
|
2381
2411
|
if (!import_fs8.default.existsSync(TRUST_FILE)) return false;
|
|
2382
2412
|
const trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
@@ -2385,12 +2415,20 @@ function getActiveTrustSession(toolName) {
|
|
|
2385
2415
|
if (active.length !== trust.entries.length) {
|
|
2386
2416
|
import_fs8.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
2387
2417
|
}
|
|
2388
|
-
return active.some((e) =>
|
|
2418
|
+
return active.some((e) => {
|
|
2419
|
+
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
2420
|
+
if (e.commandPattern) {
|
|
2421
|
+
const actual = extractCommandPattern(toolName, args) ?? "";
|
|
2422
|
+
return actual === e.commandPattern || actual.startsWith(e.commandPattern + " ");
|
|
2423
|
+
}
|
|
2424
|
+
return true;
|
|
2425
|
+
});
|
|
2389
2426
|
} catch {
|
|
2390
2427
|
return false;
|
|
2391
2428
|
}
|
|
2392
2429
|
}
|
|
2393
|
-
function writeTrustSession(toolName, durationMs) {
|
|
2430
|
+
function writeTrustSession(toolName, durationMs, args) {
|
|
2431
|
+
const commandPattern = extractCommandPattern(toolName, args);
|
|
2394
2432
|
try {
|
|
2395
2433
|
let trust = { entries: [] };
|
|
2396
2434
|
try {
|
|
@@ -2400,8 +2438,14 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
2400
2438
|
} catch {
|
|
2401
2439
|
}
|
|
2402
2440
|
const now = Date.now();
|
|
2403
|
-
trust.entries = trust.entries.filter(
|
|
2404
|
-
|
|
2441
|
+
trust.entries = trust.entries.filter(
|
|
2442
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > now
|
|
2443
|
+
);
|
|
2444
|
+
trust.entries.push({
|
|
2445
|
+
tool: toolName,
|
|
2446
|
+
...commandPattern && { commandPattern },
|
|
2447
|
+
expiry: now + durationMs
|
|
2448
|
+
});
|
|
2405
2449
|
atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
|
|
2406
2450
|
} catch (err2) {
|
|
2407
2451
|
if (process.env.NODE9_DEBUG === "1") {
|
|
@@ -3400,12 +3444,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3400
3444
|
};
|
|
3401
3445
|
}
|
|
3402
3446
|
}
|
|
3403
|
-
if (getActiveTrustSession(toolName)) {
|
|
3404
|
-
if (approvers.cloud && creds?.apiKey)
|
|
3405
|
-
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3406
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3407
|
-
return { approved: true, checkedBy: "trust" };
|
|
3408
|
-
}
|
|
3409
3447
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
3410
3448
|
if (policyResult.decision === "allow") {
|
|
3411
3449
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -3487,6 +3525,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3487
3525
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
|
|
3488
3526
|
return { approved: true };
|
|
3489
3527
|
}
|
|
3528
|
+
if (!taintWarning && getActiveTrustSession(toolName, args)) {
|
|
3529
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3530
|
+
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3531
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3532
|
+
return { approved: true, checkedBy: "trust" };
|
|
3533
|
+
}
|
|
3490
3534
|
if (taintWarning) {
|
|
3491
3535
|
explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
|
|
3492
3536
|
riskMetadata = computeRiskMetadata(
|
|
@@ -3619,7 +3663,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3619
3663
|
riskMetadata?.ruleDescription
|
|
3620
3664
|
);
|
|
3621
3665
|
if (decision === "always_allow") {
|
|
3622
|
-
writeTrustSession(toolName, 36e5);
|
|
3666
|
+
writeTrustSession(toolName, 36e5, args);
|
|
3623
3667
|
return { approved: true, checkedBy: "trust" };
|
|
3624
3668
|
}
|
|
3625
3669
|
const isApproved = decision === "allow";
|
|
@@ -5792,7 +5836,7 @@ function writeGlobalSetting(key, value) {
|
|
|
5792
5836
|
config.settings[key] = value;
|
|
5793
5837
|
atomicWriteSync2(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5794
5838
|
}
|
|
5795
|
-
function writeTrustEntry(toolName, durationMs) {
|
|
5839
|
+
function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
5796
5840
|
try {
|
|
5797
5841
|
let trust = { entries: [] };
|
|
5798
5842
|
try {
|
|
@@ -5800,8 +5844,14 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5800
5844
|
trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5801
5845
|
} catch {
|
|
5802
5846
|
}
|
|
5803
|
-
trust.entries = trust.entries.filter(
|
|
5804
|
-
|
|
5847
|
+
trust.entries = trust.entries.filter(
|
|
5848
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > Date.now()
|
|
5849
|
+
);
|
|
5850
|
+
trust.entries.push({
|
|
5851
|
+
tool: toolName,
|
|
5852
|
+
...commandPattern && { commandPattern },
|
|
5853
|
+
expiry: Date.now() + durationMs
|
|
5854
|
+
});
|
|
5805
5855
|
atomicWriteSync2(TRUST_FILE2, JSON.stringify(trust, null, 2));
|
|
5806
5856
|
} catch {
|
|
5807
5857
|
}
|
|
@@ -6730,7 +6780,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6730
6780
|
);
|
|
6731
6781
|
if (decision === "trust" && trustDuration) {
|
|
6732
6782
|
const ms = TRUST_DURATIONS[trustDuration] ?? 60 * 6e4;
|
|
6733
|
-
|
|
6783
|
+
const commandPattern = extractCommandPattern(entry.toolName, entry.args);
|
|
6784
|
+
writeTrustEntry(entry.toolName, ms, commandPattern);
|
|
6734
6785
|
appendAuditLog({
|
|
6735
6786
|
toolName: entry.toolName,
|
|
6736
6787
|
args: entry.args,
|
|
@@ -7185,6 +7236,7 @@ var init_server = __esm({
|
|
|
7185
7236
|
init_shields();
|
|
7186
7237
|
init_ui2();
|
|
7187
7238
|
init_state2();
|
|
7239
|
+
init_state();
|
|
7188
7240
|
init_patch();
|
|
7189
7241
|
init_config_schema();
|
|
7190
7242
|
init_costSync();
|
|
@@ -7537,22 +7589,22 @@ function formatBase(activity) {
|
|
|
7537
7589
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7538
7590
|
const icon = getIcon(activity.tool);
|
|
7539
7591
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7540
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7592
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os31.default.homedir(), "~");
|
|
7541
7593
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7542
|
-
return `${
|
|
7594
|
+
return `${import_chalk24.default.gray(time)} ${icon} ${import_chalk24.default.white.bold(toolName)} ${import_chalk24.default.dim(argsPreview)}`;
|
|
7543
7595
|
}
|
|
7544
7596
|
function renderResult(activity, result) {
|
|
7545
7597
|
const base = formatBase(activity);
|
|
7546
7598
|
let status;
|
|
7547
7599
|
if (result.status === "allow") {
|
|
7548
|
-
status =
|
|
7600
|
+
status = import_chalk24.default.green("\u2713 ALLOW");
|
|
7549
7601
|
} else if (result.status === "dlp") {
|
|
7550
|
-
status =
|
|
7602
|
+
status = import_chalk24.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7551
7603
|
} else {
|
|
7552
|
-
status =
|
|
7604
|
+
status = import_chalk24.default.red("\u2717 BLOCK");
|
|
7553
7605
|
}
|
|
7554
7606
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7555
|
-
const costSuffix = cost == null ? "" :
|
|
7607
|
+
const costSuffix = cost == null ? "" : import_chalk24.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7556
7608
|
if (process.stdout.isTTY) {
|
|
7557
7609
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7558
7610
|
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7569,19 +7621,19 @@ function renderResult(activity, result) {
|
|
|
7569
7621
|
}
|
|
7570
7622
|
function renderPending(activity) {
|
|
7571
7623
|
if (!process.stdout.isTTY) return;
|
|
7572
|
-
const line = `${formatBase(activity)} ${
|
|
7624
|
+
const line = `${formatBase(activity)} ${import_chalk24.default.yellow("\u25CF \u2026")}`;
|
|
7573
7625
|
pendingShownForId = activity.id;
|
|
7574
7626
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7575
7627
|
process.stdout.write(`${line}\r`);
|
|
7576
7628
|
}
|
|
7577
7629
|
async function ensureDaemon() {
|
|
7578
7630
|
let pidPort = null;
|
|
7579
|
-
if (
|
|
7631
|
+
if (import_fs35.default.existsSync(PID_FILE)) {
|
|
7580
7632
|
try {
|
|
7581
|
-
const { port } = JSON.parse(
|
|
7633
|
+
const { port } = JSON.parse(import_fs35.default.readFileSync(PID_FILE, "utf-8"));
|
|
7582
7634
|
pidPort = port;
|
|
7583
7635
|
} catch {
|
|
7584
|
-
console.error(
|
|
7636
|
+
console.error(import_chalk24.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7585
7637
|
}
|
|
7586
7638
|
}
|
|
7587
7639
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7592,7 +7644,7 @@ async function ensureDaemon() {
|
|
|
7592
7644
|
if (res.ok) return checkPort;
|
|
7593
7645
|
} catch {
|
|
7594
7646
|
}
|
|
7595
|
-
console.log(
|
|
7647
|
+
console.log(import_chalk24.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
7596
7648
|
const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
7597
7649
|
detached: true,
|
|
7598
7650
|
stdio: "ignore",
|
|
@@ -7609,7 +7661,7 @@ async function ensureDaemon() {
|
|
|
7609
7661
|
} catch {
|
|
7610
7662
|
}
|
|
7611
7663
|
}
|
|
7612
|
-
console.error(
|
|
7664
|
+
console.error(import_chalk24.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7613
7665
|
process.exit(1);
|
|
7614
7666
|
}
|
|
7615
7667
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7730,9 +7782,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7730
7782
|
];
|
|
7731
7783
|
}
|
|
7732
7784
|
function readApproversFromDisk() {
|
|
7733
|
-
const configPath =
|
|
7785
|
+
const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
|
|
7734
7786
|
try {
|
|
7735
|
-
const raw = JSON.parse(
|
|
7787
|
+
const raw = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
|
|
7736
7788
|
const settings = raw.settings ?? {};
|
|
7737
7789
|
return settings.approvers ?? {};
|
|
7738
7790
|
} catch {
|
|
@@ -7743,20 +7795,20 @@ function approverStatusLine() {
|
|
|
7743
7795
|
const a = readApproversFromDisk();
|
|
7744
7796
|
const fmt = (label, key) => {
|
|
7745
7797
|
const on = a[key] !== false;
|
|
7746
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
7798
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk24.default.green("\u2713") : import_chalk24.default.dim("\u2717")}`;
|
|
7747
7799
|
};
|
|
7748
7800
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7749
7801
|
}
|
|
7750
7802
|
function toggleApprover(channel) {
|
|
7751
|
-
const configPath =
|
|
7803
|
+
const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
|
|
7752
7804
|
try {
|
|
7753
|
-
const raw = JSON.parse(
|
|
7805
|
+
const raw = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
|
|
7754
7806
|
const settings = raw.settings ?? {};
|
|
7755
7807
|
const approvers = settings.approvers ?? {};
|
|
7756
7808
|
approvers[channel] = approvers[channel] === false;
|
|
7757
7809
|
settings.approvers = approvers;
|
|
7758
7810
|
raw.settings = settings;
|
|
7759
|
-
|
|
7811
|
+
import_fs35.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7760
7812
|
} catch (err2) {
|
|
7761
7813
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7762
7814
|
`);
|
|
@@ -7788,7 +7840,7 @@ async function startTail(options = {}) {
|
|
|
7788
7840
|
req2.end();
|
|
7789
7841
|
});
|
|
7790
7842
|
if (result.ok) {
|
|
7791
|
-
console.log(
|
|
7843
|
+
console.log(import_chalk24.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7792
7844
|
} else if (result.code === "ECONNREFUSED") {
|
|
7793
7845
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7794
7846
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7832,7 +7884,7 @@ async function startTail(options = {}) {
|
|
|
7832
7884
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7833
7885
|
if (channel) {
|
|
7834
7886
|
toggleApprover(channel);
|
|
7835
|
-
console.log(
|
|
7887
|
+
console.log(import_chalk24.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7836
7888
|
}
|
|
7837
7889
|
};
|
|
7838
7890
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7898,7 +7950,7 @@ async function startTail(options = {}) {
|
|
|
7898
7950
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7899
7951
|
)
|
|
7900
7952
|
);
|
|
7901
|
-
const decisionStamp = action === "always-allow" ?
|
|
7953
|
+
const decisionStamp = action === "always-allow" ? import_chalk24.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk24.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk24.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk24.default.yellow("\u21A9 REDIRECT AI") : import_chalk24.default.red("\u2717 DENIED");
|
|
7902
7954
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7903
7955
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7904
7956
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7926,8 +7978,8 @@ async function startTail(options = {}) {
|
|
|
7926
7978
|
}
|
|
7927
7979
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7928
7980
|
try {
|
|
7929
|
-
|
|
7930
|
-
|
|
7981
|
+
import_fs35.default.appendFileSync(
|
|
7982
|
+
import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log"),
|
|
7931
7983
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7932
7984
|
`
|
|
7933
7985
|
);
|
|
@@ -7949,7 +8001,7 @@ async function startTail(options = {}) {
|
|
|
7949
8001
|
);
|
|
7950
8002
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7951
8003
|
if (externalDecision) {
|
|
7952
|
-
const source = externalDecision === "allow" ?
|
|
8004
|
+
const source = externalDecision === "allow" ? import_chalk24.default.green("\u2713 ALLOWED") : import_chalk24.default.red("\u2717 DENIED");
|
|
7953
8005
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7954
8006
|
}
|
|
7955
8007
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -8008,16 +8060,16 @@ async function startTail(options = {}) {
|
|
|
8008
8060
|
}
|
|
8009
8061
|
} catch {
|
|
8010
8062
|
}
|
|
8011
|
-
console.log(
|
|
8012
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
8063
|
+
console.log(import_chalk24.default.cyan.bold(`
|
|
8064
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk24.default.dim(`\u2192 ${dashboardUrl}`));
|
|
8013
8065
|
if (canApprove) {
|
|
8014
|
-
console.log(
|
|
8015
|
-
console.log(
|
|
8066
|
+
console.log(import_chalk24.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
8067
|
+
console.log(import_chalk24.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
8016
8068
|
}
|
|
8017
8069
|
if (options.history) {
|
|
8018
|
-
console.log(
|
|
8070
|
+
console.log(import_chalk24.default.dim("Showing history + live events.\n"));
|
|
8019
8071
|
} else {
|
|
8020
|
-
console.log(
|
|
8072
|
+
console.log(import_chalk24.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
8021
8073
|
}
|
|
8022
8074
|
process.on("SIGINT", () => {
|
|
8023
8075
|
exitIdleMode();
|
|
@@ -8027,13 +8079,13 @@ async function startTail(options = {}) {
|
|
|
8027
8079
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
8028
8080
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
8029
8081
|
}
|
|
8030
|
-
console.log(
|
|
8082
|
+
console.log(import_chalk24.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
8031
8083
|
process.exit(0);
|
|
8032
8084
|
});
|
|
8033
8085
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
8034
8086
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
8035
8087
|
if (res.statusCode !== 200) {
|
|
8036
|
-
console.error(
|
|
8088
|
+
console.error(import_chalk24.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
8037
8089
|
process.exit(1);
|
|
8038
8090
|
}
|
|
8039
8091
|
if (canApprove) enterIdleMode();
|
|
@@ -8064,7 +8116,7 @@ async function startTail(options = {}) {
|
|
|
8064
8116
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
8065
8117
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
8066
8118
|
}
|
|
8067
|
-
console.log(
|
|
8119
|
+
console.log(import_chalk24.default.red("\n\u274C Daemon disconnected."));
|
|
8068
8120
|
process.exit(1);
|
|
8069
8121
|
});
|
|
8070
8122
|
});
|
|
@@ -8156,9 +8208,9 @@ async function startTail(options = {}) {
|
|
|
8156
8208
|
const hash = data.hash ?? "";
|
|
8157
8209
|
const summary = data.argsSummary ?? data.tool;
|
|
8158
8210
|
const fileCount = data.fileCount ?? 0;
|
|
8159
|
-
const files = fileCount > 0 ?
|
|
8211
|
+
const files = fileCount > 0 ? import_chalk24.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
8160
8212
|
process.stdout.write(
|
|
8161
|
-
`${
|
|
8213
|
+
`${import_chalk24.default.dim(time)} ${import_chalk24.default.cyan("\u{1F4F8} snapshot")} ${import_chalk24.default.dim(hash)} ${summary}${files}
|
|
8162
8214
|
`
|
|
8163
8215
|
);
|
|
8164
8216
|
return;
|
|
@@ -8175,26 +8227,26 @@ async function startTail(options = {}) {
|
|
|
8175
8227
|
}
|
|
8176
8228
|
req.on("error", (err2) => {
|
|
8177
8229
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
8178
|
-
console.error(
|
|
8230
|
+
console.error(import_chalk24.default.red(`
|
|
8179
8231
|
\u274C ${msg}`));
|
|
8180
8232
|
process.exit(1);
|
|
8181
8233
|
});
|
|
8182
8234
|
}
|
|
8183
|
-
var import_http2,
|
|
8235
|
+
var import_http2, import_chalk24, import_fs35, import_os31, import_path38, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
8184
8236
|
var init_tail = __esm({
|
|
8185
8237
|
"src/tui/tail.ts"() {
|
|
8186
8238
|
"use strict";
|
|
8187
8239
|
import_http2 = __toESM(require("http"));
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8240
|
+
import_chalk24 = __toESM(require("chalk"));
|
|
8241
|
+
import_fs35 = __toESM(require("fs"));
|
|
8242
|
+
import_os31 = __toESM(require("os"));
|
|
8243
|
+
import_path38 = __toESM(require("path"));
|
|
8192
8244
|
import_readline5 = __toESM(require("readline"));
|
|
8193
8245
|
import_child_process15 = require("child_process");
|
|
8194
8246
|
init_daemon2();
|
|
8195
8247
|
init_daemon();
|
|
8196
8248
|
init_core();
|
|
8197
|
-
PID_FILE =
|
|
8249
|
+
PID_FILE = import_path38.default.join(import_os31.default.homedir(), ".node9", "daemon.pid");
|
|
8198
8250
|
ICONS = {
|
|
8199
8251
|
bash: "\u{1F4BB}",
|
|
8200
8252
|
shell: "\u{1F4BB}",
|
|
@@ -8309,9 +8361,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
8309
8361
|
return ` (${m}m left)`;
|
|
8310
8362
|
}
|
|
8311
8363
|
function safeReadJson(filePath) {
|
|
8312
|
-
if (!
|
|
8364
|
+
if (!import_fs36.default.existsSync(filePath)) return null;
|
|
8313
8365
|
try {
|
|
8314
|
-
return JSON.parse(
|
|
8366
|
+
return JSON.parse(import_fs36.default.readFileSync(filePath, "utf-8"));
|
|
8315
8367
|
} catch {
|
|
8316
8368
|
return null;
|
|
8317
8369
|
}
|
|
@@ -8332,12 +8384,12 @@ function countHooksInFile(filePath) {
|
|
|
8332
8384
|
return Object.keys(cfg.hooks).length;
|
|
8333
8385
|
}
|
|
8334
8386
|
function countRulesInDir(rulesDir) {
|
|
8335
|
-
if (!
|
|
8387
|
+
if (!import_fs36.default.existsSync(rulesDir)) return 0;
|
|
8336
8388
|
let count = 0;
|
|
8337
8389
|
try {
|
|
8338
|
-
for (const entry of
|
|
8390
|
+
for (const entry of import_fs36.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
8339
8391
|
if (entry.isDirectory()) {
|
|
8340
|
-
count += countRulesInDir(
|
|
8392
|
+
count += countRulesInDir(import_path39.default.join(rulesDir, entry.name));
|
|
8341
8393
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8342
8394
|
count++;
|
|
8343
8395
|
}
|
|
@@ -8348,46 +8400,46 @@ function countRulesInDir(rulesDir) {
|
|
|
8348
8400
|
}
|
|
8349
8401
|
function isSamePath(a, b) {
|
|
8350
8402
|
try {
|
|
8351
|
-
return
|
|
8403
|
+
return import_path39.default.resolve(a) === import_path39.default.resolve(b);
|
|
8352
8404
|
} catch {
|
|
8353
8405
|
return false;
|
|
8354
8406
|
}
|
|
8355
8407
|
}
|
|
8356
8408
|
function countConfigs(cwd) {
|
|
8357
|
-
const homeDir2 =
|
|
8358
|
-
const claudeDir =
|
|
8409
|
+
const homeDir2 = import_os32.default.homedir();
|
|
8410
|
+
const claudeDir = import_path39.default.join(homeDir2, ".claude");
|
|
8359
8411
|
let claudeMdCount = 0;
|
|
8360
8412
|
let rulesCount = 0;
|
|
8361
8413
|
let hooksCount = 0;
|
|
8362
8414
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
8363
8415
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
8364
|
-
if (
|
|
8365
|
-
rulesCount += countRulesInDir(
|
|
8366
|
-
const userSettings =
|
|
8416
|
+
if (import_fs36.default.existsSync(import_path39.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8417
|
+
rulesCount += countRulesInDir(import_path39.default.join(claudeDir, "rules"));
|
|
8418
|
+
const userSettings = import_path39.default.join(claudeDir, "settings.json");
|
|
8367
8419
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
8368
8420
|
hooksCount += countHooksInFile(userSettings);
|
|
8369
|
-
const userClaudeJson =
|
|
8421
|
+
const userClaudeJson = import_path39.default.join(homeDir2, ".claude.json");
|
|
8370
8422
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
8371
8423
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
8372
8424
|
userMcpServers.delete(name);
|
|
8373
8425
|
}
|
|
8374
8426
|
if (cwd) {
|
|
8375
|
-
if (
|
|
8376
|
-
if (
|
|
8377
|
-
const projectClaudeDir =
|
|
8427
|
+
if (import_fs36.default.existsSync(import_path39.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
8428
|
+
if (import_fs36.default.existsSync(import_path39.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8429
|
+
const projectClaudeDir = import_path39.default.join(cwd, ".claude");
|
|
8378
8430
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
8379
8431
|
if (!overlapsUserScope) {
|
|
8380
|
-
if (
|
|
8381
|
-
rulesCount += countRulesInDir(
|
|
8382
|
-
const projSettings =
|
|
8432
|
+
if (import_fs36.default.existsSync(import_path39.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8433
|
+
rulesCount += countRulesInDir(import_path39.default.join(projectClaudeDir, "rules"));
|
|
8434
|
+
const projSettings = import_path39.default.join(projectClaudeDir, "settings.json");
|
|
8383
8435
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
8384
8436
|
hooksCount += countHooksInFile(projSettings);
|
|
8385
8437
|
}
|
|
8386
|
-
if (
|
|
8387
|
-
const localSettings =
|
|
8438
|
+
if (import_fs36.default.existsSync(import_path39.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8439
|
+
const localSettings = import_path39.default.join(projectClaudeDir, "settings.local.json");
|
|
8388
8440
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
8389
8441
|
hooksCount += countHooksInFile(localSettings);
|
|
8390
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8442
|
+
const mcpJsonServers = getMcpServerNames(import_path39.default.join(cwd, ".mcp.json"));
|
|
8391
8443
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
8392
8444
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
8393
8445
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -8420,12 +8472,12 @@ function readActiveShieldsHud() {
|
|
|
8420
8472
|
return shieldsCache.value;
|
|
8421
8473
|
}
|
|
8422
8474
|
try {
|
|
8423
|
-
const shieldsPath =
|
|
8424
|
-
if (!
|
|
8475
|
+
const shieldsPath = import_path39.default.join(import_os32.default.homedir(), ".node9", "shields.json");
|
|
8476
|
+
if (!import_fs36.default.existsSync(shieldsPath)) {
|
|
8425
8477
|
shieldsCache = { value: [], ts: now };
|
|
8426
8478
|
return [];
|
|
8427
8479
|
}
|
|
8428
|
-
const parsed = JSON.parse(
|
|
8480
|
+
const parsed = JSON.parse(import_fs36.default.readFileSync(shieldsPath, "utf-8"));
|
|
8429
8481
|
if (!Array.isArray(parsed.active)) {
|
|
8430
8482
|
shieldsCache = { value: [], ts: now };
|
|
8431
8483
|
return [];
|
|
@@ -8527,17 +8579,17 @@ function renderContextLine(stdin) {
|
|
|
8527
8579
|
async function main() {
|
|
8528
8580
|
try {
|
|
8529
8581
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8530
|
-
if (
|
|
8582
|
+
if (import_fs36.default.existsSync(import_path39.default.join(import_os32.default.homedir(), ".node9", "hud-debug"))) {
|
|
8531
8583
|
try {
|
|
8532
|
-
const logPath =
|
|
8584
|
+
const logPath = import_path39.default.join(import_os32.default.homedir(), ".node9", "hud-debug.log");
|
|
8533
8585
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8534
8586
|
let size = 0;
|
|
8535
8587
|
try {
|
|
8536
|
-
size =
|
|
8588
|
+
size = import_fs36.default.statSync(logPath).size;
|
|
8537
8589
|
} catch {
|
|
8538
8590
|
}
|
|
8539
8591
|
if (size < MAX_LOG_SIZE) {
|
|
8540
|
-
|
|
8592
|
+
import_fs36.default.appendFileSync(
|
|
8541
8593
|
logPath,
|
|
8542
8594
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8543
8595
|
);
|
|
@@ -8558,11 +8610,11 @@ async function main() {
|
|
|
8558
8610
|
try {
|
|
8559
8611
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8560
8612
|
for (const configPath of [
|
|
8561
|
-
|
|
8562
|
-
|
|
8613
|
+
import_path39.default.join(cwd, "node9.config.json"),
|
|
8614
|
+
import_path39.default.join(import_os32.default.homedir(), ".node9", "config.json")
|
|
8563
8615
|
]) {
|
|
8564
|
-
if (!
|
|
8565
|
-
const cfg = JSON.parse(
|
|
8616
|
+
if (!import_fs36.default.existsSync(configPath)) continue;
|
|
8617
|
+
const cfg = JSON.parse(import_fs36.default.readFileSync(configPath, "utf-8"));
|
|
8566
8618
|
const hud = cfg.settings?.hud;
|
|
8567
8619
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8568
8620
|
}
|
|
@@ -8580,13 +8632,13 @@ async function main() {
|
|
|
8580
8632
|
renderOffline();
|
|
8581
8633
|
}
|
|
8582
8634
|
}
|
|
8583
|
-
var
|
|
8635
|
+
var import_fs36, import_path39, import_os32, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
8584
8636
|
var init_hud = __esm({
|
|
8585
8637
|
"src/cli/hud.ts"() {
|
|
8586
8638
|
"use strict";
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8639
|
+
import_fs36 = __toESM(require("fs"));
|
|
8640
|
+
import_path39 = __toESM(require("path"));
|
|
8641
|
+
import_os32 = __toESM(require("os"));
|
|
8590
8642
|
import_http3 = __toESM(require("http"));
|
|
8591
8643
|
init_daemon();
|
|
8592
8644
|
RESET3 = "\x1B[0m";
|
|
@@ -9501,10 +9553,10 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
|
|
|
9501
9553
|
|
|
9502
9554
|
// src/cli.ts
|
|
9503
9555
|
init_daemon2();
|
|
9504
|
-
var
|
|
9505
|
-
var
|
|
9506
|
-
var
|
|
9507
|
-
var
|
|
9556
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
9557
|
+
var import_fs37 = __toESM(require("fs"));
|
|
9558
|
+
var import_path40 = __toESM(require("path"));
|
|
9559
|
+
var import_os33 = __toESM(require("os"));
|
|
9508
9560
|
var import_prompts2 = require("@inquirer/prompts");
|
|
9509
9561
|
|
|
9510
9562
|
// src/utils/duration.ts
|
|
@@ -9729,10 +9781,10 @@ async function autoStartDaemonAndWait() {
|
|
|
9729
9781
|
|
|
9730
9782
|
// src/cli/commands/check.ts
|
|
9731
9783
|
var import_chalk5 = __toESM(require("chalk"));
|
|
9732
|
-
var
|
|
9784
|
+
var import_fs23 = __toESM(require("fs"));
|
|
9733
9785
|
var import_child_process10 = require("child_process");
|
|
9734
|
-
var
|
|
9735
|
-
var
|
|
9786
|
+
var import_path25 = __toESM(require("path"));
|
|
9787
|
+
var import_os19 = __toESM(require("os"));
|
|
9736
9788
|
init_orchestrator();
|
|
9737
9789
|
init_daemon();
|
|
9738
9790
|
init_config();
|
|
@@ -10087,6 +10139,187 @@ function applyUndo(hash, cwd) {
|
|
|
10087
10139
|
}
|
|
10088
10140
|
}
|
|
10089
10141
|
|
|
10142
|
+
// src/skill-pin.ts
|
|
10143
|
+
var import_fs22 = __toESM(require("fs"));
|
|
10144
|
+
var import_path24 = __toESM(require("path"));
|
|
10145
|
+
var import_os18 = __toESM(require("os"));
|
|
10146
|
+
var import_crypto9 = __toESM(require("crypto"));
|
|
10147
|
+
function getPinsFilePath() {
|
|
10148
|
+
return import_path24.default.join(import_os18.default.homedir(), ".node9", "skill-pins.json");
|
|
10149
|
+
}
|
|
10150
|
+
var MAX_FILES = 5e3;
|
|
10151
|
+
var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
10152
|
+
function sha256Bytes(buf) {
|
|
10153
|
+
return import_crypto9.default.createHash("sha256").update(buf).digest("hex");
|
|
10154
|
+
}
|
|
10155
|
+
function walkDir(root) {
|
|
10156
|
+
const out = [];
|
|
10157
|
+
let totalBytes = 0;
|
|
10158
|
+
const visit = (dir, relDir) => {
|
|
10159
|
+
if (out.length >= MAX_FILES) return;
|
|
10160
|
+
let entries;
|
|
10161
|
+
try {
|
|
10162
|
+
entries = import_fs22.default.readdirSync(dir, { withFileTypes: true });
|
|
10163
|
+
} catch {
|
|
10164
|
+
return;
|
|
10165
|
+
}
|
|
10166
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
10167
|
+
for (const entry of entries) {
|
|
10168
|
+
if (out.length >= MAX_FILES) return;
|
|
10169
|
+
const full = import_path24.default.join(dir, entry.name);
|
|
10170
|
+
const rel = relDir ? import_path24.default.posix.join(relDir, entry.name) : entry.name;
|
|
10171
|
+
let lst;
|
|
10172
|
+
try {
|
|
10173
|
+
lst = import_fs22.default.lstatSync(full);
|
|
10174
|
+
} catch {
|
|
10175
|
+
continue;
|
|
10176
|
+
}
|
|
10177
|
+
if (lst.isSymbolicLink()) continue;
|
|
10178
|
+
if (lst.isDirectory()) {
|
|
10179
|
+
visit(full, rel);
|
|
10180
|
+
continue;
|
|
10181
|
+
}
|
|
10182
|
+
if (!lst.isFile()) continue;
|
|
10183
|
+
if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
|
|
10184
|
+
try {
|
|
10185
|
+
const buf = import_fs22.default.readFileSync(full);
|
|
10186
|
+
totalBytes += buf.length;
|
|
10187
|
+
out.push({ rel, hash: sha256Bytes(buf) });
|
|
10188
|
+
} catch {
|
|
10189
|
+
}
|
|
10190
|
+
}
|
|
10191
|
+
};
|
|
10192
|
+
visit(root, "");
|
|
10193
|
+
out.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
10194
|
+
return out.map((e) => `${e.rel}\0${e.hash}`);
|
|
10195
|
+
}
|
|
10196
|
+
function hashSkillRoot(absPath) {
|
|
10197
|
+
let lst;
|
|
10198
|
+
try {
|
|
10199
|
+
lst = import_fs22.default.lstatSync(absPath);
|
|
10200
|
+
} catch {
|
|
10201
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10202
|
+
}
|
|
10203
|
+
if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
|
|
10204
|
+
if (lst.isFile()) {
|
|
10205
|
+
try {
|
|
10206
|
+
return { exists: true, contentHash: sha256Bytes(import_fs22.default.readFileSync(absPath)), fileCount: 1 };
|
|
10207
|
+
} catch {
|
|
10208
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10209
|
+
}
|
|
10210
|
+
}
|
|
10211
|
+
if (lst.isDirectory()) {
|
|
10212
|
+
const entries = walkDir(absPath);
|
|
10213
|
+
const contentHash = import_crypto9.default.createHash("sha256").update(entries.join("\n")).digest("hex");
|
|
10214
|
+
return { exists: true, contentHash, fileCount: entries.length };
|
|
10215
|
+
}
|
|
10216
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10217
|
+
}
|
|
10218
|
+
function getRootKey(absPath) {
|
|
10219
|
+
return import_crypto9.default.createHash("sha256").update(absPath).digest("hex").slice(0, 16);
|
|
10220
|
+
}
|
|
10221
|
+
function readSkillPinsSafe() {
|
|
10222
|
+
const filePath = getPinsFilePath();
|
|
10223
|
+
try {
|
|
10224
|
+
const raw = import_fs22.default.readFileSync(filePath, "utf-8");
|
|
10225
|
+
if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10226
|
+
const parsed = JSON.parse(raw);
|
|
10227
|
+
if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
|
|
10228
|
+
return { ok: false, reason: "corrupt", detail: "invalid structure: missing roots object" };
|
|
10229
|
+
}
|
|
10230
|
+
return { ok: true, pins: { roots: parsed.roots } };
|
|
10231
|
+
} catch (err2) {
|
|
10232
|
+
if (err2.code === "ENOENT") return { ok: false, reason: "missing" };
|
|
10233
|
+
return { ok: false, reason: "corrupt", detail: String(err2) };
|
|
10234
|
+
}
|
|
10235
|
+
}
|
|
10236
|
+
function readSkillPins() {
|
|
10237
|
+
const result = readSkillPinsSafe();
|
|
10238
|
+
if (result.ok) return result.pins;
|
|
10239
|
+
if (result.reason === "missing") return { roots: {} };
|
|
10240
|
+
throw new Error(`[node9] skill pin file is corrupt: ${result.detail}`);
|
|
10241
|
+
}
|
|
10242
|
+
function writeSkillPins(data) {
|
|
10243
|
+
const filePath = getPinsFilePath();
|
|
10244
|
+
import_fs22.default.mkdirSync(import_path24.default.dirname(filePath), { recursive: true });
|
|
10245
|
+
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
10246
|
+
import_fs22.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10247
|
+
import_fs22.default.renameSync(tmp, filePath);
|
|
10248
|
+
}
|
|
10249
|
+
function removePin(rootKey) {
|
|
10250
|
+
const pins = readSkillPins();
|
|
10251
|
+
delete pins.roots[rootKey];
|
|
10252
|
+
writeSkillPins(pins);
|
|
10253
|
+
}
|
|
10254
|
+
function clearAllPins() {
|
|
10255
|
+
writeSkillPins({ roots: {} });
|
|
10256
|
+
}
|
|
10257
|
+
function verifyAndPinRoots(roots) {
|
|
10258
|
+
const pinsRead = readSkillPinsSafe();
|
|
10259
|
+
if (!pinsRead.ok && pinsRead.reason === "corrupt") {
|
|
10260
|
+
return { kind: "corrupt", detail: pinsRead.detail };
|
|
10261
|
+
}
|
|
10262
|
+
const pins = pinsRead.ok ? pinsRead.pins : { roots: {} };
|
|
10263
|
+
let mutated = false;
|
|
10264
|
+
for (const rootPath of new Set(roots)) {
|
|
10265
|
+
const rootKey = getRootKey(rootPath);
|
|
10266
|
+
const current = hashSkillRoot(rootPath);
|
|
10267
|
+
const existing = pins.roots[rootKey];
|
|
10268
|
+
if (!existing) {
|
|
10269
|
+
pins.roots[rootKey] = {
|
|
10270
|
+
rootPath,
|
|
10271
|
+
exists: current.exists,
|
|
10272
|
+
contentHash: current.contentHash,
|
|
10273
|
+
fileCount: current.fileCount,
|
|
10274
|
+
pinnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10275
|
+
};
|
|
10276
|
+
mutated = true;
|
|
10277
|
+
continue;
|
|
10278
|
+
}
|
|
10279
|
+
if (existing.exists !== current.exists || existing.contentHash !== current.contentHash) {
|
|
10280
|
+
let summary;
|
|
10281
|
+
if (existing.exists && !current.exists) summary = `vanished: ${rootPath}`;
|
|
10282
|
+
else if (!existing.exists && current.exists) summary = `appeared: ${rootPath}`;
|
|
10283
|
+
else summary = `changed: ${rootPath}`;
|
|
10284
|
+
return { kind: "drift", changedRootKey: rootKey, changedRootPath: rootPath, summary };
|
|
10285
|
+
}
|
|
10286
|
+
}
|
|
10287
|
+
if (mutated) writeSkillPins(pins);
|
|
10288
|
+
return { kind: "verified" };
|
|
10289
|
+
}
|
|
10290
|
+
function defaultSkillRoots(_cwd) {
|
|
10291
|
+
const marketplaces = import_path24.default.join(import_os18.default.homedir(), ".claude", "plugins", "marketplaces");
|
|
10292
|
+
const roots = [];
|
|
10293
|
+
let registries;
|
|
10294
|
+
try {
|
|
10295
|
+
registries = import_fs22.default.readdirSync(marketplaces, { withFileTypes: true });
|
|
10296
|
+
} catch {
|
|
10297
|
+
return [];
|
|
10298
|
+
}
|
|
10299
|
+
for (const registry of registries) {
|
|
10300
|
+
if (!registry.isDirectory()) continue;
|
|
10301
|
+
const pluginsDir = import_path24.default.join(marketplaces, registry.name, "plugins");
|
|
10302
|
+
let plugins;
|
|
10303
|
+
try {
|
|
10304
|
+
plugins = import_fs22.default.readdirSync(pluginsDir, { withFileTypes: true });
|
|
10305
|
+
} catch {
|
|
10306
|
+
continue;
|
|
10307
|
+
}
|
|
10308
|
+
for (const plugin of plugins) {
|
|
10309
|
+
if (!plugin.isDirectory()) continue;
|
|
10310
|
+
roots.push(import_path24.default.join(pluginsDir, plugin.name));
|
|
10311
|
+
}
|
|
10312
|
+
}
|
|
10313
|
+
return roots;
|
|
10314
|
+
}
|
|
10315
|
+
function resolveUserSkillRoot(entry, cwd) {
|
|
10316
|
+
if (!entry) return null;
|
|
10317
|
+
if (entry.startsWith("~/") || entry === "~") return import_path24.default.join(import_os18.default.homedir(), entry.slice(1));
|
|
10318
|
+
if (import_path24.default.isAbsolute(entry)) return entry;
|
|
10319
|
+
if (!cwd || !import_path24.default.isAbsolute(cwd)) return null;
|
|
10320
|
+
return import_path24.default.join(cwd, entry);
|
|
10321
|
+
}
|
|
10322
|
+
|
|
10090
10323
|
// src/cli/commands/check.ts
|
|
10091
10324
|
function sanitize2(value) {
|
|
10092
10325
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
@@ -10102,9 +10335,9 @@ function registerCheckCommand(program2) {
|
|
|
10102
10335
|
} catch (err2) {
|
|
10103
10336
|
const tempConfig = getConfig();
|
|
10104
10337
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
10105
|
-
const logPath =
|
|
10338
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
10106
10339
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10107
|
-
|
|
10340
|
+
import_fs23.default.appendFileSync(
|
|
10108
10341
|
logPath,
|
|
10109
10342
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
10110
10343
|
RAW: ${raw}
|
|
@@ -10117,11 +10350,11 @@ RAW: ${raw}
|
|
|
10117
10350
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
10118
10351
|
try {
|
|
10119
10352
|
const scriptPath = process.argv[1];
|
|
10120
|
-
if (typeof scriptPath !== "string" || !
|
|
10353
|
+
if (typeof scriptPath !== "string" || !import_path25.default.isAbsolute(scriptPath))
|
|
10121
10354
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
10122
|
-
const resolvedScript =
|
|
10123
|
-
const packageDist =
|
|
10124
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
10355
|
+
const resolvedScript = import_fs23.default.realpathSync(scriptPath);
|
|
10356
|
+
const packageDist = import_fs23.default.realpathSync(import_path25.default.resolve(__dirname, "../.."));
|
|
10357
|
+
if (!resolvedScript.startsWith(packageDist + import_path25.default.sep) && resolvedScript !== packageDist)
|
|
10125
10358
|
throw new Error(
|
|
10126
10359
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
10127
10360
|
);
|
|
@@ -10143,10 +10376,10 @@ RAW: ${raw}
|
|
|
10143
10376
|
});
|
|
10144
10377
|
d.unref();
|
|
10145
10378
|
} catch (spawnErr) {
|
|
10146
|
-
const logPath =
|
|
10379
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
10147
10380
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
10148
10381
|
try {
|
|
10149
|
-
|
|
10382
|
+
import_fs23.default.appendFileSync(
|
|
10150
10383
|
logPath,
|
|
10151
10384
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
10152
10385
|
`
|
|
@@ -10156,10 +10389,10 @@ RAW: ${raw}
|
|
|
10156
10389
|
}
|
|
10157
10390
|
}
|
|
10158
10391
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
10159
|
-
const logPath =
|
|
10160
|
-
if (!
|
|
10161
|
-
|
|
10162
|
-
|
|
10392
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
10393
|
+
if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
|
|
10394
|
+
import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
|
|
10395
|
+
import_fs23.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
10163
10396
|
`);
|
|
10164
10397
|
}
|
|
10165
10398
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -10172,8 +10405,8 @@ RAW: ${raw}
|
|
|
10172
10405
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
10173
10406
|
let ttyFd = null;
|
|
10174
10407
|
try {
|
|
10175
|
-
ttyFd =
|
|
10176
|
-
const writeTty = (line) =>
|
|
10408
|
+
ttyFd = import_fs23.default.openSync("/dev/tty", "w");
|
|
10409
|
+
const writeTty = (line) => import_fs23.default.writeSync(ttyFd, line + "\n");
|
|
10177
10410
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
10178
10411
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
10179
10412
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -10192,7 +10425,7 @@ RAW: ${raw}
|
|
|
10192
10425
|
} finally {
|
|
10193
10426
|
if (ttyFd !== null)
|
|
10194
10427
|
try {
|
|
10195
|
-
|
|
10428
|
+
import_fs23.default.closeSync(ttyFd);
|
|
10196
10429
|
} catch {
|
|
10197
10430
|
}
|
|
10198
10431
|
}
|
|
@@ -10221,10 +10454,131 @@ RAW: ${raw}
|
|
|
10221
10454
|
return;
|
|
10222
10455
|
}
|
|
10223
10456
|
const meta = { agent, mcpServer };
|
|
10457
|
+
const skillPinCfg = config.policy.skillPinning;
|
|
10458
|
+
const rawSessionId = typeof payload.session_id === "string" ? payload.session_id : "";
|
|
10459
|
+
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
10460
|
+
if (skillPinCfg.enabled && safeSessionId) {
|
|
10461
|
+
try {
|
|
10462
|
+
const sessionsDir = import_path25.default.join(import_os19.default.homedir(), ".node9", "skill-sessions");
|
|
10463
|
+
const flagPath = import_path25.default.join(sessionsDir, `${safeSessionId}.json`);
|
|
10464
|
+
let flag = null;
|
|
10465
|
+
try {
|
|
10466
|
+
flag = JSON.parse(import_fs23.default.readFileSync(flagPath, "utf-8"));
|
|
10467
|
+
} catch {
|
|
10468
|
+
}
|
|
10469
|
+
const writeFlag = (data2) => {
|
|
10470
|
+
try {
|
|
10471
|
+
import_fs23.default.mkdirSync(sessionsDir, { recursive: true });
|
|
10472
|
+
import_fs23.default.writeFileSync(
|
|
10473
|
+
flagPath,
|
|
10474
|
+
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
10475
|
+
{ mode: 384 }
|
|
10476
|
+
);
|
|
10477
|
+
} catch {
|
|
10478
|
+
}
|
|
10479
|
+
};
|
|
10480
|
+
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
10481
|
+
let ttyFd = null;
|
|
10482
|
+
try {
|
|
10483
|
+
ttyFd = import_fs23.default.openSync("/dev/tty", "w");
|
|
10484
|
+
const w = (line) => import_fs23.default.writeSync(ttyFd, line + "\n");
|
|
10485
|
+
w(import_chalk5.default.yellow(`
|
|
10486
|
+
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
10487
|
+
w(import_chalk5.default.gray(` ${detail}`));
|
|
10488
|
+
w(
|
|
10489
|
+
import_chalk5.default.gray(
|
|
10490
|
+
` If you updated a plugin, acknowledge the change to clear this warning.`
|
|
10491
|
+
)
|
|
10492
|
+
);
|
|
10493
|
+
if (recoveryCmd) w(import_chalk5.default.green(` \u{1F4A1} Run: ${recoveryCmd}`));
|
|
10494
|
+
w("");
|
|
10495
|
+
} catch {
|
|
10496
|
+
} finally {
|
|
10497
|
+
if (ttyFd !== null)
|
|
10498
|
+
try {
|
|
10499
|
+
import_fs23.default.closeSync(ttyFd);
|
|
10500
|
+
} catch {
|
|
10501
|
+
}
|
|
10502
|
+
}
|
|
10503
|
+
};
|
|
10504
|
+
if (flag && flag.state === "quarantined" && skillPinCfg.mode === "block") {
|
|
10505
|
+
sendBlock(
|
|
10506
|
+
`Node9: session quarantined \u2014 installed skill changed. Open a separate terminal and run: node9 skill pin list (to see what changed) then: node9 skill pin update <rootKey> (to acknowledge). If you updated a plugin intentionally, this is expected.`,
|
|
10507
|
+
{
|
|
10508
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10509
|
+
recoveryCommand: "node9 skill pin list"
|
|
10510
|
+
}
|
|
10511
|
+
);
|
|
10512
|
+
return;
|
|
10513
|
+
}
|
|
10514
|
+
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
10515
|
+
const absoluteCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10516
|
+
const extraRoots = skillPinCfg.roots;
|
|
10517
|
+
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
10518
|
+
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
10519
|
+
const result2 = verifyAndPinRoots(roots);
|
|
10520
|
+
if (result2.kind === "corrupt") {
|
|
10521
|
+
if (skillPinCfg.mode === "block") {
|
|
10522
|
+
writeFlag({
|
|
10523
|
+
state: "quarantined",
|
|
10524
|
+
detail: `pin file corrupt: ${result2.detail}`
|
|
10525
|
+
});
|
|
10526
|
+
sendBlock("Node9: skill pin file is corrupt \u2014 fail-closed.", {
|
|
10527
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10528
|
+
recoveryCommand: "node9 skill pin reset"
|
|
10529
|
+
});
|
|
10530
|
+
return;
|
|
10531
|
+
}
|
|
10532
|
+
writeFlag({ state: "warned", detail: `pin file corrupt: ${result2.detail}` });
|
|
10533
|
+
sendSkillWarn(
|
|
10534
|
+
`Skill pin file is corrupt: ${result2.detail}`,
|
|
10535
|
+
"node9 skill pin reset"
|
|
10536
|
+
);
|
|
10537
|
+
} else if (result2.kind === "drift") {
|
|
10538
|
+
if (skillPinCfg.mode === "block") {
|
|
10539
|
+
writeFlag({ state: "quarantined", detail: result2.summary });
|
|
10540
|
+
sendBlock(
|
|
10541
|
+
`Node9: installed skill changed \u2014 ${result2.summary}. If you updated a plugin, open a separate terminal and run: node9 skill pin update ${result2.changedRootKey}`,
|
|
10542
|
+
{
|
|
10543
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10544
|
+
recoveryCommand: `node9 skill pin update ${result2.changedRootKey}`
|
|
10545
|
+
}
|
|
10546
|
+
);
|
|
10547
|
+
return;
|
|
10548
|
+
}
|
|
10549
|
+
writeFlag({ state: "warned", detail: result2.summary });
|
|
10550
|
+
sendSkillWarn(result2.summary, `node9 skill pin update ${result2.changedRootKey}`);
|
|
10551
|
+
} else {
|
|
10552
|
+
writeFlag({ state: "verified" });
|
|
10553
|
+
}
|
|
10554
|
+
try {
|
|
10555
|
+
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
10556
|
+
for (const name of import_fs23.default.readdirSync(sessionsDir)) {
|
|
10557
|
+
const p = import_path25.default.join(sessionsDir, name);
|
|
10558
|
+
try {
|
|
10559
|
+
if (import_fs23.default.statSync(p).mtimeMs < cutoff) import_fs23.default.unlinkSync(p);
|
|
10560
|
+
} catch {
|
|
10561
|
+
}
|
|
10562
|
+
}
|
|
10563
|
+
} catch {
|
|
10564
|
+
}
|
|
10565
|
+
}
|
|
10566
|
+
} catch (err2) {
|
|
10567
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
10568
|
+
try {
|
|
10569
|
+
const dbg = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
10570
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10571
|
+
import_fs23.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
10572
|
+
`);
|
|
10573
|
+
} catch {
|
|
10574
|
+
}
|
|
10575
|
+
}
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10224
10578
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
10225
10579
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
10226
10580
|
}
|
|
10227
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10581
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10228
10582
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
10229
10583
|
cwd: safeCwdForAuth
|
|
10230
10584
|
});
|
|
@@ -10236,12 +10590,12 @@ RAW: ${raw}
|
|
|
10236
10590
|
}
|
|
10237
10591
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
10238
10592
|
try {
|
|
10239
|
-
const tty =
|
|
10240
|
-
|
|
10593
|
+
const tty = import_fs23.default.openSync("/dev/tty", "w");
|
|
10594
|
+
import_fs23.default.writeSync(
|
|
10241
10595
|
tty,
|
|
10242
10596
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
10243
10597
|
);
|
|
10244
|
-
|
|
10598
|
+
import_fs23.default.closeSync(tty);
|
|
10245
10599
|
} catch {
|
|
10246
10600
|
}
|
|
10247
10601
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -10268,9 +10622,9 @@ RAW: ${raw}
|
|
|
10268
10622
|
});
|
|
10269
10623
|
} catch (err2) {
|
|
10270
10624
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10271
|
-
const logPath =
|
|
10625
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
|
|
10272
10626
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10273
|
-
|
|
10627
|
+
import_fs23.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
10274
10628
|
`);
|
|
10275
10629
|
}
|
|
10276
10630
|
process.exit(0);
|
|
@@ -10304,9 +10658,9 @@ RAW: ${raw}
|
|
|
10304
10658
|
}
|
|
10305
10659
|
|
|
10306
10660
|
// src/cli/commands/log.ts
|
|
10307
|
-
var
|
|
10308
|
-
var
|
|
10309
|
-
var
|
|
10661
|
+
var import_fs24 = __toESM(require("fs"));
|
|
10662
|
+
var import_path26 = __toESM(require("path"));
|
|
10663
|
+
var import_os20 = __toESM(require("os"));
|
|
10310
10664
|
init_audit();
|
|
10311
10665
|
init_config();
|
|
10312
10666
|
init_policy();
|
|
@@ -10382,10 +10736,10 @@ function registerLogCommand(program2) {
|
|
|
10382
10736
|
decision: "allowed",
|
|
10383
10737
|
source: "post-hook"
|
|
10384
10738
|
};
|
|
10385
|
-
const logPath =
|
|
10386
|
-
if (!
|
|
10387
|
-
|
|
10388
|
-
|
|
10739
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
|
|
10740
|
+
if (!import_fs24.default.existsSync(import_path26.default.dirname(logPath)))
|
|
10741
|
+
import_fs24.default.mkdirSync(import_path26.default.dirname(logPath), { recursive: true });
|
|
10742
|
+
import_fs24.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
10389
10743
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
10390
10744
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
10391
10745
|
if (command) {
|
|
@@ -10418,7 +10772,7 @@ function registerLogCommand(program2) {
|
|
|
10418
10772
|
}
|
|
10419
10773
|
}
|
|
10420
10774
|
}
|
|
10421
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
10775
|
+
const safeCwd = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10422
10776
|
const config = getConfig(safeCwd);
|
|
10423
10777
|
if (shouldSnapshot(tool, {}, config)) {
|
|
10424
10778
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -10427,9 +10781,9 @@ function registerLogCommand(program2) {
|
|
|
10427
10781
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10428
10782
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
10429
10783
|
`);
|
|
10430
|
-
const debugPath =
|
|
10784
|
+
const debugPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10431
10785
|
try {
|
|
10432
|
-
|
|
10786
|
+
import_fs24.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
10433
10787
|
`);
|
|
10434
10788
|
} catch {
|
|
10435
10789
|
}
|
|
@@ -10829,14 +11183,14 @@ function registerConfigShowCommand(program2) {
|
|
|
10829
11183
|
|
|
10830
11184
|
// src/cli/commands/doctor.ts
|
|
10831
11185
|
var import_chalk7 = __toESM(require("chalk"));
|
|
10832
|
-
var
|
|
10833
|
-
var
|
|
10834
|
-
var
|
|
11186
|
+
var import_fs25 = __toESM(require("fs"));
|
|
11187
|
+
var import_path27 = __toESM(require("path"));
|
|
11188
|
+
var import_os21 = __toESM(require("os"));
|
|
10835
11189
|
var import_child_process11 = require("child_process");
|
|
10836
11190
|
init_daemon();
|
|
10837
11191
|
function registerDoctorCommand(program2, version2) {
|
|
10838
11192
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
10839
|
-
const homeDir2 =
|
|
11193
|
+
const homeDir2 = import_os21.default.homedir();
|
|
10840
11194
|
let failures = 0;
|
|
10841
11195
|
function pass(msg) {
|
|
10842
11196
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -10885,10 +11239,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10885
11239
|
);
|
|
10886
11240
|
}
|
|
10887
11241
|
section("Configuration");
|
|
10888
|
-
const globalConfigPath =
|
|
10889
|
-
if (
|
|
11242
|
+
const globalConfigPath = import_path27.default.join(homeDir2, ".node9", "config.json");
|
|
11243
|
+
if (import_fs25.default.existsSync(globalConfigPath)) {
|
|
10890
11244
|
try {
|
|
10891
|
-
JSON.parse(
|
|
11245
|
+
JSON.parse(import_fs25.default.readFileSync(globalConfigPath, "utf-8"));
|
|
10892
11246
|
pass("~/.node9/config.json found and valid");
|
|
10893
11247
|
} catch {
|
|
10894
11248
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -10896,10 +11250,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10896
11250
|
} else {
|
|
10897
11251
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
10898
11252
|
}
|
|
10899
|
-
const projectConfigPath =
|
|
10900
|
-
if (
|
|
11253
|
+
const projectConfigPath = import_path27.default.join(process.cwd(), "node9.config.json");
|
|
11254
|
+
if (import_fs25.default.existsSync(projectConfigPath)) {
|
|
10901
11255
|
try {
|
|
10902
|
-
JSON.parse(
|
|
11256
|
+
JSON.parse(import_fs25.default.readFileSync(projectConfigPath, "utf-8"));
|
|
10903
11257
|
pass("node9.config.json found and valid (project)");
|
|
10904
11258
|
} catch {
|
|
10905
11259
|
fail(
|
|
@@ -10908,8 +11262,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10908
11262
|
);
|
|
10909
11263
|
}
|
|
10910
11264
|
}
|
|
10911
|
-
const credsPath =
|
|
10912
|
-
if (
|
|
11265
|
+
const credsPath = import_path27.default.join(homeDir2, ".node9", "credentials.json");
|
|
11266
|
+
if (import_fs25.default.existsSync(credsPath)) {
|
|
10913
11267
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
10914
11268
|
} else {
|
|
10915
11269
|
warn(
|
|
@@ -10918,10 +11272,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10918
11272
|
);
|
|
10919
11273
|
}
|
|
10920
11274
|
section("Agent Hooks");
|
|
10921
|
-
const claudeSettingsPath =
|
|
10922
|
-
if (
|
|
11275
|
+
const claudeSettingsPath = import_path27.default.join(homeDir2, ".claude", "settings.json");
|
|
11276
|
+
if (import_fs25.default.existsSync(claudeSettingsPath)) {
|
|
10923
11277
|
try {
|
|
10924
|
-
const cs = JSON.parse(
|
|
11278
|
+
const cs = JSON.parse(import_fs25.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
10925
11279
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
10926
11280
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10927
11281
|
);
|
|
@@ -10937,10 +11291,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10937
11291
|
} else {
|
|
10938
11292
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
10939
11293
|
}
|
|
10940
|
-
const geminiSettingsPath =
|
|
10941
|
-
if (
|
|
11294
|
+
const geminiSettingsPath = import_path27.default.join(homeDir2, ".gemini", "settings.json");
|
|
11295
|
+
if (import_fs25.default.existsSync(geminiSettingsPath)) {
|
|
10942
11296
|
try {
|
|
10943
|
-
const gs = JSON.parse(
|
|
11297
|
+
const gs = JSON.parse(import_fs25.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
10944
11298
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
10945
11299
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10946
11300
|
);
|
|
@@ -10956,10 +11310,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10956
11310
|
} else {
|
|
10957
11311
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
10958
11312
|
}
|
|
10959
|
-
const cursorHooksPath =
|
|
10960
|
-
if (
|
|
11313
|
+
const cursorHooksPath = import_path27.default.join(homeDir2, ".cursor", "hooks.json");
|
|
11314
|
+
if (import_fs25.default.existsSync(cursorHooksPath)) {
|
|
10961
11315
|
try {
|
|
10962
|
-
const cur = JSON.parse(
|
|
11316
|
+
const cur = JSON.parse(import_fs25.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
10963
11317
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
10964
11318
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
10965
11319
|
);
|
|
@@ -10997,9 +11351,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10997
11351
|
|
|
10998
11352
|
// src/cli/commands/audit.ts
|
|
10999
11353
|
var import_chalk8 = __toESM(require("chalk"));
|
|
11000
|
-
var
|
|
11001
|
-
var
|
|
11002
|
-
var
|
|
11354
|
+
var import_fs26 = __toESM(require("fs"));
|
|
11355
|
+
var import_path28 = __toESM(require("path"));
|
|
11356
|
+
var import_os22 = __toESM(require("os"));
|
|
11003
11357
|
function formatRelativeTime(timestamp) {
|
|
11004
11358
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
11005
11359
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -11012,14 +11366,14 @@ function formatRelativeTime(timestamp) {
|
|
|
11012
11366
|
}
|
|
11013
11367
|
function registerAuditCommand(program2) {
|
|
11014
11368
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
11015
|
-
const logPath =
|
|
11016
|
-
if (!
|
|
11369
|
+
const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
|
|
11370
|
+
if (!import_fs26.default.existsSync(logPath)) {
|
|
11017
11371
|
console.log(
|
|
11018
11372
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
11019
11373
|
);
|
|
11020
11374
|
return;
|
|
11021
11375
|
}
|
|
11022
|
-
const raw =
|
|
11376
|
+
const raw = import_fs26.default.readFileSync(logPath, "utf-8");
|
|
11023
11377
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
11024
11378
|
let entries = lines.flatMap((line) => {
|
|
11025
11379
|
try {
|
|
@@ -11073,9 +11427,9 @@ function registerAuditCommand(program2) {
|
|
|
11073
11427
|
|
|
11074
11428
|
// src/cli/commands/report.ts
|
|
11075
11429
|
var import_chalk9 = __toESM(require("chalk"));
|
|
11076
|
-
var
|
|
11077
|
-
var
|
|
11078
|
-
var
|
|
11430
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11431
|
+
var import_path29 = __toESM(require("path"));
|
|
11432
|
+
var import_os23 = __toESM(require("os"));
|
|
11079
11433
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
11080
11434
|
function buildTestTimestamps(allEntries) {
|
|
11081
11435
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -11122,8 +11476,8 @@ function getDateRange(period) {
|
|
|
11122
11476
|
}
|
|
11123
11477
|
}
|
|
11124
11478
|
function parseAuditLog(logPath) {
|
|
11125
|
-
if (!
|
|
11126
|
-
const raw =
|
|
11479
|
+
if (!import_fs27.default.existsSync(logPath)) return [];
|
|
11480
|
+
const raw = import_fs27.default.readFileSync(logPath, "utf-8");
|
|
11127
11481
|
return raw.split("\n").flatMap((line) => {
|
|
11128
11482
|
if (!line.trim()) return [];
|
|
11129
11483
|
try {
|
|
@@ -11192,11 +11546,11 @@ function loadClaudeCost(start, end) {
|
|
|
11192
11546
|
inputTokens: 0,
|
|
11193
11547
|
cacheReadTokens: 0
|
|
11194
11548
|
};
|
|
11195
|
-
const projectsDir =
|
|
11196
|
-
if (!
|
|
11549
|
+
const projectsDir = import_path29.default.join(import_os23.default.homedir(), ".claude", "projects");
|
|
11550
|
+
if (!import_fs27.default.existsSync(projectsDir)) return empty;
|
|
11197
11551
|
let dirs;
|
|
11198
11552
|
try {
|
|
11199
|
-
dirs =
|
|
11553
|
+
dirs = import_fs27.default.readdirSync(projectsDir);
|
|
11200
11554
|
} catch {
|
|
11201
11555
|
return empty;
|
|
11202
11556
|
}
|
|
@@ -11206,18 +11560,18 @@ function loadClaudeCost(start, end) {
|
|
|
11206
11560
|
const byDay = /* @__PURE__ */ new Map();
|
|
11207
11561
|
const byModel = /* @__PURE__ */ new Map();
|
|
11208
11562
|
for (const proj of dirs) {
|
|
11209
|
-
const projPath =
|
|
11563
|
+
const projPath = import_path29.default.join(projectsDir, proj);
|
|
11210
11564
|
let files;
|
|
11211
11565
|
try {
|
|
11212
|
-
const stat =
|
|
11566
|
+
const stat = import_fs27.default.statSync(projPath);
|
|
11213
11567
|
if (!stat.isDirectory()) continue;
|
|
11214
|
-
files =
|
|
11568
|
+
files = import_fs27.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
11215
11569
|
} catch {
|
|
11216
11570
|
continue;
|
|
11217
11571
|
}
|
|
11218
11572
|
for (const file of files) {
|
|
11219
11573
|
try {
|
|
11220
|
-
const raw =
|
|
11574
|
+
const raw = import_fs27.default.readFileSync(import_path29.default.join(projPath, file), "utf-8");
|
|
11221
11575
|
for (const line of raw.split("\n")) {
|
|
11222
11576
|
if (!line.trim()) continue;
|
|
11223
11577
|
let entry;
|
|
@@ -11260,7 +11614,7 @@ function registerReportCommand(program2) {
|
|
|
11260
11614
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
11261
11615
|
options.period
|
|
11262
11616
|
) ? options.period : "7d";
|
|
11263
|
-
const logPath =
|
|
11617
|
+
const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "audit.log");
|
|
11264
11618
|
const allEntries = parseAuditLog(logPath);
|
|
11265
11619
|
if (allEntries.length === 0) {
|
|
11266
11620
|
console.log(
|
|
@@ -11613,14 +11967,14 @@ function registerDaemonCommand(program2) {
|
|
|
11613
11967
|
|
|
11614
11968
|
// src/cli/commands/status.ts
|
|
11615
11969
|
var import_chalk11 = __toESM(require("chalk"));
|
|
11616
|
-
var
|
|
11617
|
-
var
|
|
11618
|
-
var
|
|
11970
|
+
var import_fs28 = __toESM(require("fs"));
|
|
11971
|
+
var import_path30 = __toESM(require("path"));
|
|
11972
|
+
var import_os24 = __toESM(require("os"));
|
|
11619
11973
|
init_core();
|
|
11620
11974
|
init_daemon();
|
|
11621
11975
|
function readJson2(filePath) {
|
|
11622
11976
|
try {
|
|
11623
|
-
if (
|
|
11977
|
+
if (import_fs28.default.existsSync(filePath)) return JSON.parse(import_fs28.default.readFileSync(filePath, "utf-8"));
|
|
11624
11978
|
} catch {
|
|
11625
11979
|
}
|
|
11626
11980
|
return null;
|
|
@@ -11685,28 +12039,28 @@ function registerStatusCommand(program2) {
|
|
|
11685
12039
|
console.log("");
|
|
11686
12040
|
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
11687
12041
|
console.log(` Mode: ${modeLabel}`);
|
|
11688
|
-
const projectConfig =
|
|
11689
|
-
const globalConfig =
|
|
12042
|
+
const projectConfig = import_path30.default.join(process.cwd(), "node9.config.json");
|
|
12043
|
+
const globalConfig = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
11690
12044
|
console.log(
|
|
11691
|
-
` Local: ${
|
|
12045
|
+
` Local: ${import_fs28.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
11692
12046
|
);
|
|
11693
12047
|
console.log(
|
|
11694
|
-
` Global: ${
|
|
12048
|
+
` Global: ${import_fs28.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
11695
12049
|
);
|
|
11696
12050
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
11697
12051
|
console.log(
|
|
11698
12052
|
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
11699
12053
|
);
|
|
11700
12054
|
}
|
|
11701
|
-
const homeDir2 =
|
|
12055
|
+
const homeDir2 = import_os24.default.homedir();
|
|
11702
12056
|
const claudeSettings = readJson2(
|
|
11703
|
-
|
|
12057
|
+
import_path30.default.join(homeDir2, ".claude", "settings.json")
|
|
11704
12058
|
);
|
|
11705
|
-
const claudeConfig = readJson2(
|
|
12059
|
+
const claudeConfig = readJson2(import_path30.default.join(homeDir2, ".claude.json"));
|
|
11706
12060
|
const geminiSettings = readJson2(
|
|
11707
|
-
|
|
12061
|
+
import_path30.default.join(homeDir2, ".gemini", "settings.json")
|
|
11708
12062
|
);
|
|
11709
|
-
const cursorConfig = readJson2(
|
|
12063
|
+
const cursorConfig = readJson2(import_path30.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
11710
12064
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
11711
12065
|
if (agentFound) {
|
|
11712
12066
|
console.log("");
|
|
@@ -11765,9 +12119,9 @@ function registerStatusCommand(program2) {
|
|
|
11765
12119
|
|
|
11766
12120
|
// src/cli/commands/init.ts
|
|
11767
12121
|
var import_chalk12 = __toESM(require("chalk"));
|
|
11768
|
-
var
|
|
11769
|
-
var
|
|
11770
|
-
var
|
|
12122
|
+
var import_fs29 = __toESM(require("fs"));
|
|
12123
|
+
var import_path31 = __toESM(require("path"));
|
|
12124
|
+
var import_os25 = __toESM(require("os"));
|
|
11771
12125
|
var import_https3 = __toESM(require("https"));
|
|
11772
12126
|
init_core();
|
|
11773
12127
|
init_shields();
|
|
@@ -11828,15 +12182,15 @@ function registerInitCommand(program2) {
|
|
|
11828
12182
|
}
|
|
11829
12183
|
console.log("");
|
|
11830
12184
|
}
|
|
11831
|
-
const configPath =
|
|
11832
|
-
if (
|
|
12185
|
+
const configPath = import_path31.default.join(import_os25.default.homedir(), ".node9", "config.json");
|
|
12186
|
+
if (import_fs29.default.existsSync(configPath) && !options.force) {
|
|
11833
12187
|
try {
|
|
11834
|
-
const existing = JSON.parse(
|
|
12188
|
+
const existing = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
|
|
11835
12189
|
const settings = existing.settings ?? {};
|
|
11836
12190
|
if (settings.mode !== chosenMode) {
|
|
11837
12191
|
settings.mode = chosenMode;
|
|
11838
12192
|
existing.settings = settings;
|
|
11839
|
-
|
|
12193
|
+
import_fs29.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
11840
12194
|
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
11841
12195
|
} else {
|
|
11842
12196
|
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -11849,9 +12203,9 @@ function registerInitCommand(program2) {
|
|
|
11849
12203
|
...DEFAULT_CONFIG,
|
|
11850
12204
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
11851
12205
|
};
|
|
11852
|
-
const dir =
|
|
11853
|
-
if (!
|
|
11854
|
-
|
|
12206
|
+
const dir = import_path31.default.dirname(configPath);
|
|
12207
|
+
if (!import_fs29.default.existsSync(dir)) import_fs29.default.mkdirSync(dir, { recursive: true });
|
|
12208
|
+
import_fs29.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
11855
12209
|
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
11856
12210
|
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
11857
12211
|
}
|
|
@@ -11935,7 +12289,7 @@ function registerInitCommand(program2) {
|
|
|
11935
12289
|
}
|
|
11936
12290
|
|
|
11937
12291
|
// src/cli/commands/undo.ts
|
|
11938
|
-
var
|
|
12292
|
+
var import_path32 = __toESM(require("path"));
|
|
11939
12293
|
var import_chalk14 = __toESM(require("chalk"));
|
|
11940
12294
|
|
|
11941
12295
|
// src/tui/undo-navigator.ts
|
|
@@ -12094,7 +12448,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
12094
12448
|
let dir = startDir;
|
|
12095
12449
|
while (true) {
|
|
12096
12450
|
if (cwds.has(dir)) return dir;
|
|
12097
|
-
const parent =
|
|
12451
|
+
const parent = import_path32.default.dirname(dir);
|
|
12098
12452
|
if (parent === dir) return null;
|
|
12099
12453
|
dir = parent;
|
|
12100
12454
|
}
|
|
@@ -12290,12 +12644,12 @@ init_orchestrator();
|
|
|
12290
12644
|
init_provenance();
|
|
12291
12645
|
|
|
12292
12646
|
// src/mcp-pin.ts
|
|
12293
|
-
var
|
|
12294
|
-
var
|
|
12295
|
-
var
|
|
12296
|
-
var
|
|
12297
|
-
function
|
|
12298
|
-
return
|
|
12647
|
+
var import_fs30 = __toESM(require("fs"));
|
|
12648
|
+
var import_path33 = __toESM(require("path"));
|
|
12649
|
+
var import_os26 = __toESM(require("os"));
|
|
12650
|
+
var import_crypto10 = __toESM(require("crypto"));
|
|
12651
|
+
function getPinsFilePath2() {
|
|
12652
|
+
return import_path33.default.join(import_os26.default.homedir(), ".node9", "mcp-pins.json");
|
|
12299
12653
|
}
|
|
12300
12654
|
function hashToolDefinitions(tools) {
|
|
12301
12655
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -12304,15 +12658,15 @@ function hashToolDefinitions(tools) {
|
|
|
12304
12658
|
return nameA.localeCompare(nameB);
|
|
12305
12659
|
});
|
|
12306
12660
|
const canonical = JSON.stringify(sorted);
|
|
12307
|
-
return
|
|
12661
|
+
return import_crypto10.default.createHash("sha256").update(canonical).digest("hex");
|
|
12308
12662
|
}
|
|
12309
12663
|
function getServerKey(upstreamCommand) {
|
|
12310
|
-
return
|
|
12664
|
+
return import_crypto10.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
12311
12665
|
}
|
|
12312
12666
|
function readMcpPinsSafe() {
|
|
12313
|
-
const filePath =
|
|
12667
|
+
const filePath = getPinsFilePath2();
|
|
12314
12668
|
try {
|
|
12315
|
-
const raw =
|
|
12669
|
+
const raw = import_fs30.default.readFileSync(filePath, "utf-8");
|
|
12316
12670
|
if (!raw.trim()) {
|
|
12317
12671
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
12318
12672
|
}
|
|
@@ -12335,11 +12689,11 @@ function readMcpPins() {
|
|
|
12335
12689
|
throw new Error(`[node9] MCP pin file is corrupt: ${result.detail}`);
|
|
12336
12690
|
}
|
|
12337
12691
|
function writeMcpPins(data) {
|
|
12338
|
-
const filePath =
|
|
12339
|
-
|
|
12340
|
-
const tmp = `${filePath}.${
|
|
12341
|
-
|
|
12342
|
-
|
|
12692
|
+
const filePath = getPinsFilePath2();
|
|
12693
|
+
import_fs30.default.mkdirSync(import_path33.default.dirname(filePath), { recursive: true });
|
|
12694
|
+
const tmp = `${filePath}.${import_crypto10.default.randomBytes(6).toString("hex")}.tmp`;
|
|
12695
|
+
import_fs30.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
12696
|
+
import_fs30.default.renameSync(tmp, filePath);
|
|
12343
12697
|
}
|
|
12344
12698
|
function checkPin(serverKey, currentHash) {
|
|
12345
12699
|
const result = readMcpPinsSafe();
|
|
@@ -12362,12 +12716,12 @@ function updatePin(serverKey, label, toolsHash, toolNames) {
|
|
|
12362
12716
|
};
|
|
12363
12717
|
writeMcpPins(pins);
|
|
12364
12718
|
}
|
|
12365
|
-
function
|
|
12719
|
+
function removePin2(serverKey) {
|
|
12366
12720
|
const pins = readMcpPins();
|
|
12367
12721
|
delete pins.servers[serverKey];
|
|
12368
12722
|
writeMcpPins(pins);
|
|
12369
12723
|
}
|
|
12370
|
-
function
|
|
12724
|
+
function clearAllPins2() {
|
|
12371
12725
|
writeMcpPins({ servers: {} });
|
|
12372
12726
|
}
|
|
12373
12727
|
|
|
@@ -12711,9 +13065,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
12711
13065
|
|
|
12712
13066
|
// src/mcp-server/index.ts
|
|
12713
13067
|
var import_readline4 = __toESM(require("readline"));
|
|
12714
|
-
var
|
|
12715
|
-
var
|
|
12716
|
-
var
|
|
13068
|
+
var import_fs31 = __toESM(require("fs"));
|
|
13069
|
+
var import_os27 = __toESM(require("os"));
|
|
13070
|
+
var import_path34 = __toESM(require("path"));
|
|
12717
13071
|
init_core();
|
|
12718
13072
|
init_daemon();
|
|
12719
13073
|
init_shields();
|
|
@@ -12888,13 +13242,13 @@ function handleStatus() {
|
|
|
12888
13242
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
12889
13243
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
12890
13244
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
12891
|
-
const projectConfig =
|
|
12892
|
-
const globalConfig =
|
|
13245
|
+
const projectConfig = import_path34.default.join(process.cwd(), "node9.config.json");
|
|
13246
|
+
const globalConfig = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
|
|
12893
13247
|
lines.push(
|
|
12894
|
-
`Project config (node9.config.json): ${
|
|
13248
|
+
`Project config (node9.config.json): ${import_fs31.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
12895
13249
|
);
|
|
12896
13250
|
lines.push(
|
|
12897
|
-
`Global config (~/.node9/config.json): ${
|
|
13251
|
+
`Global config (~/.node9/config.json): ${import_fs31.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
12898
13252
|
);
|
|
12899
13253
|
return lines.join("\n");
|
|
12900
13254
|
}
|
|
@@ -12968,21 +13322,21 @@ function handleShieldDisable(args) {
|
|
|
12968
13322
|
writeActiveShields(active.filter((s) => s !== name));
|
|
12969
13323
|
return `Shield "${name}" disabled.`;
|
|
12970
13324
|
}
|
|
12971
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
13325
|
+
var GLOBAL_CONFIG_PATH2 = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
|
|
12972
13326
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
12973
13327
|
function readGlobalConfigRaw() {
|
|
12974
13328
|
try {
|
|
12975
|
-
if (
|
|
12976
|
-
return JSON.parse(
|
|
13329
|
+
if (import_fs31.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
13330
|
+
return JSON.parse(import_fs31.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
12977
13331
|
}
|
|
12978
13332
|
} catch {
|
|
12979
13333
|
}
|
|
12980
13334
|
return {};
|
|
12981
13335
|
}
|
|
12982
13336
|
function writeGlobalConfigRaw(data) {
|
|
12983
|
-
const dir =
|
|
12984
|
-
if (!
|
|
12985
|
-
|
|
13337
|
+
const dir = import_path34.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
13338
|
+
if (!import_fs31.default.existsSync(dir)) import_fs31.default.mkdirSync(dir, { recursive: true });
|
|
13339
|
+
import_fs31.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
12986
13340
|
}
|
|
12987
13341
|
function handleApproverList() {
|
|
12988
13342
|
const config = getConfig();
|
|
@@ -13025,9 +13379,9 @@ function handleApproverSet(args) {
|
|
|
13025
13379
|
}
|
|
13026
13380
|
function handleAuditGet(args) {
|
|
13027
13381
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
13028
|
-
const auditPath =
|
|
13029
|
-
if (!
|
|
13030
|
-
const lines =
|
|
13382
|
+
const auditPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
|
|
13383
|
+
if (!import_fs31.default.existsSync(auditPath)) return "No audit log found.";
|
|
13384
|
+
const lines = import_fs31.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
13031
13385
|
const recent = lines.slice(-limit);
|
|
13032
13386
|
const entries = recent.map((line) => {
|
|
13033
13387
|
try {
|
|
@@ -13325,7 +13679,7 @@ function registerMcpPinCommand(program2) {
|
|
|
13325
13679
|
process.exit(1);
|
|
13326
13680
|
}
|
|
13327
13681
|
const label = pins.servers[serverKey].label;
|
|
13328
|
-
|
|
13682
|
+
removePin2(serverKey);
|
|
13329
13683
|
console.log(import_chalk18.default.green(`
|
|
13330
13684
|
\u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
|
|
13331
13685
|
console.log(import_chalk18.default.gray(` Server: ${label}`));
|
|
@@ -13338,7 +13692,7 @@ function registerMcpPinCommand(program2) {
|
|
|
13338
13692
|
return;
|
|
13339
13693
|
}
|
|
13340
13694
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
13341
|
-
|
|
13695
|
+
clearAllPins2();
|
|
13342
13696
|
console.log(import_chalk18.default.green(`
|
|
13343
13697
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
13344
13698
|
console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
|
|
@@ -13485,9 +13839,9 @@ function registerAgentsCommand(program2) {
|
|
|
13485
13839
|
|
|
13486
13840
|
// src/cli/commands/scan.ts
|
|
13487
13841
|
var import_chalk21 = __toESM(require("chalk"));
|
|
13488
|
-
var
|
|
13489
|
-
var
|
|
13490
|
-
var
|
|
13842
|
+
var import_fs32 = __toESM(require("fs"));
|
|
13843
|
+
var import_path35 = __toESM(require("path"));
|
|
13844
|
+
var import_os28 = __toESM(require("os"));
|
|
13491
13845
|
init_shields();
|
|
13492
13846
|
init_config();
|
|
13493
13847
|
init_policy();
|
|
@@ -13559,7 +13913,7 @@ function buildRuleSources() {
|
|
|
13559
13913
|
return sources;
|
|
13560
13914
|
}
|
|
13561
13915
|
function scanClaudeHistory(startDate) {
|
|
13562
|
-
const projectsDir =
|
|
13916
|
+
const projectsDir = import_path35.default.join(import_os28.default.homedir(), ".claude", "projects");
|
|
13563
13917
|
const result = {
|
|
13564
13918
|
filesScanned: 0,
|
|
13565
13919
|
sessions: 0,
|
|
@@ -13571,25 +13925,25 @@ function scanClaudeHistory(startDate) {
|
|
|
13571
13925
|
firstDate: null,
|
|
13572
13926
|
lastDate: null
|
|
13573
13927
|
};
|
|
13574
|
-
if (!
|
|
13928
|
+
if (!import_fs32.default.existsSync(projectsDir)) return result;
|
|
13575
13929
|
let projDirs;
|
|
13576
13930
|
try {
|
|
13577
|
-
projDirs =
|
|
13931
|
+
projDirs = import_fs32.default.readdirSync(projectsDir);
|
|
13578
13932
|
} catch {
|
|
13579
13933
|
return result;
|
|
13580
13934
|
}
|
|
13581
13935
|
const ruleSources = buildRuleSources();
|
|
13582
13936
|
for (const proj of projDirs) {
|
|
13583
|
-
const projPath =
|
|
13937
|
+
const projPath = import_path35.default.join(projectsDir, proj);
|
|
13584
13938
|
try {
|
|
13585
|
-
if (!
|
|
13939
|
+
if (!import_fs32.default.statSync(projPath).isDirectory()) continue;
|
|
13586
13940
|
} catch {
|
|
13587
13941
|
continue;
|
|
13588
13942
|
}
|
|
13589
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
13943
|
+
const projLabel = decodeURIComponent(proj).replace(import_os28.default.homedir(), "~").slice(0, 40);
|
|
13590
13944
|
let files;
|
|
13591
13945
|
try {
|
|
13592
|
-
files =
|
|
13946
|
+
files = import_fs32.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
13593
13947
|
} catch {
|
|
13594
13948
|
continue;
|
|
13595
13949
|
}
|
|
@@ -13598,7 +13952,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13598
13952
|
result.sessions++;
|
|
13599
13953
|
let raw;
|
|
13600
13954
|
try {
|
|
13601
|
-
raw =
|
|
13955
|
+
raw = import_fs32.default.readFileSync(import_path35.default.join(projPath, file), "utf-8");
|
|
13602
13956
|
} catch {
|
|
13603
13957
|
continue;
|
|
13604
13958
|
}
|
|
@@ -13691,8 +14045,8 @@ function registerScanCommand(program2) {
|
|
|
13691
14045
|
console.log("");
|
|
13692
14046
|
console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
|
|
13693
14047
|
console.log("");
|
|
13694
|
-
const projectsDir =
|
|
13695
|
-
if (!
|
|
14048
|
+
const projectsDir = import_path35.default.join(import_os28.default.homedir(), ".claude", "projects");
|
|
14049
|
+
if (!import_fs32.default.existsSync(projectsDir)) {
|
|
13696
14050
|
console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
13697
14051
|
console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
13698
14052
|
return;
|
|
@@ -13804,8 +14158,8 @@ function registerScanCommand(program2) {
|
|
|
13804
14158
|
);
|
|
13805
14159
|
console.log("");
|
|
13806
14160
|
}
|
|
13807
|
-
const auditLog =
|
|
13808
|
-
if (
|
|
14161
|
+
const auditLog = import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
|
|
14162
|
+
if (import_fs32.default.existsSync(auditLog)) {
|
|
13809
14163
|
console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
|
|
13810
14164
|
console.log(
|
|
13811
14165
|
import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
|
|
@@ -13822,9 +14176,9 @@ function registerScanCommand(program2) {
|
|
|
13822
14176
|
|
|
13823
14177
|
// src/cli/commands/sessions.ts
|
|
13824
14178
|
var import_chalk22 = __toESM(require("chalk"));
|
|
13825
|
-
var
|
|
13826
|
-
var
|
|
13827
|
-
var
|
|
14179
|
+
var import_fs33 = __toESM(require("fs"));
|
|
14180
|
+
var import_path36 = __toESM(require("path"));
|
|
14181
|
+
var import_os29 = __toESM(require("os"));
|
|
13828
14182
|
var CLAUDE_PRICING3 = {
|
|
13829
14183
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13830
14184
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -13849,10 +14203,10 @@ function encodeProjectPath(projectPath) {
|
|
|
13849
14203
|
}
|
|
13850
14204
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
13851
14205
|
const encoded = encodeProjectPath(projectPath);
|
|
13852
|
-
return
|
|
14206
|
+
return import_path36.default.join(import_os29.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
13853
14207
|
}
|
|
13854
14208
|
function projectLabel(projectPath) {
|
|
13855
|
-
return projectPath.replace(
|
|
14209
|
+
return projectPath.replace(import_os29.default.homedir(), "~");
|
|
13856
14210
|
}
|
|
13857
14211
|
function parseHistoryLines(lines) {
|
|
13858
14212
|
const entries = [];
|
|
@@ -13921,10 +14275,10 @@ function parseSessionLines(lines) {
|
|
|
13921
14275
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
13922
14276
|
}
|
|
13923
14277
|
function loadAuditEntries(auditPath) {
|
|
13924
|
-
const aPath = auditPath ??
|
|
14278
|
+
const aPath = auditPath ?? import_path36.default.join(import_os29.default.homedir(), ".node9", "audit.log");
|
|
13925
14279
|
let raw;
|
|
13926
14280
|
try {
|
|
13927
|
-
raw =
|
|
14281
|
+
raw = import_fs33.default.readFileSync(aPath, "utf-8");
|
|
13928
14282
|
} catch {
|
|
13929
14283
|
return [];
|
|
13930
14284
|
}
|
|
@@ -13960,10 +14314,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
13960
14314
|
return result;
|
|
13961
14315
|
}
|
|
13962
14316
|
function buildSessions(days, historyPath) {
|
|
13963
|
-
const hPath = historyPath ??
|
|
14317
|
+
const hPath = historyPath ?? import_path36.default.join(import_os29.default.homedir(), ".claude", "history.jsonl");
|
|
13964
14318
|
let historyRaw;
|
|
13965
14319
|
try {
|
|
13966
|
-
historyRaw =
|
|
14320
|
+
historyRaw = import_fs33.default.readFileSync(hPath, "utf-8");
|
|
13967
14321
|
} catch {
|
|
13968
14322
|
return [];
|
|
13969
14323
|
}
|
|
@@ -13988,7 +14342,7 @@ function buildSessions(days, historyPath) {
|
|
|
13988
14342
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
13989
14343
|
let sessionLines = [];
|
|
13990
14344
|
try {
|
|
13991
|
-
sessionLines =
|
|
14345
|
+
sessionLines = import_fs33.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
13992
14346
|
} catch {
|
|
13993
14347
|
}
|
|
13994
14348
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -14239,8 +14593,8 @@ function registerSessionsCommand(program2) {
|
|
|
14239
14593
|
console.log("");
|
|
14240
14594
|
console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
|
|
14241
14595
|
console.log("");
|
|
14242
|
-
const historyPath =
|
|
14243
|
-
if (!
|
|
14596
|
+
const historyPath = import_path36.default.join(import_os29.default.homedir(), ".claude", "history.jsonl");
|
|
14597
|
+
if (!import_fs33.default.existsSync(historyPath)) {
|
|
14244
14598
|
console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
14245
14599
|
console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14246
14600
|
return;
|
|
@@ -14270,22 +14624,110 @@ function registerSessionsCommand(program2) {
|
|
|
14270
14624
|
});
|
|
14271
14625
|
}
|
|
14272
14626
|
|
|
14627
|
+
// src/cli/commands/skill-pin.ts
|
|
14628
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
14629
|
+
var import_fs34 = __toESM(require("fs"));
|
|
14630
|
+
var import_os30 = __toESM(require("os"));
|
|
14631
|
+
var import_path37 = __toESM(require("path"));
|
|
14632
|
+
function wipeSkillSessions() {
|
|
14633
|
+
try {
|
|
14634
|
+
import_fs34.default.rmSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "skill-sessions"), {
|
|
14635
|
+
recursive: true,
|
|
14636
|
+
force: true
|
|
14637
|
+
});
|
|
14638
|
+
} catch {
|
|
14639
|
+
}
|
|
14640
|
+
}
|
|
14641
|
+
function registerSkillPinCommand(program2) {
|
|
14642
|
+
const skillCmd = program2.command("skill").description("Manage skill pinning (supply chain & update drift defense, AST 02 + AST 07)");
|
|
14643
|
+
const pinSubCmd = skillCmd.command("pin").description("Manage pinned skill roots");
|
|
14644
|
+
pinSubCmd.command("list").description("Show all pinned skill roots and their content hashes").action(() => {
|
|
14645
|
+
const result = readSkillPinsSafe();
|
|
14646
|
+
if (!result.ok) {
|
|
14647
|
+
if (result.reason === "missing") {
|
|
14648
|
+
console.log(import_chalk23.default.gray("\nNo skill roots are pinned yet."));
|
|
14649
|
+
console.log(
|
|
14650
|
+
import_chalk23.default.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
14651
|
+
);
|
|
14652
|
+
return;
|
|
14653
|
+
}
|
|
14654
|
+
console.error(import_chalk23.default.red(`
|
|
14655
|
+
\u274C Pin file is corrupt: ${result.detail}`));
|
|
14656
|
+
console.error(import_chalk23.default.yellow(" Run: node9 skill pin reset\n"));
|
|
14657
|
+
process.exit(1);
|
|
14658
|
+
}
|
|
14659
|
+
const entries = Object.entries(result.pins.roots);
|
|
14660
|
+
if (entries.length === 0) {
|
|
14661
|
+
console.log(import_chalk23.default.gray("\nNo skill roots are pinned yet.\n"));
|
|
14662
|
+
return;
|
|
14663
|
+
}
|
|
14664
|
+
console.log(import_chalk23.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
14665
|
+
for (const [key, entry] of entries) {
|
|
14666
|
+
const missing = entry.exists ? "" : import_chalk23.default.yellow(" (not present at pin time)");
|
|
14667
|
+
console.log(` ${import_chalk23.default.cyan(key)} ${import_chalk23.default.gray(entry.rootPath)}${missing}`);
|
|
14668
|
+
console.log(` Files (${entry.fileCount})`);
|
|
14669
|
+
console.log(` Hash: ${import_chalk23.default.gray(entry.contentHash.slice(0, 16))}...`);
|
|
14670
|
+
console.log(` Pinned: ${import_chalk23.default.gray(entry.pinnedAt)}
|
|
14671
|
+
`);
|
|
14672
|
+
}
|
|
14673
|
+
});
|
|
14674
|
+
pinSubCmd.command("update <rootKey>").description("Remove a pin so the next session re-pins with current state").action((rootKey) => {
|
|
14675
|
+
let pins;
|
|
14676
|
+
try {
|
|
14677
|
+
pins = readSkillPins();
|
|
14678
|
+
} catch {
|
|
14679
|
+
console.error(import_chalk23.default.red("\n\u274C Pin file is corrupt."));
|
|
14680
|
+
console.error(import_chalk23.default.yellow(" Run: node9 skill pin reset\n"));
|
|
14681
|
+
process.exit(1);
|
|
14682
|
+
}
|
|
14683
|
+
if (!pins.roots[rootKey]) {
|
|
14684
|
+
console.error(import_chalk23.default.red(`
|
|
14685
|
+
\u274C No pin found for root key "${rootKey}"
|
|
14686
|
+
`));
|
|
14687
|
+
console.error(`Run ${import_chalk23.default.cyan("node9 skill pin list")} to see pinned roots.
|
|
14688
|
+
`);
|
|
14689
|
+
process.exit(1);
|
|
14690
|
+
}
|
|
14691
|
+
const rootPath = pins.roots[rootKey].rootPath;
|
|
14692
|
+
removePin(rootKey);
|
|
14693
|
+
wipeSkillSessions();
|
|
14694
|
+
console.log(import_chalk23.default.green(`
|
|
14695
|
+
\u{1F513} Pin removed for ${import_chalk23.default.cyan(rootKey)}`));
|
|
14696
|
+
console.log(import_chalk23.default.gray(` ${rootPath}`));
|
|
14697
|
+
console.log(import_chalk23.default.gray(" Next session will re-pin with current state.\n"));
|
|
14698
|
+
});
|
|
14699
|
+
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
14700
|
+
const result = readSkillPinsSafe();
|
|
14701
|
+
if (!result.ok && result.reason === "missing") {
|
|
14702
|
+
wipeSkillSessions();
|
|
14703
|
+
console.log(import_chalk23.default.gray("\nNo pins to clear.\n"));
|
|
14704
|
+
return;
|
|
14705
|
+
}
|
|
14706
|
+
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
14707
|
+
clearAllPins();
|
|
14708
|
+
wipeSkillSessions();
|
|
14709
|
+
console.log(import_chalk23.default.green(`
|
|
14710
|
+
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
14711
|
+
console.log(import_chalk23.default.gray(" Next session will re-pin with current state.\n"));
|
|
14712
|
+
});
|
|
14713
|
+
}
|
|
14714
|
+
|
|
14273
14715
|
// src/cli.ts
|
|
14274
14716
|
var { version } = JSON.parse(
|
|
14275
|
-
|
|
14717
|
+
import_fs37.default.readFileSync(import_path40.default.join(__dirname, "../package.json"), "utf-8")
|
|
14276
14718
|
);
|
|
14277
14719
|
var program = new import_commander.Command();
|
|
14278
14720
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
14279
14721
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
14280
14722
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
14281
|
-
const credPath =
|
|
14282
|
-
if (!
|
|
14283
|
-
|
|
14723
|
+
const credPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "credentials.json");
|
|
14724
|
+
if (!import_fs37.default.existsSync(import_path40.default.dirname(credPath)))
|
|
14725
|
+
import_fs37.default.mkdirSync(import_path40.default.dirname(credPath), { recursive: true });
|
|
14284
14726
|
const profileName = options.profile || "default";
|
|
14285
14727
|
let existingCreds = {};
|
|
14286
14728
|
try {
|
|
14287
|
-
if (
|
|
14288
|
-
const raw = JSON.parse(
|
|
14729
|
+
if (import_fs37.default.existsSync(credPath)) {
|
|
14730
|
+
const raw = JSON.parse(import_fs37.default.readFileSync(credPath, "utf-8"));
|
|
14289
14731
|
if (raw.apiKey) {
|
|
14290
14732
|
existingCreds = {
|
|
14291
14733
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -14297,13 +14739,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14297
14739
|
} catch {
|
|
14298
14740
|
}
|
|
14299
14741
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14300
|
-
|
|
14742
|
+
import_fs37.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
14301
14743
|
if (profileName === "default") {
|
|
14302
|
-
const configPath =
|
|
14744
|
+
const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
|
|
14303
14745
|
let config = {};
|
|
14304
14746
|
try {
|
|
14305
|
-
if (
|
|
14306
|
-
config = JSON.parse(
|
|
14747
|
+
if (import_fs37.default.existsSync(configPath))
|
|
14748
|
+
config = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
|
|
14307
14749
|
} catch {
|
|
14308
14750
|
}
|
|
14309
14751
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -14318,19 +14760,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14318
14760
|
approvers.cloud = false;
|
|
14319
14761
|
}
|
|
14320
14762
|
s.approvers = approvers;
|
|
14321
|
-
if (!
|
|
14322
|
-
|
|
14323
|
-
|
|
14763
|
+
if (!import_fs37.default.existsSync(import_path40.default.dirname(configPath)))
|
|
14764
|
+
import_fs37.default.mkdirSync(import_path40.default.dirname(configPath), { recursive: true });
|
|
14765
|
+
import_fs37.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
14324
14766
|
}
|
|
14325
14767
|
if (options.profile && profileName !== "default") {
|
|
14326
|
-
console.log(
|
|
14327
|
-
console.log(
|
|
14768
|
+
console.log(import_chalk25.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
14769
|
+
console.log(import_chalk25.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
14328
14770
|
} else if (options.local) {
|
|
14329
|
-
console.log(
|
|
14330
|
-
console.log(
|
|
14771
|
+
console.log(import_chalk25.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
14772
|
+
console.log(import_chalk25.default.gray(` All decisions stay on this machine.`));
|
|
14331
14773
|
} else {
|
|
14332
|
-
console.log(
|
|
14333
|
-
console.log(
|
|
14774
|
+
console.log(import_chalk25.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
14775
|
+
console.log(import_chalk25.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
14334
14776
|
}
|
|
14335
14777
|
});
|
|
14336
14778
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
|
|
@@ -14341,7 +14783,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
14341
14783
|
if (target === "vscode") return await setupVSCode();
|
|
14342
14784
|
if (target === "hud") return setupHud();
|
|
14343
14785
|
console.error(
|
|
14344
|
-
|
|
14786
|
+
import_chalk25.default.red(
|
|
14345
14787
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14346
14788
|
)
|
|
14347
14789
|
);
|
|
@@ -14349,16 +14791,16 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
14349
14791
|
});
|
|
14350
14792
|
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
|
|
14351
14793
|
if (!target) {
|
|
14352
|
-
console.log(
|
|
14353
|
-
console.log(" Usage: " +
|
|
14794
|
+
console.log(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
14795
|
+
console.log(" Usage: " + import_chalk25.default.white("node9 setup <target>") + "\n");
|
|
14354
14796
|
console.log(" Targets:");
|
|
14355
|
-
console.log(" " +
|
|
14356
|
-
console.log(" " +
|
|
14357
|
-
console.log(" " +
|
|
14358
|
-
console.log(" " +
|
|
14359
|
-
console.log(" " +
|
|
14797
|
+
console.log(" " + import_chalk25.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
14798
|
+
console.log(" " + import_chalk25.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
14799
|
+
console.log(" " + import_chalk25.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
14800
|
+
console.log(" " + import_chalk25.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
14801
|
+
console.log(" " + import_chalk25.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
14360
14802
|
process.stdout.write(
|
|
14361
|
-
" " +
|
|
14803
|
+
" " + import_chalk25.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
14362
14804
|
);
|
|
14363
14805
|
console.log("");
|
|
14364
14806
|
return;
|
|
@@ -14371,7 +14813,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
14371
14813
|
if (t === "vscode") return await setupVSCode();
|
|
14372
14814
|
if (t === "hud") return setupHud();
|
|
14373
14815
|
console.error(
|
|
14374
|
-
|
|
14816
|
+
import_chalk25.default.red(
|
|
14375
14817
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14376
14818
|
)
|
|
14377
14819
|
);
|
|
@@ -14390,33 +14832,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
14390
14832
|
else if (target === "hud") fn = teardownHud;
|
|
14391
14833
|
else {
|
|
14392
14834
|
console.error(
|
|
14393
|
-
|
|
14835
|
+
import_chalk25.default.red(
|
|
14394
14836
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14395
14837
|
)
|
|
14396
14838
|
);
|
|
14397
14839
|
process.exit(1);
|
|
14398
14840
|
}
|
|
14399
|
-
console.log(
|
|
14841
|
+
console.log(import_chalk25.default.cyan(`
|
|
14400
14842
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
14401
14843
|
`));
|
|
14402
14844
|
try {
|
|
14403
14845
|
fn();
|
|
14404
14846
|
} catch (err2) {
|
|
14405
|
-
console.error(
|
|
14847
|
+
console.error(import_chalk25.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14406
14848
|
process.exit(1);
|
|
14407
14849
|
}
|
|
14408
|
-
console.log(
|
|
14850
|
+
console.log(import_chalk25.default.gray("\n Restart the agent for changes to take effect."));
|
|
14409
14851
|
});
|
|
14410
14852
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
14411
|
-
console.log(
|
|
14412
|
-
console.log(
|
|
14853
|
+
console.log(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
14854
|
+
console.log(import_chalk25.default.bold("Stopping daemon..."));
|
|
14413
14855
|
try {
|
|
14414
14856
|
stopDaemon();
|
|
14415
|
-
console.log(
|
|
14857
|
+
console.log(import_chalk25.default.green(" \u2705 Daemon stopped"));
|
|
14416
14858
|
} catch {
|
|
14417
|
-
console.log(
|
|
14859
|
+
console.log(import_chalk25.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
14418
14860
|
}
|
|
14419
|
-
console.log(
|
|
14861
|
+
console.log(import_chalk25.default.bold("\nRemoving hooks..."));
|
|
14420
14862
|
let teardownFailed = false;
|
|
14421
14863
|
for (const [label, fn] of [
|
|
14422
14864
|
["Claude", teardownClaude],
|
|
@@ -14430,45 +14872,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
14430
14872
|
} catch (err2) {
|
|
14431
14873
|
teardownFailed = true;
|
|
14432
14874
|
console.error(
|
|
14433
|
-
|
|
14875
|
+
import_chalk25.default.red(
|
|
14434
14876
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
14435
14877
|
)
|
|
14436
14878
|
);
|
|
14437
14879
|
}
|
|
14438
14880
|
}
|
|
14439
14881
|
if (options.purge) {
|
|
14440
|
-
const node9Dir =
|
|
14441
|
-
if (
|
|
14882
|
+
const node9Dir = import_path40.default.join(import_os33.default.homedir(), ".node9");
|
|
14883
|
+
if (import_fs37.default.existsSync(node9Dir)) {
|
|
14442
14884
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
14443
14885
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
14444
14886
|
default: false
|
|
14445
14887
|
});
|
|
14446
14888
|
if (confirmed) {
|
|
14447
|
-
|
|
14448
|
-
if (
|
|
14889
|
+
import_fs37.default.rmSync(node9Dir, { recursive: true });
|
|
14890
|
+
if (import_fs37.default.existsSync(node9Dir)) {
|
|
14449
14891
|
console.error(
|
|
14450
|
-
|
|
14892
|
+
import_chalk25.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
14451
14893
|
);
|
|
14452
14894
|
} else {
|
|
14453
|
-
console.log(
|
|
14895
|
+
console.log(import_chalk25.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
14454
14896
|
}
|
|
14455
14897
|
} else {
|
|
14456
|
-
console.log(
|
|
14898
|
+
console.log(import_chalk25.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
14457
14899
|
}
|
|
14458
14900
|
} else {
|
|
14459
|
-
console.log(
|
|
14901
|
+
console.log(import_chalk25.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
14460
14902
|
}
|
|
14461
14903
|
} else {
|
|
14462
14904
|
console.log(
|
|
14463
|
-
|
|
14905
|
+
import_chalk25.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
14464
14906
|
);
|
|
14465
14907
|
}
|
|
14466
14908
|
if (teardownFailed) {
|
|
14467
|
-
console.error(
|
|
14909
|
+
console.error(import_chalk25.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
14468
14910
|
process.exit(1);
|
|
14469
14911
|
}
|
|
14470
|
-
console.log(
|
|
14471
|
-
console.log(
|
|
14912
|
+
console.log(import_chalk25.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
14913
|
+
console.log(import_chalk25.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
14472
14914
|
});
|
|
14473
14915
|
registerDoctorCommand(program, version);
|
|
14474
14916
|
program.command("explain").description(
|
|
@@ -14481,7 +14923,7 @@ program.command("explain").description(
|
|
|
14481
14923
|
try {
|
|
14482
14924
|
args = JSON.parse(trimmed);
|
|
14483
14925
|
} catch {
|
|
14484
|
-
console.error(
|
|
14926
|
+
console.error(import_chalk25.default.red(`
|
|
14485
14927
|
\u274C Invalid JSON: ${trimmed}
|
|
14486
14928
|
`));
|
|
14487
14929
|
process.exit(1);
|
|
@@ -14492,54 +14934,54 @@ program.command("explain").description(
|
|
|
14492
14934
|
}
|
|
14493
14935
|
const result = await explainPolicy(tool, args);
|
|
14494
14936
|
console.log("");
|
|
14495
|
-
console.log(
|
|
14937
|
+
console.log(import_chalk25.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
14496
14938
|
console.log("");
|
|
14497
|
-
console.log(` ${
|
|
14939
|
+
console.log(` ${import_chalk25.default.bold("Tool:")} ${import_chalk25.default.white(result.tool)}`);
|
|
14498
14940
|
if (argsRaw) {
|
|
14499
14941
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
14500
|
-
console.log(` ${
|
|
14942
|
+
console.log(` ${import_chalk25.default.bold("Input:")} ${import_chalk25.default.gray(preview2)}`);
|
|
14501
14943
|
}
|
|
14502
14944
|
console.log("");
|
|
14503
|
-
console.log(
|
|
14945
|
+
console.log(import_chalk25.default.bold("Config Sources (Waterfall):"));
|
|
14504
14946
|
for (const tier of result.waterfall) {
|
|
14505
|
-
const num3 =
|
|
14947
|
+
const num3 = import_chalk25.default.gray(` ${tier.tier}.`);
|
|
14506
14948
|
const label = tier.label.padEnd(16);
|
|
14507
14949
|
let statusStr;
|
|
14508
14950
|
if (tier.tier === 1) {
|
|
14509
|
-
statusStr =
|
|
14951
|
+
statusStr = import_chalk25.default.gray(tier.note ?? "");
|
|
14510
14952
|
} else if (tier.status === "active") {
|
|
14511
|
-
const loc = tier.path ?
|
|
14512
|
-
const note = tier.note ?
|
|
14513
|
-
statusStr =
|
|
14953
|
+
const loc = tier.path ? import_chalk25.default.gray(tier.path) : "";
|
|
14954
|
+
const note = tier.note ? import_chalk25.default.gray(`(${tier.note})`) : "";
|
|
14955
|
+
statusStr = import_chalk25.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
14514
14956
|
} else {
|
|
14515
|
-
statusStr =
|
|
14957
|
+
statusStr = import_chalk25.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
14516
14958
|
}
|
|
14517
|
-
console.log(`${num3} ${
|
|
14959
|
+
console.log(`${num3} ${import_chalk25.default.white(label)} ${statusStr}`);
|
|
14518
14960
|
}
|
|
14519
14961
|
console.log("");
|
|
14520
|
-
console.log(
|
|
14962
|
+
console.log(import_chalk25.default.bold("Policy Evaluation:"));
|
|
14521
14963
|
for (const step of result.steps) {
|
|
14522
14964
|
const isFinal = step.isFinal;
|
|
14523
14965
|
let icon;
|
|
14524
|
-
if (step.outcome === "allow") icon =
|
|
14525
|
-
else if (step.outcome === "review") icon =
|
|
14526
|
-
else if (step.outcome === "skip") icon =
|
|
14527
|
-
else icon =
|
|
14966
|
+
if (step.outcome === "allow") icon = import_chalk25.default.green(" \u2705");
|
|
14967
|
+
else if (step.outcome === "review") icon = import_chalk25.default.red(" \u{1F534}");
|
|
14968
|
+
else if (step.outcome === "skip") icon = import_chalk25.default.gray(" \u2500 ");
|
|
14969
|
+
else icon = import_chalk25.default.gray(" \u25CB ");
|
|
14528
14970
|
const name = step.name.padEnd(18);
|
|
14529
|
-
const nameStr = isFinal ?
|
|
14530
|
-
const detail = isFinal ?
|
|
14531
|
-
const arrow = isFinal ?
|
|
14971
|
+
const nameStr = isFinal ? import_chalk25.default.white.bold(name) : import_chalk25.default.white(name);
|
|
14972
|
+
const detail = isFinal ? import_chalk25.default.white(step.detail) : import_chalk25.default.gray(step.detail);
|
|
14973
|
+
const arrow = isFinal ? import_chalk25.default.yellow(" \u2190 STOP") : "";
|
|
14532
14974
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
14533
14975
|
}
|
|
14534
14976
|
console.log("");
|
|
14535
14977
|
if (result.decision === "allow") {
|
|
14536
|
-
console.log(
|
|
14978
|
+
console.log(import_chalk25.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk25.default.gray(" \u2014 no approval needed"));
|
|
14537
14979
|
} else {
|
|
14538
14980
|
console.log(
|
|
14539
|
-
|
|
14981
|
+
import_chalk25.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk25.default.gray(" \u2014 human approval required")
|
|
14540
14982
|
);
|
|
14541
14983
|
if (result.blockedByLabel) {
|
|
14542
|
-
console.log(
|
|
14984
|
+
console.log(import_chalk25.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
14543
14985
|
}
|
|
14544
14986
|
}
|
|
14545
14987
|
console.log("");
|
|
@@ -14554,7 +14996,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
14554
14996
|
try {
|
|
14555
14997
|
await startTail2(options);
|
|
14556
14998
|
} catch (err2) {
|
|
14557
|
-
console.error(
|
|
14999
|
+
console.error(import_chalk25.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14558
15000
|
process.exit(1);
|
|
14559
15001
|
}
|
|
14560
15002
|
});
|
|
@@ -14562,6 +15004,7 @@ registerWatchCommand(program);
|
|
|
14562
15004
|
registerMcpGatewayCommand(program);
|
|
14563
15005
|
registerMcpServerCommand(program);
|
|
14564
15006
|
registerMcpPinCommand(program);
|
|
15007
|
+
registerSkillPinCommand(program);
|
|
14565
15008
|
registerCheckCommand(program);
|
|
14566
15009
|
registerLogCommand(program);
|
|
14567
15010
|
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
@@ -14586,14 +15029,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
14586
15029
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
14587
15030
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
14588
15031
|
if (subcommand === "debug") {
|
|
14589
|
-
const flagFile =
|
|
15032
|
+
const flagFile = import_path40.default.join(import_os33.default.homedir(), ".node9", "hud-debug");
|
|
14590
15033
|
if (state === "on") {
|
|
14591
|
-
|
|
14592
|
-
|
|
15034
|
+
import_fs37.default.mkdirSync(import_path40.default.dirname(flagFile), { recursive: true });
|
|
15035
|
+
import_fs37.default.writeFileSync(flagFile, "");
|
|
14593
15036
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
14594
15037
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
14595
15038
|
} else if (state === "off") {
|
|
14596
|
-
if (
|
|
15039
|
+
if (import_fs37.default.existsSync(flagFile)) import_fs37.default.unlinkSync(flagFile);
|
|
14597
15040
|
console.log("HUD debug logging disabled.");
|
|
14598
15041
|
} else {
|
|
14599
15042
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -14608,7 +15051,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
14608
15051
|
const ms = parseDuration(options.duration);
|
|
14609
15052
|
if (ms === null) {
|
|
14610
15053
|
console.error(
|
|
14611
|
-
|
|
15054
|
+
import_chalk25.default.red(`
|
|
14612
15055
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
14613
15056
|
`)
|
|
14614
15057
|
);
|
|
@@ -14616,20 +15059,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
14616
15059
|
}
|
|
14617
15060
|
pauseNode9(ms, options.duration);
|
|
14618
15061
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
14619
|
-
console.log(
|
|
15062
|
+
console.log(import_chalk25.default.yellow(`
|
|
14620
15063
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
14621
|
-
console.log(
|
|
14622
|
-
console.log(
|
|
15064
|
+
console.log(import_chalk25.default.gray(` All tool calls will be allowed without review.`));
|
|
15065
|
+
console.log(import_chalk25.default.gray(` Run "node9 resume" to re-enable early.
|
|
14623
15066
|
`));
|
|
14624
15067
|
});
|
|
14625
15068
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
14626
15069
|
const { paused } = checkPause();
|
|
14627
15070
|
if (!paused) {
|
|
14628
|
-
console.log(
|
|
15071
|
+
console.log(import_chalk25.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
14629
15072
|
return;
|
|
14630
15073
|
}
|
|
14631
15074
|
resumeNode9();
|
|
14632
|
-
console.log(
|
|
15075
|
+
console.log(import_chalk25.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
14633
15076
|
});
|
|
14634
15077
|
var HOOK_BASED_AGENTS = {
|
|
14635
15078
|
claude: "claude",
|
|
@@ -14642,15 +15085,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14642
15085
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
14643
15086
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
14644
15087
|
console.error(
|
|
14645
|
-
|
|
15088
|
+
import_chalk25.default.yellow(`
|
|
14646
15089
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
14647
15090
|
);
|
|
14648
|
-
console.error(
|
|
15091
|
+
console.error(import_chalk25.default.white(`
|
|
14649
15092
|
"${target}" uses its own hook system. Use:`));
|
|
14650
15093
|
console.error(
|
|
14651
|
-
|
|
15094
|
+
import_chalk25.default.green(` node9 addto ${target} `) + import_chalk25.default.gray("# one-time setup")
|
|
14652
15095
|
);
|
|
14653
|
-
console.error(
|
|
15096
|
+
console.error(import_chalk25.default.green(` ${target} `) + import_chalk25.default.gray("# run normally"));
|
|
14654
15097
|
process.exit(1);
|
|
14655
15098
|
}
|
|
14656
15099
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -14667,7 +15110,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14667
15110
|
}
|
|
14668
15111
|
);
|
|
14669
15112
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
14670
|
-
console.error(
|
|
15113
|
+
console.error(import_chalk25.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
14671
15114
|
const daemonReady = await autoStartDaemonAndWait();
|
|
14672
15115
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
14673
15116
|
}
|
|
@@ -14680,12 +15123,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14680
15123
|
}
|
|
14681
15124
|
if (!result.approved) {
|
|
14682
15125
|
console.error(
|
|
14683
|
-
|
|
15126
|
+
import_chalk25.default.red(`
|
|
14684
15127
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
14685
15128
|
);
|
|
14686
15129
|
process.exit(1);
|
|
14687
15130
|
}
|
|
14688
|
-
console.error(
|
|
15131
|
+
console.error(import_chalk25.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
14689
15132
|
await runProxy(fullCommand);
|
|
14690
15133
|
} else {
|
|
14691
15134
|
program.help();
|
|
@@ -14704,9 +15147,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
14704
15147
|
const isCheckHook = process.argv[2] === "check";
|
|
14705
15148
|
if (isCheckHook) {
|
|
14706
15149
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
14707
|
-
const logPath =
|
|
15150
|
+
const logPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log");
|
|
14708
15151
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
14709
|
-
|
|
15152
|
+
import_fs37.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
14710
15153
|
`);
|
|
14711
15154
|
}
|
|
14712
15155
|
process.exit(0);
|