@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.mjs
CHANGED
|
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
const lines = result.error.issues.map((issue) => {
|
|
150
|
-
const
|
|
151
|
-
return ` \u2022 ${
|
|
150
|
+
const path41 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
151
|
+
return ` \u2022 ${path41}: ${issue.message}`;
|
|
152
152
|
});
|
|
153
153
|
return {
|
|
154
154
|
sanitized,
|
|
@@ -254,6 +254,11 @@ var init_config_schema = __esm({
|
|
|
254
254
|
enabled: z.boolean().optional(),
|
|
255
255
|
threshold: z.number().min(2).optional(),
|
|
256
256
|
windowSeconds: z.number().min(10).optional()
|
|
257
|
+
}).optional(),
|
|
258
|
+
skillPinning: z.object({
|
|
259
|
+
enabled: z.boolean().optional(),
|
|
260
|
+
mode: z.enum(["warn", "block"]).optional(),
|
|
261
|
+
roots: z.array(z.string()).optional()
|
|
257
262
|
}).optional()
|
|
258
263
|
}).optional(),
|
|
259
264
|
environments: z.record(z.object({ requireApproval: z.boolean().optional() })).optional()
|
|
@@ -552,7 +557,11 @@ function getConfig(cwd) {
|
|
|
552
557
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
553
558
|
},
|
|
554
559
|
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
555
|
-
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
560
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
|
|
561
|
+
skillPinning: {
|
|
562
|
+
...DEFAULT_CONFIG.policy.skillPinning,
|
|
563
|
+
roots: [...DEFAULT_CONFIG.policy.skillPinning.roots]
|
|
564
|
+
}
|
|
556
565
|
};
|
|
557
566
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
558
567
|
const applyLayer = (source) => {
|
|
@@ -605,6 +614,16 @@ function getConfig(cwd) {
|
|
|
605
614
|
if (ld.windowSeconds !== void 0)
|
|
606
615
|
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
607
616
|
}
|
|
617
|
+
if (p.skillPinning && typeof p.skillPinning === "object") {
|
|
618
|
+
const sp = p.skillPinning;
|
|
619
|
+
if (sp.enabled !== void 0) mergedPolicy.skillPinning.enabled = sp.enabled;
|
|
620
|
+
if (sp.mode !== void 0) mergedPolicy.skillPinning.mode = sp.mode;
|
|
621
|
+
if (Array.isArray(sp.roots)) {
|
|
622
|
+
for (const r of sp.roots) {
|
|
623
|
+
if (typeof r === "string" && r.length > 0) mergedPolicy.skillPinning.roots.push(r);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
608
627
|
const envs = source.environments || {};
|
|
609
628
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
610
629
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -656,6 +675,7 @@ function getConfig(cwd) {
|
|
|
656
675
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
657
676
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
658
677
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
678
|
+
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
659
679
|
mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
|
|
660
680
|
mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
|
|
661
681
|
mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
|
|
@@ -920,7 +940,8 @@ var init_config = __esm({
|
|
|
920
940
|
}
|
|
921
941
|
],
|
|
922
942
|
dlp: { enabled: true, scanIgnoredTools: true },
|
|
923
|
-
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
943
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
|
|
944
|
+
skillPinning: { enabled: false, mode: "warn", roots: [] }
|
|
924
945
|
},
|
|
925
946
|
environments: {}
|
|
926
947
|
};
|
|
@@ -1729,9 +1750,9 @@ function matchesPattern(text, patterns) {
|
|
|
1729
1750
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1730
1751
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1731
1752
|
}
|
|
1732
|
-
function getNestedValue(obj,
|
|
1753
|
+
function getNestedValue(obj, path41) {
|
|
1733
1754
|
if (!obj || typeof obj !== "object") return null;
|
|
1734
|
-
return
|
|
1755
|
+
return path41.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1735
1756
|
}
|
|
1736
1757
|
function shouldSnapshot(toolName, args, config) {
|
|
1737
1758
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2324,6 +2345,15 @@ var init_policy = __esm({
|
|
|
2324
2345
|
import fs8 from "fs";
|
|
2325
2346
|
import path9 from "path";
|
|
2326
2347
|
import os7 from "os";
|
|
2348
|
+
function extractCommandPattern(toolName, args) {
|
|
2349
|
+
const lower = toolName.toLowerCase();
|
|
2350
|
+
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
2351
|
+
const a = args;
|
|
2352
|
+
const cmd = typeof a?.["command"] === "string" ? a["command"].trim() : "";
|
|
2353
|
+
if (!cmd) return void 0;
|
|
2354
|
+
const words = cmd.split(/\s+/);
|
|
2355
|
+
return words.slice(0, 2).join(" ");
|
|
2356
|
+
}
|
|
2327
2357
|
function checkPause() {
|
|
2328
2358
|
try {
|
|
2329
2359
|
if (!fs8.existsSync(PAUSED_FILE)) return { paused: false };
|
|
@@ -2357,7 +2387,7 @@ function resumeNode9() {
|
|
|
2357
2387
|
} catch {
|
|
2358
2388
|
}
|
|
2359
2389
|
}
|
|
2360
|
-
function getActiveTrustSession(toolName) {
|
|
2390
|
+
function getActiveTrustSession(toolName, args) {
|
|
2361
2391
|
try {
|
|
2362
2392
|
if (!fs8.existsSync(TRUST_FILE)) return false;
|
|
2363
2393
|
const trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
|
|
@@ -2366,12 +2396,20 @@ function getActiveTrustSession(toolName) {
|
|
|
2366
2396
|
if (active.length !== trust.entries.length) {
|
|
2367
2397
|
fs8.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
2368
2398
|
}
|
|
2369
|
-
return active.some((e) =>
|
|
2399
|
+
return active.some((e) => {
|
|
2400
|
+
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
2401
|
+
if (e.commandPattern) {
|
|
2402
|
+
const actual = extractCommandPattern(toolName, args) ?? "";
|
|
2403
|
+
return actual === e.commandPattern || actual.startsWith(e.commandPattern + " ");
|
|
2404
|
+
}
|
|
2405
|
+
return true;
|
|
2406
|
+
});
|
|
2370
2407
|
} catch {
|
|
2371
2408
|
return false;
|
|
2372
2409
|
}
|
|
2373
2410
|
}
|
|
2374
|
-
function writeTrustSession(toolName, durationMs) {
|
|
2411
|
+
function writeTrustSession(toolName, durationMs, args) {
|
|
2412
|
+
const commandPattern = extractCommandPattern(toolName, args);
|
|
2375
2413
|
try {
|
|
2376
2414
|
let trust = { entries: [] };
|
|
2377
2415
|
try {
|
|
@@ -2381,8 +2419,14 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
2381
2419
|
} catch {
|
|
2382
2420
|
}
|
|
2383
2421
|
const now = Date.now();
|
|
2384
|
-
trust.entries = trust.entries.filter(
|
|
2385
|
-
|
|
2422
|
+
trust.entries = trust.entries.filter(
|
|
2423
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > now
|
|
2424
|
+
);
|
|
2425
|
+
trust.entries.push({
|
|
2426
|
+
tool: toolName,
|
|
2427
|
+
...commandPattern && { commandPattern },
|
|
2428
|
+
expiry: now + durationMs
|
|
2429
|
+
});
|
|
2386
2430
|
atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
|
|
2387
2431
|
} catch (err2) {
|
|
2388
2432
|
if (process.env.NODE9_DEBUG === "1") {
|
|
@@ -3378,12 +3422,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3378
3422
|
};
|
|
3379
3423
|
}
|
|
3380
3424
|
}
|
|
3381
|
-
if (getActiveTrustSession(toolName)) {
|
|
3382
|
-
if (approvers.cloud && creds?.apiKey)
|
|
3383
|
-
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3384
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3385
|
-
return { approved: true, checkedBy: "trust" };
|
|
3386
|
-
}
|
|
3387
3425
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
3388
3426
|
if (policyResult.decision === "allow") {
|
|
3389
3427
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -3465,6 +3503,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3465
3503
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
|
|
3466
3504
|
return { approved: true };
|
|
3467
3505
|
}
|
|
3506
|
+
if (!taintWarning && getActiveTrustSession(toolName, args)) {
|
|
3507
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3508
|
+
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3509
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3510
|
+
return { approved: true, checkedBy: "trust" };
|
|
3511
|
+
}
|
|
3468
3512
|
if (taintWarning) {
|
|
3469
3513
|
explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
|
|
3470
3514
|
riskMetadata = computeRiskMetadata(
|
|
@@ -3597,7 +3641,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3597
3641
|
riskMetadata?.ruleDescription
|
|
3598
3642
|
);
|
|
3599
3643
|
if (decision === "always_allow") {
|
|
3600
|
-
writeTrustSession(toolName, 36e5);
|
|
3644
|
+
writeTrustSession(toolName, 36e5, args);
|
|
3601
3645
|
return { approved: true, checkedBy: "trust" };
|
|
3602
3646
|
}
|
|
3603
3647
|
const isApproved = decision === "allow";
|
|
@@ -5775,7 +5819,7 @@ function writeGlobalSetting(key, value) {
|
|
|
5775
5819
|
config.settings[key] = value;
|
|
5776
5820
|
atomicWriteSync2(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5777
5821
|
}
|
|
5778
|
-
function writeTrustEntry(toolName, durationMs) {
|
|
5822
|
+
function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
5779
5823
|
try {
|
|
5780
5824
|
let trust = { entries: [] };
|
|
5781
5825
|
try {
|
|
@@ -5783,8 +5827,14 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5783
5827
|
trust = JSON.parse(fs14.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5784
5828
|
} catch {
|
|
5785
5829
|
}
|
|
5786
|
-
trust.entries = trust.entries.filter(
|
|
5787
|
-
|
|
5830
|
+
trust.entries = trust.entries.filter(
|
|
5831
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > Date.now()
|
|
5832
|
+
);
|
|
5833
|
+
trust.entries.push({
|
|
5834
|
+
tool: toolName,
|
|
5835
|
+
...commandPattern && { commandPattern },
|
|
5836
|
+
expiry: Date.now() + durationMs
|
|
5837
|
+
});
|
|
5788
5838
|
atomicWriteSync2(TRUST_FILE2, JSON.stringify(trust, null, 2));
|
|
5789
5839
|
} catch {
|
|
5790
5840
|
}
|
|
@@ -6713,7 +6763,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6713
6763
|
);
|
|
6714
6764
|
if (decision === "trust" && trustDuration) {
|
|
6715
6765
|
const ms = TRUST_DURATIONS[trustDuration] ?? 60 * 6e4;
|
|
6716
|
-
|
|
6766
|
+
const commandPattern = extractCommandPattern(entry.toolName, entry.args);
|
|
6767
|
+
writeTrustEntry(entry.toolName, ms, commandPattern);
|
|
6717
6768
|
appendAuditLog({
|
|
6718
6769
|
toolName: entry.toolName,
|
|
6719
6770
|
args: entry.args,
|
|
@@ -7161,6 +7212,7 @@ var init_server = __esm({
|
|
|
7161
7212
|
init_shields();
|
|
7162
7213
|
init_ui2();
|
|
7163
7214
|
init_state2();
|
|
7215
|
+
init_state();
|
|
7164
7216
|
init_patch();
|
|
7165
7217
|
init_config_schema();
|
|
7166
7218
|
init_costSync();
|
|
@@ -7494,10 +7546,10 @@ __export(tail_exports, {
|
|
|
7494
7546
|
startTail: () => startTail
|
|
7495
7547
|
});
|
|
7496
7548
|
import http2 from "http";
|
|
7497
|
-
import
|
|
7498
|
-
import
|
|
7499
|
-
import
|
|
7500
|
-
import
|
|
7549
|
+
import chalk24 from "chalk";
|
|
7550
|
+
import fs35 from "fs";
|
|
7551
|
+
import os31 from "os";
|
|
7552
|
+
import path38 from "path";
|
|
7501
7553
|
import readline5 from "readline";
|
|
7502
7554
|
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
7503
7555
|
function getIcon(tool) {
|
|
@@ -7520,22 +7572,22 @@ function formatBase(activity) {
|
|
|
7520
7572
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7521
7573
|
const icon = getIcon(activity.tool);
|
|
7522
7574
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7523
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7575
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os31.homedir(), "~");
|
|
7524
7576
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7525
|
-
return `${
|
|
7577
|
+
return `${chalk24.gray(time)} ${icon} ${chalk24.white.bold(toolName)} ${chalk24.dim(argsPreview)}`;
|
|
7526
7578
|
}
|
|
7527
7579
|
function renderResult(activity, result) {
|
|
7528
7580
|
const base = formatBase(activity);
|
|
7529
7581
|
let status;
|
|
7530
7582
|
if (result.status === "allow") {
|
|
7531
|
-
status =
|
|
7583
|
+
status = chalk24.green("\u2713 ALLOW");
|
|
7532
7584
|
} else if (result.status === "dlp") {
|
|
7533
|
-
status =
|
|
7585
|
+
status = chalk24.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7534
7586
|
} else {
|
|
7535
|
-
status =
|
|
7587
|
+
status = chalk24.red("\u2717 BLOCK");
|
|
7536
7588
|
}
|
|
7537
7589
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7538
|
-
const costSuffix = cost == null ? "" :
|
|
7590
|
+
const costSuffix = cost == null ? "" : chalk24.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7539
7591
|
if (process.stdout.isTTY) {
|
|
7540
7592
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7541
7593
|
readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7552,19 +7604,19 @@ function renderResult(activity, result) {
|
|
|
7552
7604
|
}
|
|
7553
7605
|
function renderPending(activity) {
|
|
7554
7606
|
if (!process.stdout.isTTY) return;
|
|
7555
|
-
const line = `${formatBase(activity)} ${
|
|
7607
|
+
const line = `${formatBase(activity)} ${chalk24.yellow("\u25CF \u2026")}`;
|
|
7556
7608
|
pendingShownForId = activity.id;
|
|
7557
7609
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7558
7610
|
process.stdout.write(`${line}\r`);
|
|
7559
7611
|
}
|
|
7560
7612
|
async function ensureDaemon() {
|
|
7561
7613
|
let pidPort = null;
|
|
7562
|
-
if (
|
|
7614
|
+
if (fs35.existsSync(PID_FILE)) {
|
|
7563
7615
|
try {
|
|
7564
|
-
const { port } = JSON.parse(
|
|
7616
|
+
const { port } = JSON.parse(fs35.readFileSync(PID_FILE, "utf-8"));
|
|
7565
7617
|
pidPort = port;
|
|
7566
7618
|
} catch {
|
|
7567
|
-
console.error(
|
|
7619
|
+
console.error(chalk24.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7568
7620
|
}
|
|
7569
7621
|
}
|
|
7570
7622
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7575,7 +7627,7 @@ async function ensureDaemon() {
|
|
|
7575
7627
|
if (res.ok) return checkPort;
|
|
7576
7628
|
} catch {
|
|
7577
7629
|
}
|
|
7578
|
-
console.log(
|
|
7630
|
+
console.log(chalk24.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
7579
7631
|
const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
|
|
7580
7632
|
detached: true,
|
|
7581
7633
|
stdio: "ignore",
|
|
@@ -7592,7 +7644,7 @@ async function ensureDaemon() {
|
|
|
7592
7644
|
} catch {
|
|
7593
7645
|
}
|
|
7594
7646
|
}
|
|
7595
|
-
console.error(
|
|
7647
|
+
console.error(chalk24.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7596
7648
|
process.exit(1);
|
|
7597
7649
|
}
|
|
7598
7650
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7713,9 +7765,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7713
7765
|
];
|
|
7714
7766
|
}
|
|
7715
7767
|
function readApproversFromDisk() {
|
|
7716
|
-
const configPath =
|
|
7768
|
+
const configPath = path38.join(os31.homedir(), ".node9", "config.json");
|
|
7717
7769
|
try {
|
|
7718
|
-
const raw = JSON.parse(
|
|
7770
|
+
const raw = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
|
|
7719
7771
|
const settings = raw.settings ?? {};
|
|
7720
7772
|
return settings.approvers ?? {};
|
|
7721
7773
|
} catch {
|
|
@@ -7726,20 +7778,20 @@ function approverStatusLine() {
|
|
|
7726
7778
|
const a = readApproversFromDisk();
|
|
7727
7779
|
const fmt = (label, key) => {
|
|
7728
7780
|
const on = a[key] !== false;
|
|
7729
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
7781
|
+
return `[${key[0]}]${label.slice(1)} ${on ? chalk24.green("\u2713") : chalk24.dim("\u2717")}`;
|
|
7730
7782
|
};
|
|
7731
7783
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7732
7784
|
}
|
|
7733
7785
|
function toggleApprover(channel) {
|
|
7734
|
-
const configPath =
|
|
7786
|
+
const configPath = path38.join(os31.homedir(), ".node9", "config.json");
|
|
7735
7787
|
try {
|
|
7736
|
-
const raw = JSON.parse(
|
|
7788
|
+
const raw = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
|
|
7737
7789
|
const settings = raw.settings ?? {};
|
|
7738
7790
|
const approvers = settings.approvers ?? {};
|
|
7739
7791
|
approvers[channel] = approvers[channel] === false;
|
|
7740
7792
|
settings.approvers = approvers;
|
|
7741
7793
|
raw.settings = settings;
|
|
7742
|
-
|
|
7794
|
+
fs35.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7743
7795
|
} catch (err2) {
|
|
7744
7796
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7745
7797
|
`);
|
|
@@ -7771,7 +7823,7 @@ async function startTail(options = {}) {
|
|
|
7771
7823
|
req2.end();
|
|
7772
7824
|
});
|
|
7773
7825
|
if (result.ok) {
|
|
7774
|
-
console.log(
|
|
7826
|
+
console.log(chalk24.green("\u2713 Flight Recorder buffer cleared."));
|
|
7775
7827
|
} else if (result.code === "ECONNREFUSED") {
|
|
7776
7828
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7777
7829
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7815,7 +7867,7 @@ async function startTail(options = {}) {
|
|
|
7815
7867
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7816
7868
|
if (channel) {
|
|
7817
7869
|
toggleApprover(channel);
|
|
7818
|
-
console.log(
|
|
7870
|
+
console.log(chalk24.dim(` Approvers: ${approverStatusLine()}`));
|
|
7819
7871
|
}
|
|
7820
7872
|
};
|
|
7821
7873
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7881,7 +7933,7 @@ async function startTail(options = {}) {
|
|
|
7881
7933
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7882
7934
|
)
|
|
7883
7935
|
);
|
|
7884
|
-
const decisionStamp = action === "always-allow" ?
|
|
7936
|
+
const decisionStamp = action === "always-allow" ? chalk24.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk24.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk24.green("\u2713 ALLOWED") : action === "redirect" ? chalk24.yellow("\u21A9 REDIRECT AI") : chalk24.red("\u2717 DENIED");
|
|
7885
7937
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7886
7938
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7887
7939
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7909,8 +7961,8 @@ async function startTail(options = {}) {
|
|
|
7909
7961
|
}
|
|
7910
7962
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7911
7963
|
try {
|
|
7912
|
-
|
|
7913
|
-
|
|
7964
|
+
fs35.appendFileSync(
|
|
7965
|
+
path38.join(os31.homedir(), ".node9", "hook-debug.log"),
|
|
7914
7966
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7915
7967
|
`
|
|
7916
7968
|
);
|
|
@@ -7932,7 +7984,7 @@ async function startTail(options = {}) {
|
|
|
7932
7984
|
);
|
|
7933
7985
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7934
7986
|
if (externalDecision) {
|
|
7935
|
-
const source = externalDecision === "allow" ?
|
|
7987
|
+
const source = externalDecision === "allow" ? chalk24.green("\u2713 ALLOWED") : chalk24.red("\u2717 DENIED");
|
|
7936
7988
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7937
7989
|
}
|
|
7938
7990
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7991,16 +8043,16 @@ async function startTail(options = {}) {
|
|
|
7991
8043
|
}
|
|
7992
8044
|
} catch {
|
|
7993
8045
|
}
|
|
7994
|
-
console.log(
|
|
7995
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
8046
|
+
console.log(chalk24.cyan.bold(`
|
|
8047
|
+
\u{1F6F0}\uFE0F Node9 tail `) + chalk24.dim(`\u2192 ${dashboardUrl}`));
|
|
7996
8048
|
if (canApprove) {
|
|
7997
|
-
console.log(
|
|
7998
|
-
console.log(
|
|
8049
|
+
console.log(chalk24.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
8050
|
+
console.log(chalk24.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
7999
8051
|
}
|
|
8000
8052
|
if (options.history) {
|
|
8001
|
-
console.log(
|
|
8053
|
+
console.log(chalk24.dim("Showing history + live events.\n"));
|
|
8002
8054
|
} else {
|
|
8003
|
-
console.log(
|
|
8055
|
+
console.log(chalk24.dim("Showing live events only. Use --history to include past.\n"));
|
|
8004
8056
|
}
|
|
8005
8057
|
process.on("SIGINT", () => {
|
|
8006
8058
|
exitIdleMode();
|
|
@@ -8010,13 +8062,13 @@ async function startTail(options = {}) {
|
|
|
8010
8062
|
readline5.clearLine(process.stdout, 0);
|
|
8011
8063
|
readline5.cursorTo(process.stdout, 0);
|
|
8012
8064
|
}
|
|
8013
|
-
console.log(
|
|
8065
|
+
console.log(chalk24.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
8014
8066
|
process.exit(0);
|
|
8015
8067
|
});
|
|
8016
8068
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
8017
8069
|
const req = http2.get(sseUrl, (res) => {
|
|
8018
8070
|
if (res.statusCode !== 200) {
|
|
8019
|
-
console.error(
|
|
8071
|
+
console.error(chalk24.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
8020
8072
|
process.exit(1);
|
|
8021
8073
|
}
|
|
8022
8074
|
if (canApprove) enterIdleMode();
|
|
@@ -8047,7 +8099,7 @@ async function startTail(options = {}) {
|
|
|
8047
8099
|
readline5.clearLine(process.stdout, 0);
|
|
8048
8100
|
readline5.cursorTo(process.stdout, 0);
|
|
8049
8101
|
}
|
|
8050
|
-
console.log(
|
|
8102
|
+
console.log(chalk24.red("\n\u274C Daemon disconnected."));
|
|
8051
8103
|
process.exit(1);
|
|
8052
8104
|
});
|
|
8053
8105
|
});
|
|
@@ -8139,9 +8191,9 @@ async function startTail(options = {}) {
|
|
|
8139
8191
|
const hash = data.hash ?? "";
|
|
8140
8192
|
const summary = data.argsSummary ?? data.tool;
|
|
8141
8193
|
const fileCount = data.fileCount ?? 0;
|
|
8142
|
-
const files = fileCount > 0 ?
|
|
8194
|
+
const files = fileCount > 0 ? chalk24.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
8143
8195
|
process.stdout.write(
|
|
8144
|
-
`${
|
|
8196
|
+
`${chalk24.dim(time)} ${chalk24.cyan("\u{1F4F8} snapshot")} ${chalk24.dim(hash)} ${summary}${files}
|
|
8145
8197
|
`
|
|
8146
8198
|
);
|
|
8147
8199
|
return;
|
|
@@ -8158,7 +8210,7 @@ async function startTail(options = {}) {
|
|
|
8158
8210
|
}
|
|
8159
8211
|
req.on("error", (err2) => {
|
|
8160
8212
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
8161
|
-
console.error(
|
|
8213
|
+
console.error(chalk24.red(`
|
|
8162
8214
|
\u274C ${msg}`));
|
|
8163
8215
|
process.exit(1);
|
|
8164
8216
|
});
|
|
@@ -8170,7 +8222,7 @@ var init_tail = __esm({
|
|
|
8170
8222
|
init_daemon2();
|
|
8171
8223
|
init_daemon();
|
|
8172
8224
|
init_core();
|
|
8173
|
-
PID_FILE =
|
|
8225
|
+
PID_FILE = path38.join(os31.homedir(), ".node9", "daemon.pid");
|
|
8174
8226
|
ICONS = {
|
|
8175
8227
|
bash: "\u{1F4BB}",
|
|
8176
8228
|
shell: "\u{1F4BB}",
|
|
@@ -8211,9 +8263,9 @@ __export(hud_exports, {
|
|
|
8211
8263
|
main: () => main,
|
|
8212
8264
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
8213
8265
|
});
|
|
8214
|
-
import
|
|
8215
|
-
import
|
|
8216
|
-
import
|
|
8266
|
+
import fs36 from "fs";
|
|
8267
|
+
import path39 from "path";
|
|
8268
|
+
import os32 from "os";
|
|
8217
8269
|
import http3 from "http";
|
|
8218
8270
|
async function readStdin() {
|
|
8219
8271
|
const chunks = [];
|
|
@@ -8289,9 +8341,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
8289
8341
|
return ` (${m}m left)`;
|
|
8290
8342
|
}
|
|
8291
8343
|
function safeReadJson(filePath) {
|
|
8292
|
-
if (!
|
|
8344
|
+
if (!fs36.existsSync(filePath)) return null;
|
|
8293
8345
|
try {
|
|
8294
|
-
return JSON.parse(
|
|
8346
|
+
return JSON.parse(fs36.readFileSync(filePath, "utf-8"));
|
|
8295
8347
|
} catch {
|
|
8296
8348
|
return null;
|
|
8297
8349
|
}
|
|
@@ -8312,12 +8364,12 @@ function countHooksInFile(filePath) {
|
|
|
8312
8364
|
return Object.keys(cfg.hooks).length;
|
|
8313
8365
|
}
|
|
8314
8366
|
function countRulesInDir(rulesDir) {
|
|
8315
|
-
if (!
|
|
8367
|
+
if (!fs36.existsSync(rulesDir)) return 0;
|
|
8316
8368
|
let count = 0;
|
|
8317
8369
|
try {
|
|
8318
|
-
for (const entry of
|
|
8370
|
+
for (const entry of fs36.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
8319
8371
|
if (entry.isDirectory()) {
|
|
8320
|
-
count += countRulesInDir(
|
|
8372
|
+
count += countRulesInDir(path39.join(rulesDir, entry.name));
|
|
8321
8373
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8322
8374
|
count++;
|
|
8323
8375
|
}
|
|
@@ -8328,46 +8380,46 @@ function countRulesInDir(rulesDir) {
|
|
|
8328
8380
|
}
|
|
8329
8381
|
function isSamePath(a, b) {
|
|
8330
8382
|
try {
|
|
8331
|
-
return
|
|
8383
|
+
return path39.resolve(a) === path39.resolve(b);
|
|
8332
8384
|
} catch {
|
|
8333
8385
|
return false;
|
|
8334
8386
|
}
|
|
8335
8387
|
}
|
|
8336
8388
|
function countConfigs(cwd) {
|
|
8337
|
-
const homeDir2 =
|
|
8338
|
-
const claudeDir =
|
|
8389
|
+
const homeDir2 = os32.homedir();
|
|
8390
|
+
const claudeDir = path39.join(homeDir2, ".claude");
|
|
8339
8391
|
let claudeMdCount = 0;
|
|
8340
8392
|
let rulesCount = 0;
|
|
8341
8393
|
let hooksCount = 0;
|
|
8342
8394
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
8343
8395
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
8344
|
-
if (
|
|
8345
|
-
rulesCount += countRulesInDir(
|
|
8346
|
-
const userSettings =
|
|
8396
|
+
if (fs36.existsSync(path39.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8397
|
+
rulesCount += countRulesInDir(path39.join(claudeDir, "rules"));
|
|
8398
|
+
const userSettings = path39.join(claudeDir, "settings.json");
|
|
8347
8399
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
8348
8400
|
hooksCount += countHooksInFile(userSettings);
|
|
8349
|
-
const userClaudeJson =
|
|
8401
|
+
const userClaudeJson = path39.join(homeDir2, ".claude.json");
|
|
8350
8402
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
8351
8403
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
8352
8404
|
userMcpServers.delete(name);
|
|
8353
8405
|
}
|
|
8354
8406
|
if (cwd) {
|
|
8355
|
-
if (
|
|
8356
|
-
if (
|
|
8357
|
-
const projectClaudeDir =
|
|
8407
|
+
if (fs36.existsSync(path39.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
8408
|
+
if (fs36.existsSync(path39.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8409
|
+
const projectClaudeDir = path39.join(cwd, ".claude");
|
|
8358
8410
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
8359
8411
|
if (!overlapsUserScope) {
|
|
8360
|
-
if (
|
|
8361
|
-
rulesCount += countRulesInDir(
|
|
8362
|
-
const projSettings =
|
|
8412
|
+
if (fs36.existsSync(path39.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8413
|
+
rulesCount += countRulesInDir(path39.join(projectClaudeDir, "rules"));
|
|
8414
|
+
const projSettings = path39.join(projectClaudeDir, "settings.json");
|
|
8363
8415
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
8364
8416
|
hooksCount += countHooksInFile(projSettings);
|
|
8365
8417
|
}
|
|
8366
|
-
if (
|
|
8367
|
-
const localSettings =
|
|
8418
|
+
if (fs36.existsSync(path39.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8419
|
+
const localSettings = path39.join(projectClaudeDir, "settings.local.json");
|
|
8368
8420
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
8369
8421
|
hooksCount += countHooksInFile(localSettings);
|
|
8370
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8422
|
+
const mcpJsonServers = getMcpServerNames(path39.join(cwd, ".mcp.json"));
|
|
8371
8423
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
8372
8424
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
8373
8425
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -8400,12 +8452,12 @@ function readActiveShieldsHud() {
|
|
|
8400
8452
|
return shieldsCache.value;
|
|
8401
8453
|
}
|
|
8402
8454
|
try {
|
|
8403
|
-
const shieldsPath =
|
|
8404
|
-
if (!
|
|
8455
|
+
const shieldsPath = path39.join(os32.homedir(), ".node9", "shields.json");
|
|
8456
|
+
if (!fs36.existsSync(shieldsPath)) {
|
|
8405
8457
|
shieldsCache = { value: [], ts: now };
|
|
8406
8458
|
return [];
|
|
8407
8459
|
}
|
|
8408
|
-
const parsed = JSON.parse(
|
|
8460
|
+
const parsed = JSON.parse(fs36.readFileSync(shieldsPath, "utf-8"));
|
|
8409
8461
|
if (!Array.isArray(parsed.active)) {
|
|
8410
8462
|
shieldsCache = { value: [], ts: now };
|
|
8411
8463
|
return [];
|
|
@@ -8507,17 +8559,17 @@ function renderContextLine(stdin) {
|
|
|
8507
8559
|
async function main() {
|
|
8508
8560
|
try {
|
|
8509
8561
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8510
|
-
if (
|
|
8562
|
+
if (fs36.existsSync(path39.join(os32.homedir(), ".node9", "hud-debug"))) {
|
|
8511
8563
|
try {
|
|
8512
|
-
const logPath =
|
|
8564
|
+
const logPath = path39.join(os32.homedir(), ".node9", "hud-debug.log");
|
|
8513
8565
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8514
8566
|
let size = 0;
|
|
8515
8567
|
try {
|
|
8516
|
-
size =
|
|
8568
|
+
size = fs36.statSync(logPath).size;
|
|
8517
8569
|
} catch {
|
|
8518
8570
|
}
|
|
8519
8571
|
if (size < MAX_LOG_SIZE) {
|
|
8520
|
-
|
|
8572
|
+
fs36.appendFileSync(
|
|
8521
8573
|
logPath,
|
|
8522
8574
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8523
8575
|
);
|
|
@@ -8538,11 +8590,11 @@ async function main() {
|
|
|
8538
8590
|
try {
|
|
8539
8591
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8540
8592
|
for (const configPath of [
|
|
8541
|
-
|
|
8542
|
-
|
|
8593
|
+
path39.join(cwd, "node9.config.json"),
|
|
8594
|
+
path39.join(os32.homedir(), ".node9", "config.json")
|
|
8543
8595
|
]) {
|
|
8544
|
-
if (!
|
|
8545
|
-
const cfg = JSON.parse(
|
|
8596
|
+
if (!fs36.existsSync(configPath)) continue;
|
|
8597
|
+
const cfg = JSON.parse(fs36.readFileSync(configPath, "utf-8"));
|
|
8546
8598
|
const hud = cfg.settings?.hud;
|
|
8547
8599
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8548
8600
|
}
|
|
@@ -9477,10 +9529,10 @@ function getAgentsStatus(homeDir2 = os11.homedir()) {
|
|
|
9477
9529
|
|
|
9478
9530
|
// src/cli.ts
|
|
9479
9531
|
init_daemon2();
|
|
9480
|
-
import
|
|
9481
|
-
import
|
|
9482
|
-
import
|
|
9483
|
-
import
|
|
9532
|
+
import chalk25 from "chalk";
|
|
9533
|
+
import fs37 from "fs";
|
|
9534
|
+
import path40 from "path";
|
|
9535
|
+
import os33 from "os";
|
|
9484
9536
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
9485
9537
|
|
|
9486
9538
|
// src/utils/duration.ts
|
|
@@ -9709,10 +9761,10 @@ init_daemon();
|
|
|
9709
9761
|
init_config();
|
|
9710
9762
|
init_policy();
|
|
9711
9763
|
import chalk5 from "chalk";
|
|
9712
|
-
import
|
|
9764
|
+
import fs23 from "fs";
|
|
9713
9765
|
import { spawn as spawn6 } from "child_process";
|
|
9714
|
-
import
|
|
9715
|
-
import
|
|
9766
|
+
import path25 from "path";
|
|
9767
|
+
import os19 from "os";
|
|
9716
9768
|
|
|
9717
9769
|
// src/undo.ts
|
|
9718
9770
|
import { spawnSync as spawnSync5, spawn as spawn5 } from "child_process";
|
|
@@ -10063,6 +10115,187 @@ function applyUndo(hash, cwd) {
|
|
|
10063
10115
|
}
|
|
10064
10116
|
}
|
|
10065
10117
|
|
|
10118
|
+
// src/skill-pin.ts
|
|
10119
|
+
import fs22 from "fs";
|
|
10120
|
+
import path24 from "path";
|
|
10121
|
+
import os18 from "os";
|
|
10122
|
+
import crypto4 from "crypto";
|
|
10123
|
+
function getPinsFilePath() {
|
|
10124
|
+
return path24.join(os18.homedir(), ".node9", "skill-pins.json");
|
|
10125
|
+
}
|
|
10126
|
+
var MAX_FILES = 5e3;
|
|
10127
|
+
var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
10128
|
+
function sha256Bytes(buf) {
|
|
10129
|
+
return crypto4.createHash("sha256").update(buf).digest("hex");
|
|
10130
|
+
}
|
|
10131
|
+
function walkDir(root) {
|
|
10132
|
+
const out = [];
|
|
10133
|
+
let totalBytes = 0;
|
|
10134
|
+
const visit = (dir, relDir) => {
|
|
10135
|
+
if (out.length >= MAX_FILES) return;
|
|
10136
|
+
let entries;
|
|
10137
|
+
try {
|
|
10138
|
+
entries = fs22.readdirSync(dir, { withFileTypes: true });
|
|
10139
|
+
} catch {
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
10142
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
10143
|
+
for (const entry of entries) {
|
|
10144
|
+
if (out.length >= MAX_FILES) return;
|
|
10145
|
+
const full = path24.join(dir, entry.name);
|
|
10146
|
+
const rel = relDir ? path24.posix.join(relDir, entry.name) : entry.name;
|
|
10147
|
+
let lst;
|
|
10148
|
+
try {
|
|
10149
|
+
lst = fs22.lstatSync(full);
|
|
10150
|
+
} catch {
|
|
10151
|
+
continue;
|
|
10152
|
+
}
|
|
10153
|
+
if (lst.isSymbolicLink()) continue;
|
|
10154
|
+
if (lst.isDirectory()) {
|
|
10155
|
+
visit(full, rel);
|
|
10156
|
+
continue;
|
|
10157
|
+
}
|
|
10158
|
+
if (!lst.isFile()) continue;
|
|
10159
|
+
if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
|
|
10160
|
+
try {
|
|
10161
|
+
const buf = fs22.readFileSync(full);
|
|
10162
|
+
totalBytes += buf.length;
|
|
10163
|
+
out.push({ rel, hash: sha256Bytes(buf) });
|
|
10164
|
+
} catch {
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
};
|
|
10168
|
+
visit(root, "");
|
|
10169
|
+
out.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
10170
|
+
return out.map((e) => `${e.rel}\0${e.hash}`);
|
|
10171
|
+
}
|
|
10172
|
+
function hashSkillRoot(absPath) {
|
|
10173
|
+
let lst;
|
|
10174
|
+
try {
|
|
10175
|
+
lst = fs22.lstatSync(absPath);
|
|
10176
|
+
} catch {
|
|
10177
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10178
|
+
}
|
|
10179
|
+
if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
|
|
10180
|
+
if (lst.isFile()) {
|
|
10181
|
+
try {
|
|
10182
|
+
return { exists: true, contentHash: sha256Bytes(fs22.readFileSync(absPath)), fileCount: 1 };
|
|
10183
|
+
} catch {
|
|
10184
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10185
|
+
}
|
|
10186
|
+
}
|
|
10187
|
+
if (lst.isDirectory()) {
|
|
10188
|
+
const entries = walkDir(absPath);
|
|
10189
|
+
const contentHash = crypto4.createHash("sha256").update(entries.join("\n")).digest("hex");
|
|
10190
|
+
return { exists: true, contentHash, fileCount: entries.length };
|
|
10191
|
+
}
|
|
10192
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10193
|
+
}
|
|
10194
|
+
function getRootKey(absPath) {
|
|
10195
|
+
return crypto4.createHash("sha256").update(absPath).digest("hex").slice(0, 16);
|
|
10196
|
+
}
|
|
10197
|
+
function readSkillPinsSafe() {
|
|
10198
|
+
const filePath = getPinsFilePath();
|
|
10199
|
+
try {
|
|
10200
|
+
const raw = fs22.readFileSync(filePath, "utf-8");
|
|
10201
|
+
if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10202
|
+
const parsed = JSON.parse(raw);
|
|
10203
|
+
if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
|
|
10204
|
+
return { ok: false, reason: "corrupt", detail: "invalid structure: missing roots object" };
|
|
10205
|
+
}
|
|
10206
|
+
return { ok: true, pins: { roots: parsed.roots } };
|
|
10207
|
+
} catch (err2) {
|
|
10208
|
+
if (err2.code === "ENOENT") return { ok: false, reason: "missing" };
|
|
10209
|
+
return { ok: false, reason: "corrupt", detail: String(err2) };
|
|
10210
|
+
}
|
|
10211
|
+
}
|
|
10212
|
+
function readSkillPins() {
|
|
10213
|
+
const result = readSkillPinsSafe();
|
|
10214
|
+
if (result.ok) return result.pins;
|
|
10215
|
+
if (result.reason === "missing") return { roots: {} };
|
|
10216
|
+
throw new Error(`[node9] skill pin file is corrupt: ${result.detail}`);
|
|
10217
|
+
}
|
|
10218
|
+
function writeSkillPins(data) {
|
|
10219
|
+
const filePath = getPinsFilePath();
|
|
10220
|
+
fs22.mkdirSync(path24.dirname(filePath), { recursive: true });
|
|
10221
|
+
const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
|
|
10222
|
+
fs22.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10223
|
+
fs22.renameSync(tmp, filePath);
|
|
10224
|
+
}
|
|
10225
|
+
function removePin(rootKey) {
|
|
10226
|
+
const pins = readSkillPins();
|
|
10227
|
+
delete pins.roots[rootKey];
|
|
10228
|
+
writeSkillPins(pins);
|
|
10229
|
+
}
|
|
10230
|
+
function clearAllPins() {
|
|
10231
|
+
writeSkillPins({ roots: {} });
|
|
10232
|
+
}
|
|
10233
|
+
function verifyAndPinRoots(roots) {
|
|
10234
|
+
const pinsRead = readSkillPinsSafe();
|
|
10235
|
+
if (!pinsRead.ok && pinsRead.reason === "corrupt") {
|
|
10236
|
+
return { kind: "corrupt", detail: pinsRead.detail };
|
|
10237
|
+
}
|
|
10238
|
+
const pins = pinsRead.ok ? pinsRead.pins : { roots: {} };
|
|
10239
|
+
let mutated = false;
|
|
10240
|
+
for (const rootPath of new Set(roots)) {
|
|
10241
|
+
const rootKey = getRootKey(rootPath);
|
|
10242
|
+
const current = hashSkillRoot(rootPath);
|
|
10243
|
+
const existing = pins.roots[rootKey];
|
|
10244
|
+
if (!existing) {
|
|
10245
|
+
pins.roots[rootKey] = {
|
|
10246
|
+
rootPath,
|
|
10247
|
+
exists: current.exists,
|
|
10248
|
+
contentHash: current.contentHash,
|
|
10249
|
+
fileCount: current.fileCount,
|
|
10250
|
+
pinnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10251
|
+
};
|
|
10252
|
+
mutated = true;
|
|
10253
|
+
continue;
|
|
10254
|
+
}
|
|
10255
|
+
if (existing.exists !== current.exists || existing.contentHash !== current.contentHash) {
|
|
10256
|
+
let summary;
|
|
10257
|
+
if (existing.exists && !current.exists) summary = `vanished: ${rootPath}`;
|
|
10258
|
+
else if (!existing.exists && current.exists) summary = `appeared: ${rootPath}`;
|
|
10259
|
+
else summary = `changed: ${rootPath}`;
|
|
10260
|
+
return { kind: "drift", changedRootKey: rootKey, changedRootPath: rootPath, summary };
|
|
10261
|
+
}
|
|
10262
|
+
}
|
|
10263
|
+
if (mutated) writeSkillPins(pins);
|
|
10264
|
+
return { kind: "verified" };
|
|
10265
|
+
}
|
|
10266
|
+
function defaultSkillRoots(_cwd) {
|
|
10267
|
+
const marketplaces = path24.join(os18.homedir(), ".claude", "plugins", "marketplaces");
|
|
10268
|
+
const roots = [];
|
|
10269
|
+
let registries;
|
|
10270
|
+
try {
|
|
10271
|
+
registries = fs22.readdirSync(marketplaces, { withFileTypes: true });
|
|
10272
|
+
} catch {
|
|
10273
|
+
return [];
|
|
10274
|
+
}
|
|
10275
|
+
for (const registry of registries) {
|
|
10276
|
+
if (!registry.isDirectory()) continue;
|
|
10277
|
+
const pluginsDir = path24.join(marketplaces, registry.name, "plugins");
|
|
10278
|
+
let plugins;
|
|
10279
|
+
try {
|
|
10280
|
+
plugins = fs22.readdirSync(pluginsDir, { withFileTypes: true });
|
|
10281
|
+
} catch {
|
|
10282
|
+
continue;
|
|
10283
|
+
}
|
|
10284
|
+
for (const plugin of plugins) {
|
|
10285
|
+
if (!plugin.isDirectory()) continue;
|
|
10286
|
+
roots.push(path24.join(pluginsDir, plugin.name));
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
10289
|
+
return roots;
|
|
10290
|
+
}
|
|
10291
|
+
function resolveUserSkillRoot(entry, cwd) {
|
|
10292
|
+
if (!entry) return null;
|
|
10293
|
+
if (entry.startsWith("~/") || entry === "~") return path24.join(os18.homedir(), entry.slice(1));
|
|
10294
|
+
if (path24.isAbsolute(entry)) return entry;
|
|
10295
|
+
if (!cwd || !path24.isAbsolute(cwd)) return null;
|
|
10296
|
+
return path24.join(cwd, entry);
|
|
10297
|
+
}
|
|
10298
|
+
|
|
10066
10299
|
// src/cli/commands/check.ts
|
|
10067
10300
|
function sanitize2(value) {
|
|
10068
10301
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
@@ -10078,9 +10311,9 @@ function registerCheckCommand(program2) {
|
|
|
10078
10311
|
} catch (err2) {
|
|
10079
10312
|
const tempConfig = getConfig();
|
|
10080
10313
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
10081
|
-
const logPath =
|
|
10314
|
+
const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
|
|
10082
10315
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10083
|
-
|
|
10316
|
+
fs23.appendFileSync(
|
|
10084
10317
|
logPath,
|
|
10085
10318
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
10086
10319
|
RAW: ${raw}
|
|
@@ -10093,11 +10326,11 @@ RAW: ${raw}
|
|
|
10093
10326
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
10094
10327
|
try {
|
|
10095
10328
|
const scriptPath = process.argv[1];
|
|
10096
|
-
if (typeof scriptPath !== "string" || !
|
|
10329
|
+
if (typeof scriptPath !== "string" || !path25.isAbsolute(scriptPath))
|
|
10097
10330
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
10098
|
-
const resolvedScript =
|
|
10099
|
-
const packageDist =
|
|
10100
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
10331
|
+
const resolvedScript = fs23.realpathSync(scriptPath);
|
|
10332
|
+
const packageDist = fs23.realpathSync(path25.resolve(__dirname, "../.."));
|
|
10333
|
+
if (!resolvedScript.startsWith(packageDist + path25.sep) && resolvedScript !== packageDist)
|
|
10101
10334
|
throw new Error(
|
|
10102
10335
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
10103
10336
|
);
|
|
@@ -10119,10 +10352,10 @@ RAW: ${raw}
|
|
|
10119
10352
|
});
|
|
10120
10353
|
d.unref();
|
|
10121
10354
|
} catch (spawnErr) {
|
|
10122
|
-
const logPath =
|
|
10355
|
+
const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
|
|
10123
10356
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
10124
10357
|
try {
|
|
10125
|
-
|
|
10358
|
+
fs23.appendFileSync(
|
|
10126
10359
|
logPath,
|
|
10127
10360
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
10128
10361
|
`
|
|
@@ -10132,10 +10365,10 @@ RAW: ${raw}
|
|
|
10132
10365
|
}
|
|
10133
10366
|
}
|
|
10134
10367
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
10135
|
-
const logPath =
|
|
10136
|
-
if (!
|
|
10137
|
-
|
|
10138
|
-
|
|
10368
|
+
const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
|
|
10369
|
+
if (!fs23.existsSync(path25.dirname(logPath)))
|
|
10370
|
+
fs23.mkdirSync(path25.dirname(logPath), { recursive: true });
|
|
10371
|
+
fs23.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
10139
10372
|
`);
|
|
10140
10373
|
}
|
|
10141
10374
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -10148,8 +10381,8 @@ RAW: ${raw}
|
|
|
10148
10381
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
10149
10382
|
let ttyFd = null;
|
|
10150
10383
|
try {
|
|
10151
|
-
ttyFd =
|
|
10152
|
-
const writeTty = (line) =>
|
|
10384
|
+
ttyFd = fs23.openSync("/dev/tty", "w");
|
|
10385
|
+
const writeTty = (line) => fs23.writeSync(ttyFd, line + "\n");
|
|
10153
10386
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
10154
10387
|
writeTty(chalk5.bgRed.white.bold(`
|
|
10155
10388
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -10168,7 +10401,7 @@ RAW: ${raw}
|
|
|
10168
10401
|
} finally {
|
|
10169
10402
|
if (ttyFd !== null)
|
|
10170
10403
|
try {
|
|
10171
|
-
|
|
10404
|
+
fs23.closeSync(ttyFd);
|
|
10172
10405
|
} catch {
|
|
10173
10406
|
}
|
|
10174
10407
|
}
|
|
@@ -10197,10 +10430,131 @@ RAW: ${raw}
|
|
|
10197
10430
|
return;
|
|
10198
10431
|
}
|
|
10199
10432
|
const meta = { agent, mcpServer };
|
|
10433
|
+
const skillPinCfg = config.policy.skillPinning;
|
|
10434
|
+
const rawSessionId = typeof payload.session_id === "string" ? payload.session_id : "";
|
|
10435
|
+
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
10436
|
+
if (skillPinCfg.enabled && safeSessionId) {
|
|
10437
|
+
try {
|
|
10438
|
+
const sessionsDir = path25.join(os19.homedir(), ".node9", "skill-sessions");
|
|
10439
|
+
const flagPath = path25.join(sessionsDir, `${safeSessionId}.json`);
|
|
10440
|
+
let flag = null;
|
|
10441
|
+
try {
|
|
10442
|
+
flag = JSON.parse(fs23.readFileSync(flagPath, "utf-8"));
|
|
10443
|
+
} catch {
|
|
10444
|
+
}
|
|
10445
|
+
const writeFlag = (data2) => {
|
|
10446
|
+
try {
|
|
10447
|
+
fs23.mkdirSync(sessionsDir, { recursive: true });
|
|
10448
|
+
fs23.writeFileSync(
|
|
10449
|
+
flagPath,
|
|
10450
|
+
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
10451
|
+
{ mode: 384 }
|
|
10452
|
+
);
|
|
10453
|
+
} catch {
|
|
10454
|
+
}
|
|
10455
|
+
};
|
|
10456
|
+
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
10457
|
+
let ttyFd = null;
|
|
10458
|
+
try {
|
|
10459
|
+
ttyFd = fs23.openSync("/dev/tty", "w");
|
|
10460
|
+
const w = (line) => fs23.writeSync(ttyFd, line + "\n");
|
|
10461
|
+
w(chalk5.yellow(`
|
|
10462
|
+
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
10463
|
+
w(chalk5.gray(` ${detail}`));
|
|
10464
|
+
w(
|
|
10465
|
+
chalk5.gray(
|
|
10466
|
+
` If you updated a plugin, acknowledge the change to clear this warning.`
|
|
10467
|
+
)
|
|
10468
|
+
);
|
|
10469
|
+
if (recoveryCmd) w(chalk5.green(` \u{1F4A1} Run: ${recoveryCmd}`));
|
|
10470
|
+
w("");
|
|
10471
|
+
} catch {
|
|
10472
|
+
} finally {
|
|
10473
|
+
if (ttyFd !== null)
|
|
10474
|
+
try {
|
|
10475
|
+
fs23.closeSync(ttyFd);
|
|
10476
|
+
} catch {
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
};
|
|
10480
|
+
if (flag && flag.state === "quarantined" && skillPinCfg.mode === "block") {
|
|
10481
|
+
sendBlock(
|
|
10482
|
+
`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.`,
|
|
10483
|
+
{
|
|
10484
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10485
|
+
recoveryCommand: "node9 skill pin list"
|
|
10486
|
+
}
|
|
10487
|
+
);
|
|
10488
|
+
return;
|
|
10489
|
+
}
|
|
10490
|
+
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
10491
|
+
const absoluteCwd = typeof payload.cwd === "string" && path25.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10492
|
+
const extraRoots = skillPinCfg.roots;
|
|
10493
|
+
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
10494
|
+
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
10495
|
+
const result2 = verifyAndPinRoots(roots);
|
|
10496
|
+
if (result2.kind === "corrupt") {
|
|
10497
|
+
if (skillPinCfg.mode === "block") {
|
|
10498
|
+
writeFlag({
|
|
10499
|
+
state: "quarantined",
|
|
10500
|
+
detail: `pin file corrupt: ${result2.detail}`
|
|
10501
|
+
});
|
|
10502
|
+
sendBlock("Node9: skill pin file is corrupt \u2014 fail-closed.", {
|
|
10503
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10504
|
+
recoveryCommand: "node9 skill pin reset"
|
|
10505
|
+
});
|
|
10506
|
+
return;
|
|
10507
|
+
}
|
|
10508
|
+
writeFlag({ state: "warned", detail: `pin file corrupt: ${result2.detail}` });
|
|
10509
|
+
sendSkillWarn(
|
|
10510
|
+
`Skill pin file is corrupt: ${result2.detail}`,
|
|
10511
|
+
"node9 skill pin reset"
|
|
10512
|
+
);
|
|
10513
|
+
} else if (result2.kind === "drift") {
|
|
10514
|
+
if (skillPinCfg.mode === "block") {
|
|
10515
|
+
writeFlag({ state: "quarantined", detail: result2.summary });
|
|
10516
|
+
sendBlock(
|
|
10517
|
+
`Node9: installed skill changed \u2014 ${result2.summary}. If you updated a plugin, open a separate terminal and run: node9 skill pin update ${result2.changedRootKey}`,
|
|
10518
|
+
{
|
|
10519
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10520
|
+
recoveryCommand: `node9 skill pin update ${result2.changedRootKey}`
|
|
10521
|
+
}
|
|
10522
|
+
);
|
|
10523
|
+
return;
|
|
10524
|
+
}
|
|
10525
|
+
writeFlag({ state: "warned", detail: result2.summary });
|
|
10526
|
+
sendSkillWarn(result2.summary, `node9 skill pin update ${result2.changedRootKey}`);
|
|
10527
|
+
} else {
|
|
10528
|
+
writeFlag({ state: "verified" });
|
|
10529
|
+
}
|
|
10530
|
+
try {
|
|
10531
|
+
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
10532
|
+
for (const name of fs23.readdirSync(sessionsDir)) {
|
|
10533
|
+
const p = path25.join(sessionsDir, name);
|
|
10534
|
+
try {
|
|
10535
|
+
if (fs23.statSync(p).mtimeMs < cutoff) fs23.unlinkSync(p);
|
|
10536
|
+
} catch {
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
} catch {
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
10542
|
+
} catch (err2) {
|
|
10543
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
10544
|
+
try {
|
|
10545
|
+
const dbg = path25.join(os19.homedir(), ".node9", "hook-debug.log");
|
|
10546
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10547
|
+
fs23.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
10548
|
+
`);
|
|
10549
|
+
} catch {
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
}
|
|
10200
10554
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
10201
10555
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
10202
10556
|
}
|
|
10203
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10557
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && path25.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10204
10558
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
10205
10559
|
cwd: safeCwdForAuth
|
|
10206
10560
|
});
|
|
@@ -10212,12 +10566,12 @@ RAW: ${raw}
|
|
|
10212
10566
|
}
|
|
10213
10567
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
10214
10568
|
try {
|
|
10215
|
-
const tty =
|
|
10216
|
-
|
|
10569
|
+
const tty = fs23.openSync("/dev/tty", "w");
|
|
10570
|
+
fs23.writeSync(
|
|
10217
10571
|
tty,
|
|
10218
10572
|
chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
10219
10573
|
);
|
|
10220
|
-
|
|
10574
|
+
fs23.closeSync(tty);
|
|
10221
10575
|
} catch {
|
|
10222
10576
|
}
|
|
10223
10577
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -10244,9 +10598,9 @@ RAW: ${raw}
|
|
|
10244
10598
|
});
|
|
10245
10599
|
} catch (err2) {
|
|
10246
10600
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10247
|
-
const logPath =
|
|
10601
|
+
const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
|
|
10248
10602
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10249
|
-
|
|
10603
|
+
fs23.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
10250
10604
|
`);
|
|
10251
10605
|
}
|
|
10252
10606
|
process.exit(0);
|
|
@@ -10283,9 +10637,9 @@ RAW: ${raw}
|
|
|
10283
10637
|
init_audit();
|
|
10284
10638
|
init_config();
|
|
10285
10639
|
init_policy();
|
|
10286
|
-
import
|
|
10287
|
-
import
|
|
10288
|
-
import
|
|
10640
|
+
import fs24 from "fs";
|
|
10641
|
+
import path26 from "path";
|
|
10642
|
+
import os20 from "os";
|
|
10289
10643
|
init_daemon();
|
|
10290
10644
|
|
|
10291
10645
|
// src/utils/cp-mv-parser.ts
|
|
@@ -10358,10 +10712,10 @@ function registerLogCommand(program2) {
|
|
|
10358
10712
|
decision: "allowed",
|
|
10359
10713
|
source: "post-hook"
|
|
10360
10714
|
};
|
|
10361
|
-
const logPath =
|
|
10362
|
-
if (!
|
|
10363
|
-
|
|
10364
|
-
|
|
10715
|
+
const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
|
|
10716
|
+
if (!fs24.existsSync(path26.dirname(logPath)))
|
|
10717
|
+
fs24.mkdirSync(path26.dirname(logPath), { recursive: true });
|
|
10718
|
+
fs24.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
10365
10719
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
10366
10720
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
10367
10721
|
if (command) {
|
|
@@ -10394,7 +10748,7 @@ function registerLogCommand(program2) {
|
|
|
10394
10748
|
}
|
|
10395
10749
|
}
|
|
10396
10750
|
}
|
|
10397
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
10751
|
+
const safeCwd = typeof payload.cwd === "string" && path26.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10398
10752
|
const config = getConfig(safeCwd);
|
|
10399
10753
|
if (shouldSnapshot(tool, {}, config)) {
|
|
10400
10754
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -10403,9 +10757,9 @@ function registerLogCommand(program2) {
|
|
|
10403
10757
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10404
10758
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
10405
10759
|
`);
|
|
10406
|
-
const debugPath =
|
|
10760
|
+
const debugPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
|
|
10407
10761
|
try {
|
|
10408
|
-
|
|
10762
|
+
fs24.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
10409
10763
|
`);
|
|
10410
10764
|
} catch {
|
|
10411
10765
|
}
|
|
@@ -10806,13 +11160,13 @@ function registerConfigShowCommand(program2) {
|
|
|
10806
11160
|
// src/cli/commands/doctor.ts
|
|
10807
11161
|
init_daemon();
|
|
10808
11162
|
import chalk7 from "chalk";
|
|
10809
|
-
import
|
|
10810
|
-
import
|
|
10811
|
-
import
|
|
11163
|
+
import fs25 from "fs";
|
|
11164
|
+
import path27 from "path";
|
|
11165
|
+
import os21 from "os";
|
|
10812
11166
|
import { execSync as execSync2 } from "child_process";
|
|
10813
11167
|
function registerDoctorCommand(program2, version2) {
|
|
10814
11168
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
10815
|
-
const homeDir2 =
|
|
11169
|
+
const homeDir2 = os21.homedir();
|
|
10816
11170
|
let failures = 0;
|
|
10817
11171
|
function pass(msg) {
|
|
10818
11172
|
console.log(chalk7.green(" \u2705 ") + msg);
|
|
@@ -10861,10 +11215,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10861
11215
|
);
|
|
10862
11216
|
}
|
|
10863
11217
|
section("Configuration");
|
|
10864
|
-
const globalConfigPath =
|
|
10865
|
-
if (
|
|
11218
|
+
const globalConfigPath = path27.join(homeDir2, ".node9", "config.json");
|
|
11219
|
+
if (fs25.existsSync(globalConfigPath)) {
|
|
10866
11220
|
try {
|
|
10867
|
-
JSON.parse(
|
|
11221
|
+
JSON.parse(fs25.readFileSync(globalConfigPath, "utf-8"));
|
|
10868
11222
|
pass("~/.node9/config.json found and valid");
|
|
10869
11223
|
} catch {
|
|
10870
11224
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -10872,10 +11226,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10872
11226
|
} else {
|
|
10873
11227
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
10874
11228
|
}
|
|
10875
|
-
const projectConfigPath =
|
|
10876
|
-
if (
|
|
11229
|
+
const projectConfigPath = path27.join(process.cwd(), "node9.config.json");
|
|
11230
|
+
if (fs25.existsSync(projectConfigPath)) {
|
|
10877
11231
|
try {
|
|
10878
|
-
JSON.parse(
|
|
11232
|
+
JSON.parse(fs25.readFileSync(projectConfigPath, "utf-8"));
|
|
10879
11233
|
pass("node9.config.json found and valid (project)");
|
|
10880
11234
|
} catch {
|
|
10881
11235
|
fail(
|
|
@@ -10884,8 +11238,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10884
11238
|
);
|
|
10885
11239
|
}
|
|
10886
11240
|
}
|
|
10887
|
-
const credsPath =
|
|
10888
|
-
if (
|
|
11241
|
+
const credsPath = path27.join(homeDir2, ".node9", "credentials.json");
|
|
11242
|
+
if (fs25.existsSync(credsPath)) {
|
|
10889
11243
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
10890
11244
|
} else {
|
|
10891
11245
|
warn(
|
|
@@ -10894,10 +11248,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10894
11248
|
);
|
|
10895
11249
|
}
|
|
10896
11250
|
section("Agent Hooks");
|
|
10897
|
-
const claudeSettingsPath =
|
|
10898
|
-
if (
|
|
11251
|
+
const claudeSettingsPath = path27.join(homeDir2, ".claude", "settings.json");
|
|
11252
|
+
if (fs25.existsSync(claudeSettingsPath)) {
|
|
10899
11253
|
try {
|
|
10900
|
-
const cs = JSON.parse(
|
|
11254
|
+
const cs = JSON.parse(fs25.readFileSync(claudeSettingsPath, "utf-8"));
|
|
10901
11255
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
10902
11256
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10903
11257
|
);
|
|
@@ -10913,10 +11267,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10913
11267
|
} else {
|
|
10914
11268
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
10915
11269
|
}
|
|
10916
|
-
const geminiSettingsPath =
|
|
10917
|
-
if (
|
|
11270
|
+
const geminiSettingsPath = path27.join(homeDir2, ".gemini", "settings.json");
|
|
11271
|
+
if (fs25.existsSync(geminiSettingsPath)) {
|
|
10918
11272
|
try {
|
|
10919
|
-
const gs = JSON.parse(
|
|
11273
|
+
const gs = JSON.parse(fs25.readFileSync(geminiSettingsPath, "utf-8"));
|
|
10920
11274
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
10921
11275
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10922
11276
|
);
|
|
@@ -10932,10 +11286,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10932
11286
|
} else {
|
|
10933
11287
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
10934
11288
|
}
|
|
10935
|
-
const cursorHooksPath =
|
|
10936
|
-
if (
|
|
11289
|
+
const cursorHooksPath = path27.join(homeDir2, ".cursor", "hooks.json");
|
|
11290
|
+
if (fs25.existsSync(cursorHooksPath)) {
|
|
10937
11291
|
try {
|
|
10938
|
-
const cur = JSON.parse(
|
|
11292
|
+
const cur = JSON.parse(fs25.readFileSync(cursorHooksPath, "utf-8"));
|
|
10939
11293
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
10940
11294
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
10941
11295
|
);
|
|
@@ -10973,9 +11327,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10973
11327
|
|
|
10974
11328
|
// src/cli/commands/audit.ts
|
|
10975
11329
|
import chalk8 from "chalk";
|
|
10976
|
-
import
|
|
10977
|
-
import
|
|
10978
|
-
import
|
|
11330
|
+
import fs26 from "fs";
|
|
11331
|
+
import path28 from "path";
|
|
11332
|
+
import os22 from "os";
|
|
10979
11333
|
function formatRelativeTime(timestamp) {
|
|
10980
11334
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
10981
11335
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -10988,14 +11342,14 @@ function formatRelativeTime(timestamp) {
|
|
|
10988
11342
|
}
|
|
10989
11343
|
function registerAuditCommand(program2) {
|
|
10990
11344
|
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) => {
|
|
10991
|
-
const logPath =
|
|
10992
|
-
if (!
|
|
11345
|
+
const logPath = path28.join(os22.homedir(), ".node9", "audit.log");
|
|
11346
|
+
if (!fs26.existsSync(logPath)) {
|
|
10993
11347
|
console.log(
|
|
10994
11348
|
chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
10995
11349
|
);
|
|
10996
11350
|
return;
|
|
10997
11351
|
}
|
|
10998
|
-
const raw =
|
|
11352
|
+
const raw = fs26.readFileSync(logPath, "utf-8");
|
|
10999
11353
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
11000
11354
|
let entries = lines.flatMap((line) => {
|
|
11001
11355
|
try {
|
|
@@ -11049,9 +11403,9 @@ function registerAuditCommand(program2) {
|
|
|
11049
11403
|
|
|
11050
11404
|
// src/cli/commands/report.ts
|
|
11051
11405
|
import chalk9 from "chalk";
|
|
11052
|
-
import
|
|
11053
|
-
import
|
|
11054
|
-
import
|
|
11406
|
+
import fs27 from "fs";
|
|
11407
|
+
import path29 from "path";
|
|
11408
|
+
import os23 from "os";
|
|
11055
11409
|
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;
|
|
11056
11410
|
function buildTestTimestamps(allEntries) {
|
|
11057
11411
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -11098,8 +11452,8 @@ function getDateRange(period) {
|
|
|
11098
11452
|
}
|
|
11099
11453
|
}
|
|
11100
11454
|
function parseAuditLog(logPath) {
|
|
11101
|
-
if (!
|
|
11102
|
-
const raw =
|
|
11455
|
+
if (!fs27.existsSync(logPath)) return [];
|
|
11456
|
+
const raw = fs27.readFileSync(logPath, "utf-8");
|
|
11103
11457
|
return raw.split("\n").flatMap((line) => {
|
|
11104
11458
|
if (!line.trim()) return [];
|
|
11105
11459
|
try {
|
|
@@ -11168,11 +11522,11 @@ function loadClaudeCost(start, end) {
|
|
|
11168
11522
|
inputTokens: 0,
|
|
11169
11523
|
cacheReadTokens: 0
|
|
11170
11524
|
};
|
|
11171
|
-
const projectsDir =
|
|
11172
|
-
if (!
|
|
11525
|
+
const projectsDir = path29.join(os23.homedir(), ".claude", "projects");
|
|
11526
|
+
if (!fs27.existsSync(projectsDir)) return empty;
|
|
11173
11527
|
let dirs;
|
|
11174
11528
|
try {
|
|
11175
|
-
dirs =
|
|
11529
|
+
dirs = fs27.readdirSync(projectsDir);
|
|
11176
11530
|
} catch {
|
|
11177
11531
|
return empty;
|
|
11178
11532
|
}
|
|
@@ -11182,18 +11536,18 @@ function loadClaudeCost(start, end) {
|
|
|
11182
11536
|
const byDay = /* @__PURE__ */ new Map();
|
|
11183
11537
|
const byModel = /* @__PURE__ */ new Map();
|
|
11184
11538
|
for (const proj of dirs) {
|
|
11185
|
-
const projPath =
|
|
11539
|
+
const projPath = path29.join(projectsDir, proj);
|
|
11186
11540
|
let files;
|
|
11187
11541
|
try {
|
|
11188
|
-
const stat =
|
|
11542
|
+
const stat = fs27.statSync(projPath);
|
|
11189
11543
|
if (!stat.isDirectory()) continue;
|
|
11190
|
-
files =
|
|
11544
|
+
files = fs27.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
11191
11545
|
} catch {
|
|
11192
11546
|
continue;
|
|
11193
11547
|
}
|
|
11194
11548
|
for (const file of files) {
|
|
11195
11549
|
try {
|
|
11196
|
-
const raw =
|
|
11550
|
+
const raw = fs27.readFileSync(path29.join(projPath, file), "utf-8");
|
|
11197
11551
|
for (const line of raw.split("\n")) {
|
|
11198
11552
|
if (!line.trim()) continue;
|
|
11199
11553
|
let entry;
|
|
@@ -11236,7 +11590,7 @@ function registerReportCommand(program2) {
|
|
|
11236
11590
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
11237
11591
|
options.period
|
|
11238
11592
|
) ? options.period : "7d";
|
|
11239
|
-
const logPath =
|
|
11593
|
+
const logPath = path29.join(os23.homedir(), ".node9", "audit.log");
|
|
11240
11594
|
const allEntries = parseAuditLog(logPath);
|
|
11241
11595
|
if (allEntries.length === 0) {
|
|
11242
11596
|
console.log(
|
|
@@ -11591,12 +11945,12 @@ function registerDaemonCommand(program2) {
|
|
|
11591
11945
|
init_core();
|
|
11592
11946
|
init_daemon();
|
|
11593
11947
|
import chalk11 from "chalk";
|
|
11594
|
-
import
|
|
11595
|
-
import
|
|
11596
|
-
import
|
|
11948
|
+
import fs28 from "fs";
|
|
11949
|
+
import path30 from "path";
|
|
11950
|
+
import os24 from "os";
|
|
11597
11951
|
function readJson2(filePath) {
|
|
11598
11952
|
try {
|
|
11599
|
-
if (
|
|
11953
|
+
if (fs28.existsSync(filePath)) return JSON.parse(fs28.readFileSync(filePath, "utf-8"));
|
|
11600
11954
|
} catch {
|
|
11601
11955
|
}
|
|
11602
11956
|
return null;
|
|
@@ -11661,28 +12015,28 @@ function registerStatusCommand(program2) {
|
|
|
11661
12015
|
console.log("");
|
|
11662
12016
|
const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
|
|
11663
12017
|
console.log(` Mode: ${modeLabel}`);
|
|
11664
|
-
const projectConfig =
|
|
11665
|
-
const globalConfig =
|
|
12018
|
+
const projectConfig = path30.join(process.cwd(), "node9.config.json");
|
|
12019
|
+
const globalConfig = path30.join(os24.homedir(), ".node9", "config.json");
|
|
11666
12020
|
console.log(
|
|
11667
|
-
` Local: ${
|
|
12021
|
+
` Local: ${fs28.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
|
|
11668
12022
|
);
|
|
11669
12023
|
console.log(
|
|
11670
|
-
` Global: ${
|
|
12024
|
+
` Global: ${fs28.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
|
|
11671
12025
|
);
|
|
11672
12026
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
11673
12027
|
console.log(
|
|
11674
12028
|
` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
11675
12029
|
);
|
|
11676
12030
|
}
|
|
11677
|
-
const homeDir2 =
|
|
12031
|
+
const homeDir2 = os24.homedir();
|
|
11678
12032
|
const claudeSettings = readJson2(
|
|
11679
|
-
|
|
12033
|
+
path30.join(homeDir2, ".claude", "settings.json")
|
|
11680
12034
|
);
|
|
11681
|
-
const claudeConfig = readJson2(
|
|
12035
|
+
const claudeConfig = readJson2(path30.join(homeDir2, ".claude.json"));
|
|
11682
12036
|
const geminiSettings = readJson2(
|
|
11683
|
-
|
|
12037
|
+
path30.join(homeDir2, ".gemini", "settings.json")
|
|
11684
12038
|
);
|
|
11685
|
-
const cursorConfig = readJson2(
|
|
12039
|
+
const cursorConfig = readJson2(path30.join(homeDir2, ".cursor", "mcp.json"));
|
|
11686
12040
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
11687
12041
|
if (agentFound) {
|
|
11688
12042
|
console.log("");
|
|
@@ -11742,9 +12096,9 @@ function registerStatusCommand(program2) {
|
|
|
11742
12096
|
// src/cli/commands/init.ts
|
|
11743
12097
|
init_core();
|
|
11744
12098
|
import chalk12 from "chalk";
|
|
11745
|
-
import
|
|
11746
|
-
import
|
|
11747
|
-
import
|
|
12099
|
+
import fs29 from "fs";
|
|
12100
|
+
import path31 from "path";
|
|
12101
|
+
import os25 from "os";
|
|
11748
12102
|
import https3 from "https";
|
|
11749
12103
|
init_shields();
|
|
11750
12104
|
init_service();
|
|
@@ -11804,15 +12158,15 @@ function registerInitCommand(program2) {
|
|
|
11804
12158
|
}
|
|
11805
12159
|
console.log("");
|
|
11806
12160
|
}
|
|
11807
|
-
const configPath =
|
|
11808
|
-
if (
|
|
12161
|
+
const configPath = path31.join(os25.homedir(), ".node9", "config.json");
|
|
12162
|
+
if (fs29.existsSync(configPath) && !options.force) {
|
|
11809
12163
|
try {
|
|
11810
|
-
const existing = JSON.parse(
|
|
12164
|
+
const existing = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
|
|
11811
12165
|
const settings = existing.settings ?? {};
|
|
11812
12166
|
if (settings.mode !== chosenMode) {
|
|
11813
12167
|
settings.mode = chosenMode;
|
|
11814
12168
|
existing.settings = settings;
|
|
11815
|
-
|
|
12169
|
+
fs29.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
11816
12170
|
console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
11817
12171
|
} else {
|
|
11818
12172
|
console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -11825,9 +12179,9 @@ function registerInitCommand(program2) {
|
|
|
11825
12179
|
...DEFAULT_CONFIG,
|
|
11826
12180
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
11827
12181
|
};
|
|
11828
|
-
const dir =
|
|
11829
|
-
if (!
|
|
11830
|
-
|
|
12182
|
+
const dir = path31.dirname(configPath);
|
|
12183
|
+
if (!fs29.existsSync(dir)) fs29.mkdirSync(dir, { recursive: true });
|
|
12184
|
+
fs29.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
11831
12185
|
console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
|
|
11832
12186
|
console.log(chalk12.gray(` Mode: ${chosenMode}`));
|
|
11833
12187
|
}
|
|
@@ -11911,7 +12265,7 @@ function registerInitCommand(program2) {
|
|
|
11911
12265
|
}
|
|
11912
12266
|
|
|
11913
12267
|
// src/cli/commands/undo.ts
|
|
11914
|
-
import
|
|
12268
|
+
import path32 from "path";
|
|
11915
12269
|
import chalk14 from "chalk";
|
|
11916
12270
|
|
|
11917
12271
|
// src/tui/undo-navigator.ts
|
|
@@ -12070,7 +12424,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
12070
12424
|
let dir = startDir;
|
|
12071
12425
|
while (true) {
|
|
12072
12426
|
if (cwds.has(dir)) return dir;
|
|
12073
|
-
const parent =
|
|
12427
|
+
const parent = path32.dirname(dir);
|
|
12074
12428
|
if (parent === dir) return null;
|
|
12075
12429
|
dir = parent;
|
|
12076
12430
|
}
|
|
@@ -12266,12 +12620,12 @@ import { execa as execa2 } from "execa";
|
|
|
12266
12620
|
init_provenance();
|
|
12267
12621
|
|
|
12268
12622
|
// src/mcp-pin.ts
|
|
12269
|
-
import
|
|
12270
|
-
import
|
|
12271
|
-
import
|
|
12272
|
-
import
|
|
12273
|
-
function
|
|
12274
|
-
return
|
|
12623
|
+
import fs30 from "fs";
|
|
12624
|
+
import path33 from "path";
|
|
12625
|
+
import os26 from "os";
|
|
12626
|
+
import crypto5 from "crypto";
|
|
12627
|
+
function getPinsFilePath2() {
|
|
12628
|
+
return path33.join(os26.homedir(), ".node9", "mcp-pins.json");
|
|
12275
12629
|
}
|
|
12276
12630
|
function hashToolDefinitions(tools) {
|
|
12277
12631
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -12280,15 +12634,15 @@ function hashToolDefinitions(tools) {
|
|
|
12280
12634
|
return nameA.localeCompare(nameB);
|
|
12281
12635
|
});
|
|
12282
12636
|
const canonical = JSON.stringify(sorted);
|
|
12283
|
-
return
|
|
12637
|
+
return crypto5.createHash("sha256").update(canonical).digest("hex");
|
|
12284
12638
|
}
|
|
12285
12639
|
function getServerKey(upstreamCommand) {
|
|
12286
|
-
return
|
|
12640
|
+
return crypto5.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
12287
12641
|
}
|
|
12288
12642
|
function readMcpPinsSafe() {
|
|
12289
|
-
const filePath =
|
|
12643
|
+
const filePath = getPinsFilePath2();
|
|
12290
12644
|
try {
|
|
12291
|
-
const raw =
|
|
12645
|
+
const raw = fs30.readFileSync(filePath, "utf-8");
|
|
12292
12646
|
if (!raw.trim()) {
|
|
12293
12647
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
12294
12648
|
}
|
|
@@ -12311,11 +12665,11 @@ function readMcpPins() {
|
|
|
12311
12665
|
throw new Error(`[node9] MCP pin file is corrupt: ${result.detail}`);
|
|
12312
12666
|
}
|
|
12313
12667
|
function writeMcpPins(data) {
|
|
12314
|
-
const filePath =
|
|
12315
|
-
|
|
12316
|
-
const tmp = `${filePath}.${
|
|
12317
|
-
|
|
12318
|
-
|
|
12668
|
+
const filePath = getPinsFilePath2();
|
|
12669
|
+
fs30.mkdirSync(path33.dirname(filePath), { recursive: true });
|
|
12670
|
+
const tmp = `${filePath}.${crypto5.randomBytes(6).toString("hex")}.tmp`;
|
|
12671
|
+
fs30.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
12672
|
+
fs30.renameSync(tmp, filePath);
|
|
12319
12673
|
}
|
|
12320
12674
|
function checkPin(serverKey, currentHash) {
|
|
12321
12675
|
const result = readMcpPinsSafe();
|
|
@@ -12338,12 +12692,12 @@ function updatePin(serverKey, label, toolsHash, toolNames) {
|
|
|
12338
12692
|
};
|
|
12339
12693
|
writeMcpPins(pins);
|
|
12340
12694
|
}
|
|
12341
|
-
function
|
|
12695
|
+
function removePin2(serverKey) {
|
|
12342
12696
|
const pins = readMcpPins();
|
|
12343
12697
|
delete pins.servers[serverKey];
|
|
12344
12698
|
writeMcpPins(pins);
|
|
12345
12699
|
}
|
|
12346
|
-
function
|
|
12700
|
+
function clearAllPins2() {
|
|
12347
12701
|
writeMcpPins({ servers: {} });
|
|
12348
12702
|
}
|
|
12349
12703
|
|
|
@@ -12687,9 +13041,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
12687
13041
|
|
|
12688
13042
|
// src/mcp-server/index.ts
|
|
12689
13043
|
import readline4 from "readline";
|
|
12690
|
-
import
|
|
12691
|
-
import
|
|
12692
|
-
import
|
|
13044
|
+
import fs31 from "fs";
|
|
13045
|
+
import os27 from "os";
|
|
13046
|
+
import path34 from "path";
|
|
12693
13047
|
init_core();
|
|
12694
13048
|
init_daemon();
|
|
12695
13049
|
init_shields();
|
|
@@ -12864,13 +13218,13 @@ function handleStatus() {
|
|
|
12864
13218
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
12865
13219
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
12866
13220
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
12867
|
-
const projectConfig =
|
|
12868
|
-
const globalConfig =
|
|
13221
|
+
const projectConfig = path34.join(process.cwd(), "node9.config.json");
|
|
13222
|
+
const globalConfig = path34.join(os27.homedir(), ".node9", "config.json");
|
|
12869
13223
|
lines.push(
|
|
12870
|
-
`Project config (node9.config.json): ${
|
|
13224
|
+
`Project config (node9.config.json): ${fs31.existsSync(projectConfig) ? "present" : "not found"}`
|
|
12871
13225
|
);
|
|
12872
13226
|
lines.push(
|
|
12873
|
-
`Global config (~/.node9/config.json): ${
|
|
13227
|
+
`Global config (~/.node9/config.json): ${fs31.existsSync(globalConfig) ? "present" : "not found"}`
|
|
12874
13228
|
);
|
|
12875
13229
|
return lines.join("\n");
|
|
12876
13230
|
}
|
|
@@ -12944,21 +13298,21 @@ function handleShieldDisable(args) {
|
|
|
12944
13298
|
writeActiveShields(active.filter((s) => s !== name));
|
|
12945
13299
|
return `Shield "${name}" disabled.`;
|
|
12946
13300
|
}
|
|
12947
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
13301
|
+
var GLOBAL_CONFIG_PATH2 = path34.join(os27.homedir(), ".node9", "config.json");
|
|
12948
13302
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
12949
13303
|
function readGlobalConfigRaw() {
|
|
12950
13304
|
try {
|
|
12951
|
-
if (
|
|
12952
|
-
return JSON.parse(
|
|
13305
|
+
if (fs31.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
13306
|
+
return JSON.parse(fs31.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
12953
13307
|
}
|
|
12954
13308
|
} catch {
|
|
12955
13309
|
}
|
|
12956
13310
|
return {};
|
|
12957
13311
|
}
|
|
12958
13312
|
function writeGlobalConfigRaw(data) {
|
|
12959
|
-
const dir =
|
|
12960
|
-
if (!
|
|
12961
|
-
|
|
13313
|
+
const dir = path34.dirname(GLOBAL_CONFIG_PATH2);
|
|
13314
|
+
if (!fs31.existsSync(dir)) fs31.mkdirSync(dir, { recursive: true });
|
|
13315
|
+
fs31.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
12962
13316
|
}
|
|
12963
13317
|
function handleApproverList() {
|
|
12964
13318
|
const config = getConfig();
|
|
@@ -13001,9 +13355,9 @@ function handleApproverSet(args) {
|
|
|
13001
13355
|
}
|
|
13002
13356
|
function handleAuditGet(args) {
|
|
13003
13357
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
13004
|
-
const auditPath =
|
|
13005
|
-
if (!
|
|
13006
|
-
const lines =
|
|
13358
|
+
const auditPath = path34.join(os27.homedir(), ".node9", "audit.log");
|
|
13359
|
+
if (!fs31.existsSync(auditPath)) return "No audit log found.";
|
|
13360
|
+
const lines = fs31.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
13007
13361
|
const recent = lines.slice(-limit);
|
|
13008
13362
|
const entries = recent.map((line) => {
|
|
13009
13363
|
try {
|
|
@@ -13301,7 +13655,7 @@ function registerMcpPinCommand(program2) {
|
|
|
13301
13655
|
process.exit(1);
|
|
13302
13656
|
}
|
|
13303
13657
|
const label = pins.servers[serverKey].label;
|
|
13304
|
-
|
|
13658
|
+
removePin2(serverKey);
|
|
13305
13659
|
console.log(chalk18.green(`
|
|
13306
13660
|
\u{1F513} Pin removed for ${chalk18.cyan(serverKey)}`));
|
|
13307
13661
|
console.log(chalk18.gray(` Server: ${label}`));
|
|
@@ -13314,7 +13668,7 @@ function registerMcpPinCommand(program2) {
|
|
|
13314
13668
|
return;
|
|
13315
13669
|
}
|
|
13316
13670
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
13317
|
-
|
|
13671
|
+
clearAllPins2();
|
|
13318
13672
|
console.log(chalk18.green(`
|
|
13319
13673
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
13320
13674
|
console.log(chalk18.gray(" Next connection to each server will re-pin.\n"));
|
|
@@ -13465,9 +13819,9 @@ init_config();
|
|
|
13465
13819
|
init_policy();
|
|
13466
13820
|
init_dlp();
|
|
13467
13821
|
import chalk21 from "chalk";
|
|
13468
|
-
import
|
|
13469
|
-
import
|
|
13470
|
-
import
|
|
13822
|
+
import fs32 from "fs";
|
|
13823
|
+
import path35 from "path";
|
|
13824
|
+
import os28 from "os";
|
|
13471
13825
|
var CLAUDE_PRICING2 = {
|
|
13472
13826
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13473
13827
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -13535,7 +13889,7 @@ function buildRuleSources() {
|
|
|
13535
13889
|
return sources;
|
|
13536
13890
|
}
|
|
13537
13891
|
function scanClaudeHistory(startDate) {
|
|
13538
|
-
const projectsDir =
|
|
13892
|
+
const projectsDir = path35.join(os28.homedir(), ".claude", "projects");
|
|
13539
13893
|
const result = {
|
|
13540
13894
|
filesScanned: 0,
|
|
13541
13895
|
sessions: 0,
|
|
@@ -13547,25 +13901,25 @@ function scanClaudeHistory(startDate) {
|
|
|
13547
13901
|
firstDate: null,
|
|
13548
13902
|
lastDate: null
|
|
13549
13903
|
};
|
|
13550
|
-
if (!
|
|
13904
|
+
if (!fs32.existsSync(projectsDir)) return result;
|
|
13551
13905
|
let projDirs;
|
|
13552
13906
|
try {
|
|
13553
|
-
projDirs =
|
|
13907
|
+
projDirs = fs32.readdirSync(projectsDir);
|
|
13554
13908
|
} catch {
|
|
13555
13909
|
return result;
|
|
13556
13910
|
}
|
|
13557
13911
|
const ruleSources = buildRuleSources();
|
|
13558
13912
|
for (const proj of projDirs) {
|
|
13559
|
-
const projPath =
|
|
13913
|
+
const projPath = path35.join(projectsDir, proj);
|
|
13560
13914
|
try {
|
|
13561
|
-
if (!
|
|
13915
|
+
if (!fs32.statSync(projPath).isDirectory()) continue;
|
|
13562
13916
|
} catch {
|
|
13563
13917
|
continue;
|
|
13564
13918
|
}
|
|
13565
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
13919
|
+
const projLabel = decodeURIComponent(proj).replace(os28.homedir(), "~").slice(0, 40);
|
|
13566
13920
|
let files;
|
|
13567
13921
|
try {
|
|
13568
|
-
files =
|
|
13922
|
+
files = fs32.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
13569
13923
|
} catch {
|
|
13570
13924
|
continue;
|
|
13571
13925
|
}
|
|
@@ -13574,7 +13928,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13574
13928
|
result.sessions++;
|
|
13575
13929
|
let raw;
|
|
13576
13930
|
try {
|
|
13577
|
-
raw =
|
|
13931
|
+
raw = fs32.readFileSync(path35.join(projPath, file), "utf-8");
|
|
13578
13932
|
} catch {
|
|
13579
13933
|
continue;
|
|
13580
13934
|
}
|
|
@@ -13667,8 +14021,8 @@ function registerScanCommand(program2) {
|
|
|
13667
14021
|
console.log("");
|
|
13668
14022
|
console.log(chalk21.cyan.bold("\u{1F50D} node9 scan") + chalk21.dim(" \u2014 what would node9 catch?"));
|
|
13669
14023
|
console.log("");
|
|
13670
|
-
const projectsDir =
|
|
13671
|
-
if (!
|
|
14024
|
+
const projectsDir = path35.join(os28.homedir(), ".claude", "projects");
|
|
14025
|
+
if (!fs32.existsSync(projectsDir)) {
|
|
13672
14026
|
console.log(chalk21.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
13673
14027
|
console.log(chalk21.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
13674
14028
|
return;
|
|
@@ -13780,8 +14134,8 @@ function registerScanCommand(program2) {
|
|
|
13780
14134
|
);
|
|
13781
14135
|
console.log("");
|
|
13782
14136
|
}
|
|
13783
|
-
const auditLog =
|
|
13784
|
-
if (
|
|
14137
|
+
const auditLog = path35.join(os28.homedir(), ".node9", "audit.log");
|
|
14138
|
+
if (fs32.existsSync(auditLog)) {
|
|
13785
14139
|
console.log(chalk21.green(" \u2705 node9 is active \u2014 future sessions are protected."));
|
|
13786
14140
|
console.log(
|
|
13787
14141
|
chalk21.dim(" Run ") + chalk21.cyan("node9 report") + chalk21.dim(" to see live stats.")
|
|
@@ -13798,9 +14152,9 @@ function registerScanCommand(program2) {
|
|
|
13798
14152
|
|
|
13799
14153
|
// src/cli/commands/sessions.ts
|
|
13800
14154
|
import chalk22 from "chalk";
|
|
13801
|
-
import
|
|
13802
|
-
import
|
|
13803
|
-
import
|
|
14155
|
+
import fs33 from "fs";
|
|
14156
|
+
import path36 from "path";
|
|
14157
|
+
import os29 from "os";
|
|
13804
14158
|
var CLAUDE_PRICING3 = {
|
|
13805
14159
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13806
14160
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -13825,10 +14179,10 @@ function encodeProjectPath(projectPath) {
|
|
|
13825
14179
|
}
|
|
13826
14180
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
13827
14181
|
const encoded = encodeProjectPath(projectPath);
|
|
13828
|
-
return
|
|
14182
|
+
return path36.join(os29.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
13829
14183
|
}
|
|
13830
14184
|
function projectLabel(projectPath) {
|
|
13831
|
-
return projectPath.replace(
|
|
14185
|
+
return projectPath.replace(os29.homedir(), "~");
|
|
13832
14186
|
}
|
|
13833
14187
|
function parseHistoryLines(lines) {
|
|
13834
14188
|
const entries = [];
|
|
@@ -13897,10 +14251,10 @@ function parseSessionLines(lines) {
|
|
|
13897
14251
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
13898
14252
|
}
|
|
13899
14253
|
function loadAuditEntries(auditPath) {
|
|
13900
|
-
const aPath = auditPath ??
|
|
14254
|
+
const aPath = auditPath ?? path36.join(os29.homedir(), ".node9", "audit.log");
|
|
13901
14255
|
let raw;
|
|
13902
14256
|
try {
|
|
13903
|
-
raw =
|
|
14257
|
+
raw = fs33.readFileSync(aPath, "utf-8");
|
|
13904
14258
|
} catch {
|
|
13905
14259
|
return [];
|
|
13906
14260
|
}
|
|
@@ -13936,10 +14290,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
13936
14290
|
return result;
|
|
13937
14291
|
}
|
|
13938
14292
|
function buildSessions(days, historyPath) {
|
|
13939
|
-
const hPath = historyPath ??
|
|
14293
|
+
const hPath = historyPath ?? path36.join(os29.homedir(), ".claude", "history.jsonl");
|
|
13940
14294
|
let historyRaw;
|
|
13941
14295
|
try {
|
|
13942
|
-
historyRaw =
|
|
14296
|
+
historyRaw = fs33.readFileSync(hPath, "utf-8");
|
|
13943
14297
|
} catch {
|
|
13944
14298
|
return [];
|
|
13945
14299
|
}
|
|
@@ -13964,7 +14318,7 @@ function buildSessions(days, historyPath) {
|
|
|
13964
14318
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
13965
14319
|
let sessionLines = [];
|
|
13966
14320
|
try {
|
|
13967
|
-
sessionLines =
|
|
14321
|
+
sessionLines = fs33.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
13968
14322
|
} catch {
|
|
13969
14323
|
}
|
|
13970
14324
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -14215,8 +14569,8 @@ function registerSessionsCommand(program2) {
|
|
|
14215
14569
|
console.log("");
|
|
14216
14570
|
console.log(chalk22.cyan.bold("\u{1F4CB} node9 sessions") + chalk22.dim(" \u2014 what your AI agent did"));
|
|
14217
14571
|
console.log("");
|
|
14218
|
-
const historyPath =
|
|
14219
|
-
if (!
|
|
14572
|
+
const historyPath = path36.join(os29.homedir(), ".claude", "history.jsonl");
|
|
14573
|
+
if (!fs33.existsSync(historyPath)) {
|
|
14220
14574
|
console.log(chalk22.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
14221
14575
|
console.log(chalk22.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14222
14576
|
return;
|
|
@@ -14246,22 +14600,110 @@ function registerSessionsCommand(program2) {
|
|
|
14246
14600
|
});
|
|
14247
14601
|
}
|
|
14248
14602
|
|
|
14603
|
+
// src/cli/commands/skill-pin.ts
|
|
14604
|
+
import chalk23 from "chalk";
|
|
14605
|
+
import fs34 from "fs";
|
|
14606
|
+
import os30 from "os";
|
|
14607
|
+
import path37 from "path";
|
|
14608
|
+
function wipeSkillSessions() {
|
|
14609
|
+
try {
|
|
14610
|
+
fs34.rmSync(path37.join(os30.homedir(), ".node9", "skill-sessions"), {
|
|
14611
|
+
recursive: true,
|
|
14612
|
+
force: true
|
|
14613
|
+
});
|
|
14614
|
+
} catch {
|
|
14615
|
+
}
|
|
14616
|
+
}
|
|
14617
|
+
function registerSkillPinCommand(program2) {
|
|
14618
|
+
const skillCmd = program2.command("skill").description("Manage skill pinning (supply chain & update drift defense, AST 02 + AST 07)");
|
|
14619
|
+
const pinSubCmd = skillCmd.command("pin").description("Manage pinned skill roots");
|
|
14620
|
+
pinSubCmd.command("list").description("Show all pinned skill roots and their content hashes").action(() => {
|
|
14621
|
+
const result = readSkillPinsSafe();
|
|
14622
|
+
if (!result.ok) {
|
|
14623
|
+
if (result.reason === "missing") {
|
|
14624
|
+
console.log(chalk23.gray("\nNo skill roots are pinned yet."));
|
|
14625
|
+
console.log(
|
|
14626
|
+
chalk23.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
14627
|
+
);
|
|
14628
|
+
return;
|
|
14629
|
+
}
|
|
14630
|
+
console.error(chalk23.red(`
|
|
14631
|
+
\u274C Pin file is corrupt: ${result.detail}`));
|
|
14632
|
+
console.error(chalk23.yellow(" Run: node9 skill pin reset\n"));
|
|
14633
|
+
process.exit(1);
|
|
14634
|
+
}
|
|
14635
|
+
const entries = Object.entries(result.pins.roots);
|
|
14636
|
+
if (entries.length === 0) {
|
|
14637
|
+
console.log(chalk23.gray("\nNo skill roots are pinned yet.\n"));
|
|
14638
|
+
return;
|
|
14639
|
+
}
|
|
14640
|
+
console.log(chalk23.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
14641
|
+
for (const [key, entry] of entries) {
|
|
14642
|
+
const missing = entry.exists ? "" : chalk23.yellow(" (not present at pin time)");
|
|
14643
|
+
console.log(` ${chalk23.cyan(key)} ${chalk23.gray(entry.rootPath)}${missing}`);
|
|
14644
|
+
console.log(` Files (${entry.fileCount})`);
|
|
14645
|
+
console.log(` Hash: ${chalk23.gray(entry.contentHash.slice(0, 16))}...`);
|
|
14646
|
+
console.log(` Pinned: ${chalk23.gray(entry.pinnedAt)}
|
|
14647
|
+
`);
|
|
14648
|
+
}
|
|
14649
|
+
});
|
|
14650
|
+
pinSubCmd.command("update <rootKey>").description("Remove a pin so the next session re-pins with current state").action((rootKey) => {
|
|
14651
|
+
let pins;
|
|
14652
|
+
try {
|
|
14653
|
+
pins = readSkillPins();
|
|
14654
|
+
} catch {
|
|
14655
|
+
console.error(chalk23.red("\n\u274C Pin file is corrupt."));
|
|
14656
|
+
console.error(chalk23.yellow(" Run: node9 skill pin reset\n"));
|
|
14657
|
+
process.exit(1);
|
|
14658
|
+
}
|
|
14659
|
+
if (!pins.roots[rootKey]) {
|
|
14660
|
+
console.error(chalk23.red(`
|
|
14661
|
+
\u274C No pin found for root key "${rootKey}"
|
|
14662
|
+
`));
|
|
14663
|
+
console.error(`Run ${chalk23.cyan("node9 skill pin list")} to see pinned roots.
|
|
14664
|
+
`);
|
|
14665
|
+
process.exit(1);
|
|
14666
|
+
}
|
|
14667
|
+
const rootPath = pins.roots[rootKey].rootPath;
|
|
14668
|
+
removePin(rootKey);
|
|
14669
|
+
wipeSkillSessions();
|
|
14670
|
+
console.log(chalk23.green(`
|
|
14671
|
+
\u{1F513} Pin removed for ${chalk23.cyan(rootKey)}`));
|
|
14672
|
+
console.log(chalk23.gray(` ${rootPath}`));
|
|
14673
|
+
console.log(chalk23.gray(" Next session will re-pin with current state.\n"));
|
|
14674
|
+
});
|
|
14675
|
+
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
14676
|
+
const result = readSkillPinsSafe();
|
|
14677
|
+
if (!result.ok && result.reason === "missing") {
|
|
14678
|
+
wipeSkillSessions();
|
|
14679
|
+
console.log(chalk23.gray("\nNo pins to clear.\n"));
|
|
14680
|
+
return;
|
|
14681
|
+
}
|
|
14682
|
+
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
14683
|
+
clearAllPins();
|
|
14684
|
+
wipeSkillSessions();
|
|
14685
|
+
console.log(chalk23.green(`
|
|
14686
|
+
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
14687
|
+
console.log(chalk23.gray(" Next session will re-pin with current state.\n"));
|
|
14688
|
+
});
|
|
14689
|
+
}
|
|
14690
|
+
|
|
14249
14691
|
// src/cli.ts
|
|
14250
14692
|
var { version } = JSON.parse(
|
|
14251
|
-
|
|
14693
|
+
fs37.readFileSync(path40.join(__dirname, "../package.json"), "utf-8")
|
|
14252
14694
|
);
|
|
14253
14695
|
var program = new Command();
|
|
14254
14696
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
14255
14697
|
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) => {
|
|
14256
14698
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
14257
|
-
const credPath =
|
|
14258
|
-
if (!
|
|
14259
|
-
|
|
14699
|
+
const credPath = path40.join(os33.homedir(), ".node9", "credentials.json");
|
|
14700
|
+
if (!fs37.existsSync(path40.dirname(credPath)))
|
|
14701
|
+
fs37.mkdirSync(path40.dirname(credPath), { recursive: true });
|
|
14260
14702
|
const profileName = options.profile || "default";
|
|
14261
14703
|
let existingCreds = {};
|
|
14262
14704
|
try {
|
|
14263
|
-
if (
|
|
14264
|
-
const raw = JSON.parse(
|
|
14705
|
+
if (fs37.existsSync(credPath)) {
|
|
14706
|
+
const raw = JSON.parse(fs37.readFileSync(credPath, "utf-8"));
|
|
14265
14707
|
if (raw.apiKey) {
|
|
14266
14708
|
existingCreds = {
|
|
14267
14709
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -14273,13 +14715,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14273
14715
|
} catch {
|
|
14274
14716
|
}
|
|
14275
14717
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14276
|
-
|
|
14718
|
+
fs37.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
14277
14719
|
if (profileName === "default") {
|
|
14278
|
-
const configPath =
|
|
14720
|
+
const configPath = path40.join(os33.homedir(), ".node9", "config.json");
|
|
14279
14721
|
let config = {};
|
|
14280
14722
|
try {
|
|
14281
|
-
if (
|
|
14282
|
-
config = JSON.parse(
|
|
14723
|
+
if (fs37.existsSync(configPath))
|
|
14724
|
+
config = JSON.parse(fs37.readFileSync(configPath, "utf-8"));
|
|
14283
14725
|
} catch {
|
|
14284
14726
|
}
|
|
14285
14727
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -14294,19 +14736,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14294
14736
|
approvers.cloud = false;
|
|
14295
14737
|
}
|
|
14296
14738
|
s.approvers = approvers;
|
|
14297
|
-
if (!
|
|
14298
|
-
|
|
14299
|
-
|
|
14739
|
+
if (!fs37.existsSync(path40.dirname(configPath)))
|
|
14740
|
+
fs37.mkdirSync(path40.dirname(configPath), { recursive: true });
|
|
14741
|
+
fs37.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
14300
14742
|
}
|
|
14301
14743
|
if (options.profile && profileName !== "default") {
|
|
14302
|
-
console.log(
|
|
14303
|
-
console.log(
|
|
14744
|
+
console.log(chalk25.green(`\u2705 Profile "${profileName}" saved`));
|
|
14745
|
+
console.log(chalk25.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
14304
14746
|
} else if (options.local) {
|
|
14305
|
-
console.log(
|
|
14306
|
-
console.log(
|
|
14747
|
+
console.log(chalk25.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
14748
|
+
console.log(chalk25.gray(` All decisions stay on this machine.`));
|
|
14307
14749
|
} else {
|
|
14308
|
-
console.log(
|
|
14309
|
-
console.log(
|
|
14750
|
+
console.log(chalk25.green(`\u2705 Logged in \u2014 agent mode`));
|
|
14751
|
+
console.log(chalk25.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
14310
14752
|
}
|
|
14311
14753
|
});
|
|
14312
14754
|
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) => {
|
|
@@ -14317,7 +14759,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
14317
14759
|
if (target === "vscode") return await setupVSCode();
|
|
14318
14760
|
if (target === "hud") return setupHud();
|
|
14319
14761
|
console.error(
|
|
14320
|
-
|
|
14762
|
+
chalk25.red(
|
|
14321
14763
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14322
14764
|
)
|
|
14323
14765
|
);
|
|
@@ -14325,16 +14767,16 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
14325
14767
|
});
|
|
14326
14768
|
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) => {
|
|
14327
14769
|
if (!target) {
|
|
14328
|
-
console.log(
|
|
14329
|
-
console.log(" Usage: " +
|
|
14770
|
+
console.log(chalk25.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
14771
|
+
console.log(" Usage: " + chalk25.white("node9 setup <target>") + "\n");
|
|
14330
14772
|
console.log(" Targets:");
|
|
14331
|
-
console.log(" " +
|
|
14332
|
-
console.log(" " +
|
|
14333
|
-
console.log(" " +
|
|
14334
|
-
console.log(" " +
|
|
14335
|
-
console.log(" " +
|
|
14773
|
+
console.log(" " + chalk25.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
14774
|
+
console.log(" " + chalk25.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
14775
|
+
console.log(" " + chalk25.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
14776
|
+
console.log(" " + chalk25.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
14777
|
+
console.log(" " + chalk25.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
14336
14778
|
process.stdout.write(
|
|
14337
|
-
" " +
|
|
14779
|
+
" " + chalk25.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
14338
14780
|
);
|
|
14339
14781
|
console.log("");
|
|
14340
14782
|
return;
|
|
@@ -14347,7 +14789,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
14347
14789
|
if (t === "vscode") return await setupVSCode();
|
|
14348
14790
|
if (t === "hud") return setupHud();
|
|
14349
14791
|
console.error(
|
|
14350
|
-
|
|
14792
|
+
chalk25.red(
|
|
14351
14793
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14352
14794
|
)
|
|
14353
14795
|
);
|
|
@@ -14366,33 +14808,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
14366
14808
|
else if (target === "hud") fn = teardownHud;
|
|
14367
14809
|
else {
|
|
14368
14810
|
console.error(
|
|
14369
|
-
|
|
14811
|
+
chalk25.red(
|
|
14370
14812
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
14371
14813
|
)
|
|
14372
14814
|
);
|
|
14373
14815
|
process.exit(1);
|
|
14374
14816
|
}
|
|
14375
|
-
console.log(
|
|
14817
|
+
console.log(chalk25.cyan(`
|
|
14376
14818
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
14377
14819
|
`));
|
|
14378
14820
|
try {
|
|
14379
14821
|
fn();
|
|
14380
14822
|
} catch (err2) {
|
|
14381
|
-
console.error(
|
|
14823
|
+
console.error(chalk25.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14382
14824
|
process.exit(1);
|
|
14383
14825
|
}
|
|
14384
|
-
console.log(
|
|
14826
|
+
console.log(chalk25.gray("\n Restart the agent for changes to take effect."));
|
|
14385
14827
|
});
|
|
14386
14828
|
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) => {
|
|
14387
|
-
console.log(
|
|
14388
|
-
console.log(
|
|
14829
|
+
console.log(chalk25.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
14830
|
+
console.log(chalk25.bold("Stopping daemon..."));
|
|
14389
14831
|
try {
|
|
14390
14832
|
stopDaemon();
|
|
14391
|
-
console.log(
|
|
14833
|
+
console.log(chalk25.green(" \u2705 Daemon stopped"));
|
|
14392
14834
|
} catch {
|
|
14393
|
-
console.log(
|
|
14835
|
+
console.log(chalk25.blue(" \u2139\uFE0F Daemon was not running"));
|
|
14394
14836
|
}
|
|
14395
|
-
console.log(
|
|
14837
|
+
console.log(chalk25.bold("\nRemoving hooks..."));
|
|
14396
14838
|
let teardownFailed = false;
|
|
14397
14839
|
for (const [label, fn] of [
|
|
14398
14840
|
["Claude", teardownClaude],
|
|
@@ -14406,45 +14848,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
14406
14848
|
} catch (err2) {
|
|
14407
14849
|
teardownFailed = true;
|
|
14408
14850
|
console.error(
|
|
14409
|
-
|
|
14851
|
+
chalk25.red(
|
|
14410
14852
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
14411
14853
|
)
|
|
14412
14854
|
);
|
|
14413
14855
|
}
|
|
14414
14856
|
}
|
|
14415
14857
|
if (options.purge) {
|
|
14416
|
-
const node9Dir =
|
|
14417
|
-
if (
|
|
14858
|
+
const node9Dir = path40.join(os33.homedir(), ".node9");
|
|
14859
|
+
if (fs37.existsSync(node9Dir)) {
|
|
14418
14860
|
const confirmed = await confirm2({
|
|
14419
14861
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
14420
14862
|
default: false
|
|
14421
14863
|
});
|
|
14422
14864
|
if (confirmed) {
|
|
14423
|
-
|
|
14424
|
-
if (
|
|
14865
|
+
fs37.rmSync(node9Dir, { recursive: true });
|
|
14866
|
+
if (fs37.existsSync(node9Dir)) {
|
|
14425
14867
|
console.error(
|
|
14426
|
-
|
|
14868
|
+
chalk25.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
14427
14869
|
);
|
|
14428
14870
|
} else {
|
|
14429
|
-
console.log(
|
|
14871
|
+
console.log(chalk25.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
14430
14872
|
}
|
|
14431
14873
|
} else {
|
|
14432
|
-
console.log(
|
|
14874
|
+
console.log(chalk25.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
14433
14875
|
}
|
|
14434
14876
|
} else {
|
|
14435
|
-
console.log(
|
|
14877
|
+
console.log(chalk25.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
14436
14878
|
}
|
|
14437
14879
|
} else {
|
|
14438
14880
|
console.log(
|
|
14439
|
-
|
|
14881
|
+
chalk25.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
14440
14882
|
);
|
|
14441
14883
|
}
|
|
14442
14884
|
if (teardownFailed) {
|
|
14443
|
-
console.error(
|
|
14885
|
+
console.error(chalk25.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
14444
14886
|
process.exit(1);
|
|
14445
14887
|
}
|
|
14446
|
-
console.log(
|
|
14447
|
-
console.log(
|
|
14888
|
+
console.log(chalk25.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
14889
|
+
console.log(chalk25.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
14448
14890
|
});
|
|
14449
14891
|
registerDoctorCommand(program, version);
|
|
14450
14892
|
program.command("explain").description(
|
|
@@ -14457,7 +14899,7 @@ program.command("explain").description(
|
|
|
14457
14899
|
try {
|
|
14458
14900
|
args = JSON.parse(trimmed);
|
|
14459
14901
|
} catch {
|
|
14460
|
-
console.error(
|
|
14902
|
+
console.error(chalk25.red(`
|
|
14461
14903
|
\u274C Invalid JSON: ${trimmed}
|
|
14462
14904
|
`));
|
|
14463
14905
|
process.exit(1);
|
|
@@ -14468,54 +14910,54 @@ program.command("explain").description(
|
|
|
14468
14910
|
}
|
|
14469
14911
|
const result = await explainPolicy(tool, args);
|
|
14470
14912
|
console.log("");
|
|
14471
|
-
console.log(
|
|
14913
|
+
console.log(chalk25.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
14472
14914
|
console.log("");
|
|
14473
|
-
console.log(` ${
|
|
14915
|
+
console.log(` ${chalk25.bold("Tool:")} ${chalk25.white(result.tool)}`);
|
|
14474
14916
|
if (argsRaw) {
|
|
14475
14917
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
14476
|
-
console.log(` ${
|
|
14918
|
+
console.log(` ${chalk25.bold("Input:")} ${chalk25.gray(preview2)}`);
|
|
14477
14919
|
}
|
|
14478
14920
|
console.log("");
|
|
14479
|
-
console.log(
|
|
14921
|
+
console.log(chalk25.bold("Config Sources (Waterfall):"));
|
|
14480
14922
|
for (const tier of result.waterfall) {
|
|
14481
|
-
const num3 =
|
|
14923
|
+
const num3 = chalk25.gray(` ${tier.tier}.`);
|
|
14482
14924
|
const label = tier.label.padEnd(16);
|
|
14483
14925
|
let statusStr;
|
|
14484
14926
|
if (tier.tier === 1) {
|
|
14485
|
-
statusStr =
|
|
14927
|
+
statusStr = chalk25.gray(tier.note ?? "");
|
|
14486
14928
|
} else if (tier.status === "active") {
|
|
14487
|
-
const loc = tier.path ?
|
|
14488
|
-
const note = tier.note ?
|
|
14489
|
-
statusStr =
|
|
14929
|
+
const loc = tier.path ? chalk25.gray(tier.path) : "";
|
|
14930
|
+
const note = tier.note ? chalk25.gray(`(${tier.note})`) : "";
|
|
14931
|
+
statusStr = chalk25.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
14490
14932
|
} else {
|
|
14491
|
-
statusStr =
|
|
14933
|
+
statusStr = chalk25.gray("\u25CB " + (tier.note ?? "not found"));
|
|
14492
14934
|
}
|
|
14493
|
-
console.log(`${num3} ${
|
|
14935
|
+
console.log(`${num3} ${chalk25.white(label)} ${statusStr}`);
|
|
14494
14936
|
}
|
|
14495
14937
|
console.log("");
|
|
14496
|
-
console.log(
|
|
14938
|
+
console.log(chalk25.bold("Policy Evaluation:"));
|
|
14497
14939
|
for (const step of result.steps) {
|
|
14498
14940
|
const isFinal = step.isFinal;
|
|
14499
14941
|
let icon;
|
|
14500
|
-
if (step.outcome === "allow") icon =
|
|
14501
|
-
else if (step.outcome === "review") icon =
|
|
14502
|
-
else if (step.outcome === "skip") icon =
|
|
14503
|
-
else icon =
|
|
14942
|
+
if (step.outcome === "allow") icon = chalk25.green(" \u2705");
|
|
14943
|
+
else if (step.outcome === "review") icon = chalk25.red(" \u{1F534}");
|
|
14944
|
+
else if (step.outcome === "skip") icon = chalk25.gray(" \u2500 ");
|
|
14945
|
+
else icon = chalk25.gray(" \u25CB ");
|
|
14504
14946
|
const name = step.name.padEnd(18);
|
|
14505
|
-
const nameStr = isFinal ?
|
|
14506
|
-
const detail = isFinal ?
|
|
14507
|
-
const arrow = isFinal ?
|
|
14947
|
+
const nameStr = isFinal ? chalk25.white.bold(name) : chalk25.white(name);
|
|
14948
|
+
const detail = isFinal ? chalk25.white(step.detail) : chalk25.gray(step.detail);
|
|
14949
|
+
const arrow = isFinal ? chalk25.yellow(" \u2190 STOP") : "";
|
|
14508
14950
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
14509
14951
|
}
|
|
14510
14952
|
console.log("");
|
|
14511
14953
|
if (result.decision === "allow") {
|
|
14512
|
-
console.log(
|
|
14954
|
+
console.log(chalk25.green.bold(" Decision: \u2705 ALLOW") + chalk25.gray(" \u2014 no approval needed"));
|
|
14513
14955
|
} else {
|
|
14514
14956
|
console.log(
|
|
14515
|
-
|
|
14957
|
+
chalk25.red.bold(" Decision: \u{1F534} REVIEW") + chalk25.gray(" \u2014 human approval required")
|
|
14516
14958
|
);
|
|
14517
14959
|
if (result.blockedByLabel) {
|
|
14518
|
-
console.log(
|
|
14960
|
+
console.log(chalk25.gray(` Reason: ${result.blockedByLabel}`));
|
|
14519
14961
|
}
|
|
14520
14962
|
}
|
|
14521
14963
|
console.log("");
|
|
@@ -14530,7 +14972,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
14530
14972
|
try {
|
|
14531
14973
|
await startTail2(options);
|
|
14532
14974
|
} catch (err2) {
|
|
14533
|
-
console.error(
|
|
14975
|
+
console.error(chalk25.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14534
14976
|
process.exit(1);
|
|
14535
14977
|
}
|
|
14536
14978
|
});
|
|
@@ -14538,6 +14980,7 @@ registerWatchCommand(program);
|
|
|
14538
14980
|
registerMcpGatewayCommand(program);
|
|
14539
14981
|
registerMcpServerCommand(program);
|
|
14540
14982
|
registerMcpPinCommand(program);
|
|
14983
|
+
registerSkillPinCommand(program);
|
|
14541
14984
|
registerCheckCommand(program);
|
|
14542
14985
|
registerLogCommand(program);
|
|
14543
14986
|
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
@@ -14562,14 +15005,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
14562
15005
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
14563
15006
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
14564
15007
|
if (subcommand === "debug") {
|
|
14565
|
-
const flagFile =
|
|
15008
|
+
const flagFile = path40.join(os33.homedir(), ".node9", "hud-debug");
|
|
14566
15009
|
if (state === "on") {
|
|
14567
|
-
|
|
14568
|
-
|
|
15010
|
+
fs37.mkdirSync(path40.dirname(flagFile), { recursive: true });
|
|
15011
|
+
fs37.writeFileSync(flagFile, "");
|
|
14569
15012
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
14570
15013
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
14571
15014
|
} else if (state === "off") {
|
|
14572
|
-
if (
|
|
15015
|
+
if (fs37.existsSync(flagFile)) fs37.unlinkSync(flagFile);
|
|
14573
15016
|
console.log("HUD debug logging disabled.");
|
|
14574
15017
|
} else {
|
|
14575
15018
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -14584,7 +15027,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
14584
15027
|
const ms = parseDuration(options.duration);
|
|
14585
15028
|
if (ms === null) {
|
|
14586
15029
|
console.error(
|
|
14587
|
-
|
|
15030
|
+
chalk25.red(`
|
|
14588
15031
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
14589
15032
|
`)
|
|
14590
15033
|
);
|
|
@@ -14592,20 +15035,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
14592
15035
|
}
|
|
14593
15036
|
pauseNode9(ms, options.duration);
|
|
14594
15037
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
14595
|
-
console.log(
|
|
15038
|
+
console.log(chalk25.yellow(`
|
|
14596
15039
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
14597
|
-
console.log(
|
|
14598
|
-
console.log(
|
|
15040
|
+
console.log(chalk25.gray(` All tool calls will be allowed without review.`));
|
|
15041
|
+
console.log(chalk25.gray(` Run "node9 resume" to re-enable early.
|
|
14599
15042
|
`));
|
|
14600
15043
|
});
|
|
14601
15044
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
14602
15045
|
const { paused } = checkPause();
|
|
14603
15046
|
if (!paused) {
|
|
14604
|
-
console.log(
|
|
15047
|
+
console.log(chalk25.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
14605
15048
|
return;
|
|
14606
15049
|
}
|
|
14607
15050
|
resumeNode9();
|
|
14608
|
-
console.log(
|
|
15051
|
+
console.log(chalk25.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
14609
15052
|
});
|
|
14610
15053
|
var HOOK_BASED_AGENTS = {
|
|
14611
15054
|
claude: "claude",
|
|
@@ -14618,15 +15061,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14618
15061
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
14619
15062
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
14620
15063
|
console.error(
|
|
14621
|
-
|
|
15064
|
+
chalk25.yellow(`
|
|
14622
15065
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
14623
15066
|
);
|
|
14624
|
-
console.error(
|
|
15067
|
+
console.error(chalk25.white(`
|
|
14625
15068
|
"${target}" uses its own hook system. Use:`));
|
|
14626
15069
|
console.error(
|
|
14627
|
-
|
|
15070
|
+
chalk25.green(` node9 addto ${target} `) + chalk25.gray("# one-time setup")
|
|
14628
15071
|
);
|
|
14629
|
-
console.error(
|
|
15072
|
+
console.error(chalk25.green(` ${target} `) + chalk25.gray("# run normally"));
|
|
14630
15073
|
process.exit(1);
|
|
14631
15074
|
}
|
|
14632
15075
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -14643,7 +15086,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14643
15086
|
}
|
|
14644
15087
|
);
|
|
14645
15088
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
14646
|
-
console.error(
|
|
15089
|
+
console.error(chalk25.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
14647
15090
|
const daemonReady = await autoStartDaemonAndWait();
|
|
14648
15091
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
14649
15092
|
}
|
|
@@ -14656,12 +15099,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14656
15099
|
}
|
|
14657
15100
|
if (!result.approved) {
|
|
14658
15101
|
console.error(
|
|
14659
|
-
|
|
15102
|
+
chalk25.red(`
|
|
14660
15103
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
14661
15104
|
);
|
|
14662
15105
|
process.exit(1);
|
|
14663
15106
|
}
|
|
14664
|
-
console.error(
|
|
15107
|
+
console.error(chalk25.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
14665
15108
|
await runProxy(fullCommand);
|
|
14666
15109
|
} else {
|
|
14667
15110
|
program.help();
|
|
@@ -14680,9 +15123,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
14680
15123
|
const isCheckHook = process.argv[2] === "check";
|
|
14681
15124
|
if (isCheckHook) {
|
|
14682
15125
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
14683
|
-
const logPath =
|
|
15126
|
+
const logPath = path40.join(os33.homedir(), ".node9", "hook-debug.log");
|
|
14684
15127
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
14685
|
-
|
|
15128
|
+
fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
14686
15129
|
`);
|
|
14687
15130
|
}
|
|
14688
15131
|
process.exit(0);
|