@node9/proxy 1.10.3 → 1.11.1
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 +57 -0
- package/dist/cli.js +1447 -474
- package/dist/cli.mjs +1443 -470
- package/dist/index.js +106 -38
- package/dist/index.mjs +106 -38
- package/dist/shields/builtin/bash-safe.json +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
const lines = result.error.issues.map((issue) => {
|
|
171
|
-
const
|
|
172
|
-
return ` \u2022 ${
|
|
171
|
+
const path43 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
172
|
+
return ` \u2022 ${path43}: ${issue.message}`;
|
|
173
173
|
});
|
|
174
174
|
return {
|
|
175
175
|
sanitized,
|
|
@@ -276,6 +276,11 @@ var init_config_schema = __esm({
|
|
|
276
276
|
enabled: import_zod.z.boolean().optional(),
|
|
277
277
|
threshold: import_zod.z.number().min(2).optional(),
|
|
278
278
|
windowSeconds: import_zod.z.number().min(10).optional()
|
|
279
|
+
}).optional(),
|
|
280
|
+
skillPinning: import_zod.z.object({
|
|
281
|
+
enabled: import_zod.z.boolean().optional(),
|
|
282
|
+
mode: import_zod.z.enum(["warn", "block"]).optional(),
|
|
283
|
+
roots: import_zod.z.array(import_zod.z.string()).optional()
|
|
279
284
|
}).optional()
|
|
280
285
|
}).optional(),
|
|
281
286
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -571,7 +576,11 @@ function getConfig(cwd) {
|
|
|
571
576
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
572
577
|
},
|
|
573
578
|
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
574
|
-
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
579
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
|
|
580
|
+
skillPinning: {
|
|
581
|
+
...DEFAULT_CONFIG.policy.skillPinning,
|
|
582
|
+
roots: [...DEFAULT_CONFIG.policy.skillPinning.roots]
|
|
583
|
+
}
|
|
575
584
|
};
|
|
576
585
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
577
586
|
const applyLayer = (source) => {
|
|
@@ -624,6 +633,16 @@ function getConfig(cwd) {
|
|
|
624
633
|
if (ld.windowSeconds !== void 0)
|
|
625
634
|
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
626
635
|
}
|
|
636
|
+
if (p.skillPinning && typeof p.skillPinning === "object") {
|
|
637
|
+
const sp = p.skillPinning;
|
|
638
|
+
if (sp.enabled !== void 0) mergedPolicy.skillPinning.enabled = sp.enabled;
|
|
639
|
+
if (sp.mode !== void 0) mergedPolicy.skillPinning.mode = sp.mode;
|
|
640
|
+
if (Array.isArray(sp.roots)) {
|
|
641
|
+
for (const r of sp.roots) {
|
|
642
|
+
if (typeof r === "string" && r.length > 0) mergedPolicy.skillPinning.roots.push(r);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
627
646
|
const envs = source.environments || {};
|
|
628
647
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
629
648
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -675,6 +694,7 @@ function getConfig(cwd) {
|
|
|
675
694
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
676
695
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
677
696
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
697
|
+
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
678
698
|
mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
|
|
679
699
|
mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
|
|
680
700
|
mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
|
|
@@ -822,9 +842,8 @@ var init_config = __esm({
|
|
|
822
842
|
{
|
|
823
843
|
field: "command",
|
|
824
844
|
op: "matches",
|
|
825
|
-
//
|
|
826
|
-
|
|
827
|
-
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
845
|
+
// Anchor rm as a shell command (not inside a string arg like a git commit message).
|
|
846
|
+
value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
828
847
|
},
|
|
829
848
|
{
|
|
830
849
|
field: "command",
|
|
@@ -853,6 +872,13 @@ var init_config = __esm({
|
|
|
853
872
|
name: "review-drop-truncate-shell",
|
|
854
873
|
tool: "bash",
|
|
855
874
|
conditions: [
|
|
875
|
+
{
|
|
876
|
+
field: "command",
|
|
877
|
+
op: "matches",
|
|
878
|
+
// Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
|
|
879
|
+
value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
|
|
880
|
+
flags: "i"
|
|
881
|
+
},
|
|
856
882
|
{
|
|
857
883
|
field: "command",
|
|
858
884
|
op: "matches",
|
|
@@ -873,7 +899,9 @@ var init_config = __esm({
|
|
|
873
899
|
{
|
|
874
900
|
field: "command",
|
|
875
901
|
op: "matches",
|
|
876
|
-
|
|
902
|
+
// Anchor git as a shell command so node -e / python -c scripts containing
|
|
903
|
+
// "git push --force" as a string don't false-positive.
|
|
904
|
+
value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
|
|
877
905
|
flags: "i"
|
|
878
906
|
}
|
|
879
907
|
],
|
|
@@ -883,29 +911,20 @@ var init_config = __esm({
|
|
|
883
911
|
description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
|
|
884
912
|
},
|
|
885
913
|
{
|
|
886
|
-
name: "review-git-
|
|
914
|
+
name: "review-git-destructive",
|
|
887
915
|
tool: "bash",
|
|
888
916
|
conditions: [
|
|
889
917
|
{
|
|
890
918
|
field: "command",
|
|
891
919
|
op: "matches",
|
|
892
|
-
value: "\\bgit\\
|
|
920
|
+
value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
893
921
|
flags: "i"
|
|
894
|
-
}
|
|
895
|
-
],
|
|
896
|
-
conditionMode: "all",
|
|
897
|
-
verdict: "review",
|
|
898
|
-
reason: "git push sends changes to a shared remote",
|
|
899
|
-
description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
|
|
900
|
-
},
|
|
901
|
-
{
|
|
902
|
-
name: "review-git-destructive",
|
|
903
|
-
tool: "bash",
|
|
904
|
-
conditions: [
|
|
922
|
+
},
|
|
905
923
|
{
|
|
906
924
|
field: "command",
|
|
907
|
-
op: "
|
|
908
|
-
|
|
925
|
+
op: "notMatches",
|
|
926
|
+
// Exclude recovery ops — these resolve a conflict, not start a destructive action.
|
|
927
|
+
value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
|
|
909
928
|
flags: "i"
|
|
910
929
|
}
|
|
911
930
|
],
|
|
@@ -931,7 +950,9 @@ var init_config = __esm({
|
|
|
931
950
|
{
|
|
932
951
|
field: "command",
|
|
933
952
|
op: "matches",
|
|
934
|
-
|
|
953
|
+
// Anchor curl/wget as a shell command so node -e scripts testing this
|
|
954
|
+
// regex pattern don't self-match as a false positive.
|
|
955
|
+
value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
|
|
935
956
|
flags: "i"
|
|
936
957
|
}
|
|
937
958
|
],
|
|
@@ -942,7 +963,8 @@ var init_config = __esm({
|
|
|
942
963
|
}
|
|
943
964
|
],
|
|
944
965
|
dlp: { enabled: true, scanIgnoredTools: true },
|
|
945
|
-
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
966
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
|
|
967
|
+
skillPinning: { enabled: false, mode: "warn", roots: [] }
|
|
946
968
|
},
|
|
947
969
|
environments: {}
|
|
948
970
|
};
|
|
@@ -1152,6 +1174,20 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
|
1152
1174
|
}
|
|
1153
1175
|
return null;
|
|
1154
1176
|
}
|
|
1177
|
+
function scanText(text) {
|
|
1178
|
+
const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
|
|
1179
|
+
for (const pattern of DLP_PATTERNS) {
|
|
1180
|
+
if (pattern.regex.test(t)) {
|
|
1181
|
+
return {
|
|
1182
|
+
patternName: pattern.name,
|
|
1183
|
+
fieldPath: "response-text",
|
|
1184
|
+
redactedSample: maskSecret(t, pattern.regex),
|
|
1185
|
+
severity: pattern.severity
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1155
1191
|
var import_fs4, import_path4, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
1156
1192
|
var init_dlp = __esm({
|
|
1157
1193
|
"src/dlp.ts"() {
|
|
@@ -1183,7 +1219,7 @@ var init_dlp = __esm({
|
|
|
1183
1219
|
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
1184
1220
|
severity: "block"
|
|
1185
1221
|
},
|
|
1186
|
-
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]
|
|
1222
|
+
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
|
|
1187
1223
|
];
|
|
1188
1224
|
SENSITIVE_PATH_PATTERNS = [
|
|
1189
1225
|
/[/\\]\.ssh[/\\]/i,
|
|
@@ -1746,9 +1782,21 @@ function matchesPattern(text, patterns) {
|
|
|
1746
1782
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1747
1783
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1748
1784
|
}
|
|
1749
|
-
function getNestedValue(obj,
|
|
1785
|
+
function getNestedValue(obj, path43) {
|
|
1750
1786
|
if (!obj || typeof obj !== "object") return null;
|
|
1751
|
-
return
|
|
1787
|
+
return path43.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1788
|
+
}
|
|
1789
|
+
function stripStringArguments(cmd) {
|
|
1790
|
+
let result = cmd;
|
|
1791
|
+
result = result.replace(
|
|
1792
|
+
/\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
|
|
1793
|
+
'$1 $2 ""'
|
|
1794
|
+
);
|
|
1795
|
+
result = result.replace(
|
|
1796
|
+
/\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
|
|
1797
|
+
' $1 ""'
|
|
1798
|
+
);
|
|
1799
|
+
return result;
|
|
1752
1800
|
}
|
|
1753
1801
|
function shouldSnapshot(toolName, args, config) {
|
|
1754
1802
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1767,7 +1815,8 @@ function evaluateSmartConditions(args, rule) {
|
|
|
1767
1815
|
const mode = rule.conditionMode ?? "all";
|
|
1768
1816
|
const results = rule.conditions.map((cond) => {
|
|
1769
1817
|
const rawVal = getNestedValue(args, cond.field);
|
|
1770
|
-
const
|
|
1818
|
+
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1819
|
+
const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
|
|
1771
1820
|
switch (cond.op) {
|
|
1772
1821
|
case "exists":
|
|
1773
1822
|
return val !== null && val !== "";
|
|
@@ -2343,6 +2392,15 @@ var init_policy = __esm({
|
|
|
2343
2392
|
});
|
|
2344
2393
|
|
|
2345
2394
|
// src/auth/state.ts
|
|
2395
|
+
function extractCommandPattern(toolName, args) {
|
|
2396
|
+
const lower = toolName.toLowerCase();
|
|
2397
|
+
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
2398
|
+
const a = args;
|
|
2399
|
+
const cmd = typeof a?.["command"] === "string" ? a["command"].trim() : "";
|
|
2400
|
+
if (!cmd) return void 0;
|
|
2401
|
+
const words = cmd.split(/\s+/);
|
|
2402
|
+
return words.slice(0, 2).join(" ");
|
|
2403
|
+
}
|
|
2346
2404
|
function checkPause() {
|
|
2347
2405
|
try {
|
|
2348
2406
|
if (!import_fs8.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
@@ -2376,7 +2434,7 @@ function resumeNode9() {
|
|
|
2376
2434
|
} catch {
|
|
2377
2435
|
}
|
|
2378
2436
|
}
|
|
2379
|
-
function getActiveTrustSession(toolName) {
|
|
2437
|
+
function getActiveTrustSession(toolName, args) {
|
|
2380
2438
|
try {
|
|
2381
2439
|
if (!import_fs8.default.existsSync(TRUST_FILE)) return false;
|
|
2382
2440
|
const trust = JSON.parse(import_fs8.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
@@ -2385,12 +2443,20 @@ function getActiveTrustSession(toolName) {
|
|
|
2385
2443
|
if (active.length !== trust.entries.length) {
|
|
2386
2444
|
import_fs8.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
2387
2445
|
}
|
|
2388
|
-
return active.some((e) =>
|
|
2446
|
+
return active.some((e) => {
|
|
2447
|
+
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
2448
|
+
if (e.commandPattern) {
|
|
2449
|
+
const actual = extractCommandPattern(toolName, args) ?? "";
|
|
2450
|
+
return actual === e.commandPattern || actual.startsWith(e.commandPattern + " ");
|
|
2451
|
+
}
|
|
2452
|
+
return true;
|
|
2453
|
+
});
|
|
2389
2454
|
} catch {
|
|
2390
2455
|
return false;
|
|
2391
2456
|
}
|
|
2392
2457
|
}
|
|
2393
|
-
function writeTrustSession(toolName, durationMs) {
|
|
2458
|
+
function writeTrustSession(toolName, durationMs, args) {
|
|
2459
|
+
const commandPattern = extractCommandPattern(toolName, args);
|
|
2394
2460
|
try {
|
|
2395
2461
|
let trust = { entries: [] };
|
|
2396
2462
|
try {
|
|
@@ -2400,8 +2466,14 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
2400
2466
|
} catch {
|
|
2401
2467
|
}
|
|
2402
2468
|
const now = Date.now();
|
|
2403
|
-
trust.entries = trust.entries.filter(
|
|
2404
|
-
|
|
2469
|
+
trust.entries = trust.entries.filter(
|
|
2470
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > now
|
|
2471
|
+
);
|
|
2472
|
+
trust.entries.push({
|
|
2473
|
+
tool: toolName,
|
|
2474
|
+
...commandPattern && { commandPattern },
|
|
2475
|
+
expiry: now + durationMs
|
|
2476
|
+
});
|
|
2405
2477
|
atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
|
|
2406
2478
|
} catch (err2) {
|
|
2407
2479
|
if (process.env.NODE9_DEBUG === "1") {
|
|
@@ -2853,13 +2925,30 @@ ${smartTruncate(str, 500)}`
|
|
|
2853
2925
|
}
|
|
2854
2926
|
return { intent: "EXEC", message: smartTruncate(JSON.stringify(parsed), 200) };
|
|
2855
2927
|
}
|
|
2928
|
+
function sendDesktopNotification(title, body) {
|
|
2929
|
+
if (isTestEnv()) return;
|
|
2930
|
+
try {
|
|
2931
|
+
if (process.platform === "darwin") {
|
|
2932
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2933
|
+
const script = `display notification "${esc(body)}" with title "${esc(title)}"`;
|
|
2934
|
+
(0, import_child_process2.spawn)("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
2935
|
+
} else if (process.platform === "linux") {
|
|
2936
|
+
(0, import_child_process2.spawn)("notify-send", [title, body, "--icon=dialog-warning"], {
|
|
2937
|
+
detached: true,
|
|
2938
|
+
stdio: "ignore"
|
|
2939
|
+
}).unref();
|
|
2940
|
+
}
|
|
2941
|
+
} catch {
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2856
2944
|
function escapePango(text) {
|
|
2857
2945
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2858
2946
|
}
|
|
2859
2947
|
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2860
2948
|
const lines = [];
|
|
2861
2949
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2862
|
-
|
|
2950
|
+
const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
|
|
2951
|
+
lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
|
|
2863
2952
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2864
2953
|
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2865
2954
|
lines.push("");
|
|
@@ -3246,7 +3335,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
3246
3335
|
if (!options?.calledFromDaemon) {
|
|
3247
3336
|
const actId = (0, import_crypto4.randomUUID)();
|
|
3248
3337
|
const actTs = Date.now();
|
|
3249
|
-
await notifyActivity({
|
|
3338
|
+
await notifyActivity({
|
|
3339
|
+
id: actId,
|
|
3340
|
+
ts: actTs,
|
|
3341
|
+
tool: toolName,
|
|
3342
|
+
args,
|
|
3343
|
+
status: "pending",
|
|
3344
|
+
// Strip ANSI escape sequences — agent name comes from caller-supplied metadata
|
|
3345
|
+
// and may be displayed in a terminal (node9 tail/watch), enabling injection.
|
|
3346
|
+
agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
|
|
3347
|
+
});
|
|
3250
3348
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
3251
3349
|
...options,
|
|
3252
3350
|
activityId: actId
|
|
@@ -3400,12 +3498,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3400
3498
|
};
|
|
3401
3499
|
}
|
|
3402
3500
|
}
|
|
3403
|
-
if (getActiveTrustSession(toolName)) {
|
|
3404
|
-
if (approvers.cloud && creds?.apiKey)
|
|
3405
|
-
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3406
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3407
|
-
return { approved: true, checkedBy: "trust" };
|
|
3408
|
-
}
|
|
3409
3501
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
3410
3502
|
if (policyResult.decision === "allow") {
|
|
3411
3503
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -3487,6 +3579,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3487
3579
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
|
|
3488
3580
|
return { approved: true };
|
|
3489
3581
|
}
|
|
3582
|
+
if (!taintWarning && getActiveTrustSession(toolName, args)) {
|
|
3583
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3584
|
+
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3585
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3586
|
+
return { approved: true, checkedBy: "trust" };
|
|
3587
|
+
}
|
|
3490
3588
|
if (taintWarning) {
|
|
3491
3589
|
explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
|
|
3492
3590
|
riskMetadata = computeRiskMetadata(
|
|
@@ -3619,7 +3717,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3619
3717
|
riskMetadata?.ruleDescription
|
|
3620
3718
|
);
|
|
3621
3719
|
if (decision === "always_allow") {
|
|
3622
|
-
writeTrustSession(toolName, 36e5);
|
|
3720
|
+
writeTrustSession(toolName, 36e5, args);
|
|
3623
3721
|
return { approved: true, checkedBy: "trust" };
|
|
3624
3722
|
}
|
|
3625
3723
|
const isApproved = decision === "allow";
|
|
@@ -5792,7 +5890,7 @@ function writeGlobalSetting(key, value) {
|
|
|
5792
5890
|
config.settings[key] = value;
|
|
5793
5891
|
atomicWriteSync2(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5794
5892
|
}
|
|
5795
|
-
function writeTrustEntry(toolName, durationMs) {
|
|
5893
|
+
function writeTrustEntry(toolName, durationMs, commandPattern) {
|
|
5796
5894
|
try {
|
|
5797
5895
|
let trust = { entries: [] };
|
|
5798
5896
|
try {
|
|
@@ -5800,8 +5898,14 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5800
5898
|
trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5801
5899
|
} catch {
|
|
5802
5900
|
}
|
|
5803
|
-
trust.entries = trust.entries.filter(
|
|
5804
|
-
|
|
5901
|
+
trust.entries = trust.entries.filter(
|
|
5902
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > Date.now()
|
|
5903
|
+
);
|
|
5904
|
+
trust.entries.push({
|
|
5905
|
+
tool: toolName,
|
|
5906
|
+
...commandPattern && { commandPattern },
|
|
5907
|
+
expiry: Date.now() + durationMs
|
|
5908
|
+
});
|
|
5805
5909
|
atomicWriteSync2(TRUST_FILE2, JSON.stringify(trust, null, 2));
|
|
5806
5910
|
} catch {
|
|
5807
5911
|
}
|
|
@@ -5957,7 +6061,8 @@ function startActivitySocket() {
|
|
|
5957
6061
|
ts: data.ts,
|
|
5958
6062
|
tool: data.tool,
|
|
5959
6063
|
args: redactArgs(data.args),
|
|
5960
|
-
status: "pending"
|
|
6064
|
+
status: "pending",
|
|
6065
|
+
agent: data.agent
|
|
5961
6066
|
});
|
|
5962
6067
|
} else {
|
|
5963
6068
|
if (data.status === "allow") {
|
|
@@ -6421,10 +6526,161 @@ var init_sync = __esm({
|
|
|
6421
6526
|
}
|
|
6422
6527
|
});
|
|
6423
6528
|
|
|
6529
|
+
// src/daemon/dlp-scanner.ts
|
|
6530
|
+
function loadIndex() {
|
|
6531
|
+
try {
|
|
6532
|
+
return JSON.parse(import_fs18.default.readFileSync(INDEX_FILE, "utf-8"));
|
|
6533
|
+
} catch {
|
|
6534
|
+
return {};
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
function saveIndex(index) {
|
|
6538
|
+
try {
|
|
6539
|
+
import_fs18.default.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
|
|
6540
|
+
} catch {
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
function appendAuditEntry(entry) {
|
|
6544
|
+
try {
|
|
6545
|
+
import_fs18.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
6546
|
+
} catch {
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
function runDlpScan() {
|
|
6550
|
+
if (!import_fs18.default.existsSync(PROJECTS_DIR)) return;
|
|
6551
|
+
const index = loadIndex();
|
|
6552
|
+
let updated = false;
|
|
6553
|
+
let projDirs;
|
|
6554
|
+
try {
|
|
6555
|
+
projDirs = import_fs18.default.readdirSync(PROJECTS_DIR);
|
|
6556
|
+
} catch {
|
|
6557
|
+
return;
|
|
6558
|
+
}
|
|
6559
|
+
for (const proj of projDirs) {
|
|
6560
|
+
const projPath = import_path21.default.join(PROJECTS_DIR, proj);
|
|
6561
|
+
try {
|
|
6562
|
+
if (!import_fs18.default.lstatSync(projPath).isDirectory()) continue;
|
|
6563
|
+
const real = import_fs18.default.realpathSync(projPath);
|
|
6564
|
+
if (!real.startsWith(PROJECTS_DIR + import_path21.default.sep) && real !== PROJECTS_DIR) continue;
|
|
6565
|
+
} catch {
|
|
6566
|
+
continue;
|
|
6567
|
+
}
|
|
6568
|
+
let files;
|
|
6569
|
+
try {
|
|
6570
|
+
files = import_fs18.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
6571
|
+
} catch {
|
|
6572
|
+
continue;
|
|
6573
|
+
}
|
|
6574
|
+
for (const file of files) {
|
|
6575
|
+
const filePath = import_path21.default.join(projPath, file);
|
|
6576
|
+
const lastOffset = index[filePath] ?? 0;
|
|
6577
|
+
let size;
|
|
6578
|
+
try {
|
|
6579
|
+
size = import_fs18.default.statSync(filePath).size;
|
|
6580
|
+
} catch {
|
|
6581
|
+
continue;
|
|
6582
|
+
}
|
|
6583
|
+
if (size <= lastOffset) continue;
|
|
6584
|
+
let fd;
|
|
6585
|
+
try {
|
|
6586
|
+
fd = import_fs18.default.openSync(filePath, "r");
|
|
6587
|
+
} catch {
|
|
6588
|
+
continue;
|
|
6589
|
+
}
|
|
6590
|
+
try {
|
|
6591
|
+
const chunkSize = size - lastOffset;
|
|
6592
|
+
const buf = Buffer.alloc(chunkSize);
|
|
6593
|
+
import_fs18.default.readSync(fd, buf, 0, chunkSize, lastOffset);
|
|
6594
|
+
const chunk = buf.toString("utf-8");
|
|
6595
|
+
for (const line of chunk.split("\n")) {
|
|
6596
|
+
if (!line.trim()) continue;
|
|
6597
|
+
let entry;
|
|
6598
|
+
try {
|
|
6599
|
+
entry = JSON.parse(line);
|
|
6600
|
+
} catch {
|
|
6601
|
+
continue;
|
|
6602
|
+
}
|
|
6603
|
+
if (entry.type !== "assistant") continue;
|
|
6604
|
+
const content = entry.message?.content;
|
|
6605
|
+
if (!Array.isArray(content)) continue;
|
|
6606
|
+
for (const block of content) {
|
|
6607
|
+
if (typeof block !== "object" || block === null || block.type !== "text")
|
|
6608
|
+
continue;
|
|
6609
|
+
const text = block.text;
|
|
6610
|
+
if (typeof text !== "string") continue;
|
|
6611
|
+
const match = scanText(text);
|
|
6612
|
+
if (!match) continue;
|
|
6613
|
+
const projLabel = decodeURIComponent(proj).replace(import_os16.default.homedir(), "~").slice(0, 40);
|
|
6614
|
+
const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6615
|
+
appendAuditEntry({
|
|
6616
|
+
ts,
|
|
6617
|
+
tool: "response-text",
|
|
6618
|
+
decision: "dlp",
|
|
6619
|
+
checkedBy: "response-dlp",
|
|
6620
|
+
source: "response-dlp",
|
|
6621
|
+
dlpPattern: match.patternName,
|
|
6622
|
+
dlpSample: match.redactedSample,
|
|
6623
|
+
project: projLabel
|
|
6624
|
+
});
|
|
6625
|
+
sendDesktopNotification(
|
|
6626
|
+
"\u26A0\uFE0F node9 DLP Alert",
|
|
6627
|
+
`${match.patternName} found in Claude response
|
|
6628
|
+
Sample: ${match.redactedSample}
|
|
6629
|
+
Project: ${projLabel}
|
|
6630
|
+
Run: node9 report --period 30d`
|
|
6631
|
+
);
|
|
6632
|
+
}
|
|
6633
|
+
}
|
|
6634
|
+
index[filePath] = size;
|
|
6635
|
+
updated = true;
|
|
6636
|
+
} finally {
|
|
6637
|
+
try {
|
|
6638
|
+
import_fs18.default.closeSync(fd);
|
|
6639
|
+
} catch {
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6644
|
+
if (updated) saveIndex(index);
|
|
6645
|
+
}
|
|
6646
|
+
function startDlpScanner() {
|
|
6647
|
+
setImmediate(() => {
|
|
6648
|
+
try {
|
|
6649
|
+
runDlpScan();
|
|
6650
|
+
} catch {
|
|
6651
|
+
}
|
|
6652
|
+
});
|
|
6653
|
+
const timer = setInterval(
|
|
6654
|
+
() => {
|
|
6655
|
+
try {
|
|
6656
|
+
runDlpScan();
|
|
6657
|
+
} catch {
|
|
6658
|
+
}
|
|
6659
|
+
},
|
|
6660
|
+
60 * 60 * 1e3
|
|
6661
|
+
);
|
|
6662
|
+
timer.unref();
|
|
6663
|
+
}
|
|
6664
|
+
var import_fs18, import_path21, import_os16, INDEX_FILE, PROJECTS_DIR;
|
|
6665
|
+
var init_dlp_scanner = __esm({
|
|
6666
|
+
"src/daemon/dlp-scanner.ts"() {
|
|
6667
|
+
"use strict";
|
|
6668
|
+
import_fs18 = __toESM(require("fs"));
|
|
6669
|
+
import_path21 = __toESM(require("path"));
|
|
6670
|
+
import_os16 = __toESM(require("os"));
|
|
6671
|
+
init_dlp();
|
|
6672
|
+
init_native();
|
|
6673
|
+
init_state2();
|
|
6674
|
+
INDEX_FILE = import_path21.default.join(import_os16.default.homedir(), ".node9", "dlp-index.json");
|
|
6675
|
+
PROJECTS_DIR = import_path21.default.join(import_os16.default.homedir(), ".claude", "projects");
|
|
6676
|
+
}
|
|
6677
|
+
});
|
|
6678
|
+
|
|
6424
6679
|
// src/daemon/server.ts
|
|
6425
6680
|
function startDaemon() {
|
|
6426
6681
|
startCostSync();
|
|
6427
6682
|
startCloudSync();
|
|
6683
|
+
startDlpScanner();
|
|
6428
6684
|
loadInsightCounts();
|
|
6429
6685
|
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6430
6686
|
const internalToken = (0, import_crypto7.randomUUID)();
|
|
@@ -6440,7 +6696,7 @@ function startDaemon() {
|
|
|
6440
6696
|
idleTimer = setTimeout(() => {
|
|
6441
6697
|
if (autoStarted) {
|
|
6442
6698
|
try {
|
|
6443
|
-
|
|
6699
|
+
import_fs19.default.unlinkSync(DAEMON_PID_FILE);
|
|
6444
6700
|
} catch {
|
|
6445
6701
|
}
|
|
6446
6702
|
}
|
|
@@ -6603,7 +6859,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6603
6859
|
status: "pending"
|
|
6604
6860
|
});
|
|
6605
6861
|
}
|
|
6606
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6862
|
+
const projectCwd = typeof cwd === "string" && import_path22.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6607
6863
|
const projectConfig = getConfig(projectCwd);
|
|
6608
6864
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6609
6865
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6730,7 +6986,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6730
6986
|
);
|
|
6731
6987
|
if (decision === "trust" && trustDuration) {
|
|
6732
6988
|
const ms = TRUST_DURATIONS[trustDuration] ?? 60 * 6e4;
|
|
6733
|
-
|
|
6989
|
+
const commandPattern = extractCommandPattern(entry.toolName, entry.args);
|
|
6990
|
+
writeTrustEntry(entry.toolName, ms, commandPattern);
|
|
6734
6991
|
appendAuditLog({
|
|
6735
6992
|
toolName: entry.toolName,
|
|
6736
6993
|
args: entry.args,
|
|
@@ -6993,8 +7250,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6993
7250
|
const body = await readBody(req);
|
|
6994
7251
|
const data = body ? JSON.parse(body) : {};
|
|
6995
7252
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6996
|
-
const node9Dir =
|
|
6997
|
-
if (!
|
|
7253
|
+
const node9Dir = import_path22.default.dirname(GLOBAL_CONFIG_PATH);
|
|
7254
|
+
if (!import_path22.default.resolve(configPath).startsWith(node9Dir + import_path22.default.sep)) {
|
|
6998
7255
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6999
7256
|
return res.end(
|
|
7000
7257
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -7105,14 +7362,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
7105
7362
|
server.on("error", (e) => {
|
|
7106
7363
|
if (e.code === "EADDRINUSE") {
|
|
7107
7364
|
try {
|
|
7108
|
-
if (
|
|
7109
|
-
const { pid } = JSON.parse(
|
|
7365
|
+
if (import_fs19.default.existsSync(DAEMON_PID_FILE)) {
|
|
7366
|
+
const { pid } = JSON.parse(import_fs19.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7110
7367
|
process.kill(pid, 0);
|
|
7111
7368
|
return process.exit(0);
|
|
7112
7369
|
}
|
|
7113
7370
|
} catch {
|
|
7114
7371
|
try {
|
|
7115
|
-
|
|
7372
|
+
import_fs19.default.unlinkSync(DAEMON_PID_FILE);
|
|
7116
7373
|
} catch {
|
|
7117
7374
|
}
|
|
7118
7375
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -7171,13 +7428,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
7171
7428
|
}
|
|
7172
7429
|
startActivitySocket();
|
|
7173
7430
|
}
|
|
7174
|
-
var import_http,
|
|
7431
|
+
var import_http, import_fs19, import_path22, import_crypto7, import_child_process4, import_chalk2;
|
|
7175
7432
|
var init_server = __esm({
|
|
7176
7433
|
"src/daemon/server.ts"() {
|
|
7177
7434
|
"use strict";
|
|
7178
7435
|
import_http = __toESM(require("http"));
|
|
7179
|
-
|
|
7180
|
-
|
|
7436
|
+
import_fs19 = __toESM(require("fs"));
|
|
7437
|
+
import_path22 = __toESM(require("path"));
|
|
7181
7438
|
import_crypto7 = require("crypto");
|
|
7182
7439
|
import_child_process4 = require("child_process");
|
|
7183
7440
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -7185,10 +7442,12 @@ var init_server = __esm({
|
|
|
7185
7442
|
init_shields();
|
|
7186
7443
|
init_ui2();
|
|
7187
7444
|
init_state2();
|
|
7445
|
+
init_state();
|
|
7188
7446
|
init_patch();
|
|
7189
7447
|
init_config_schema();
|
|
7190
7448
|
init_costSync();
|
|
7191
7449
|
init_sync();
|
|
7450
|
+
init_dlp_scanner();
|
|
7192
7451
|
}
|
|
7193
7452
|
});
|
|
7194
7453
|
|
|
@@ -7196,8 +7455,8 @@ var init_server = __esm({
|
|
|
7196
7455
|
function resolveNode9Binary() {
|
|
7197
7456
|
try {
|
|
7198
7457
|
const script = process.argv[1];
|
|
7199
|
-
if (typeof script === "string" &&
|
|
7200
|
-
return
|
|
7458
|
+
if (typeof script === "string" && import_path23.default.isAbsolute(script) && import_fs20.default.existsSync(script)) {
|
|
7459
|
+
return import_fs20.default.realpathSync(script);
|
|
7201
7460
|
}
|
|
7202
7461
|
} catch {
|
|
7203
7462
|
}
|
|
@@ -7215,11 +7474,11 @@ function xmlEscape(s) {
|
|
|
7215
7474
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7216
7475
|
}
|
|
7217
7476
|
function launchdPlist(binaryPath) {
|
|
7218
|
-
const logDir =
|
|
7477
|
+
const logDir = import_path23.default.join(import_os17.default.homedir(), ".node9");
|
|
7219
7478
|
const nodePath = xmlEscape(process.execPath);
|
|
7220
7479
|
const scriptPath = xmlEscape(binaryPath);
|
|
7221
|
-
const outLog = xmlEscape(
|
|
7222
|
-
const errLog = xmlEscape(
|
|
7480
|
+
const outLog = xmlEscape(import_path23.default.join(logDir, "daemon.log"));
|
|
7481
|
+
const errLog = xmlEscape(import_path23.default.join(logDir, "daemon-error.log"));
|
|
7223
7482
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
7224
7483
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7225
7484
|
<plist version="1.0">
|
|
@@ -7254,9 +7513,9 @@ function launchdPlist(binaryPath) {
|
|
|
7254
7513
|
`;
|
|
7255
7514
|
}
|
|
7256
7515
|
function installLaunchd(binaryPath) {
|
|
7257
|
-
const dir =
|
|
7258
|
-
if (!
|
|
7259
|
-
|
|
7516
|
+
const dir = import_path23.default.dirname(LAUNCHD_PLIST);
|
|
7517
|
+
if (!import_fs20.default.existsSync(dir)) import_fs20.default.mkdirSync(dir, { recursive: true });
|
|
7518
|
+
import_fs20.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
7260
7519
|
(0, import_child_process5.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
7261
7520
|
const r = (0, import_child_process5.spawnSync)("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
7262
7521
|
encoding: "utf8",
|
|
@@ -7267,13 +7526,13 @@ function installLaunchd(binaryPath) {
|
|
|
7267
7526
|
}
|
|
7268
7527
|
}
|
|
7269
7528
|
function uninstallLaunchd() {
|
|
7270
|
-
if (
|
|
7529
|
+
if (import_fs20.default.existsSync(LAUNCHD_PLIST)) {
|
|
7271
7530
|
(0, import_child_process5.spawnSync)("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
7272
|
-
|
|
7531
|
+
import_fs20.default.unlinkSync(LAUNCHD_PLIST);
|
|
7273
7532
|
}
|
|
7274
7533
|
}
|
|
7275
7534
|
function isLaunchdInstalled() {
|
|
7276
|
-
return
|
|
7535
|
+
return import_fs20.default.existsSync(LAUNCHD_PLIST);
|
|
7277
7536
|
}
|
|
7278
7537
|
function systemdUnit(binaryPath) {
|
|
7279
7538
|
return `[Unit]
|
|
@@ -7293,12 +7552,12 @@ WantedBy=default.target
|
|
|
7293
7552
|
`;
|
|
7294
7553
|
}
|
|
7295
7554
|
function installSystemd(binaryPath) {
|
|
7296
|
-
if (!
|
|
7297
|
-
|
|
7555
|
+
if (!import_fs20.default.existsSync(SYSTEMD_UNIT_DIR)) {
|
|
7556
|
+
import_fs20.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
7298
7557
|
}
|
|
7299
|
-
|
|
7558
|
+
import_fs20.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
|
|
7300
7559
|
try {
|
|
7301
|
-
(0, import_child_process5.execFileSync)("loginctl", ["enable-linger",
|
|
7560
|
+
(0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os17.default.userInfo().username], { timeout: 3e3 });
|
|
7302
7561
|
} catch {
|
|
7303
7562
|
}
|
|
7304
7563
|
const reload = (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], {
|
|
@@ -7318,23 +7577,23 @@ function installSystemd(binaryPath) {
|
|
|
7318
7577
|
}
|
|
7319
7578
|
}
|
|
7320
7579
|
function uninstallSystemd() {
|
|
7321
|
-
if (
|
|
7580
|
+
if (import_fs20.default.existsSync(SYSTEMD_UNIT)) {
|
|
7322
7581
|
(0, import_child_process5.spawnSync)("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
7323
7582
|
encoding: "utf8",
|
|
7324
7583
|
timeout: 5e3
|
|
7325
7584
|
});
|
|
7326
7585
|
(0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
7327
|
-
|
|
7586
|
+
import_fs20.default.unlinkSync(SYSTEMD_UNIT);
|
|
7328
7587
|
}
|
|
7329
7588
|
}
|
|
7330
7589
|
function isSystemdInstalled() {
|
|
7331
|
-
return
|
|
7590
|
+
return import_fs20.default.existsSync(SYSTEMD_UNIT);
|
|
7332
7591
|
}
|
|
7333
7592
|
function stopRunningDaemon() {
|
|
7334
|
-
const pidFile =
|
|
7335
|
-
if (!
|
|
7593
|
+
const pidFile = import_path23.default.join(import_os17.default.homedir(), ".node9", "daemon.pid");
|
|
7594
|
+
if (!import_fs20.default.existsSync(pidFile)) return;
|
|
7336
7595
|
try {
|
|
7337
|
-
const data = JSON.parse(
|
|
7596
|
+
const data = JSON.parse(import_fs20.default.readFileSync(pidFile, "utf-8"));
|
|
7338
7597
|
const pid = data.pid;
|
|
7339
7598
|
const MAX_PID2 = 4194304;
|
|
7340
7599
|
if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
|
|
@@ -7354,7 +7613,7 @@ function stopRunningDaemon() {
|
|
|
7354
7613
|
}
|
|
7355
7614
|
}
|
|
7356
7615
|
try {
|
|
7357
|
-
|
|
7616
|
+
import_fs20.default.unlinkSync(pidFile);
|
|
7358
7617
|
} catch {
|
|
7359
7618
|
}
|
|
7360
7619
|
} catch {
|
|
@@ -7424,26 +7683,26 @@ function isDaemonServiceInstalled() {
|
|
|
7424
7683
|
if (process.platform === "linux") return isSystemdInstalled();
|
|
7425
7684
|
return false;
|
|
7426
7685
|
}
|
|
7427
|
-
var
|
|
7686
|
+
var import_fs20, import_path23, import_os17, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
|
|
7428
7687
|
var init_service = __esm({
|
|
7429
7688
|
"src/daemon/service.ts"() {
|
|
7430
7689
|
"use strict";
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7690
|
+
import_fs20 = __toESM(require("fs"));
|
|
7691
|
+
import_path23 = __toESM(require("path"));
|
|
7692
|
+
import_os17 = __toESM(require("os"));
|
|
7434
7693
|
import_child_process5 = require("child_process");
|
|
7435
7694
|
LAUNCHD_LABEL = "ai.node9.daemon";
|
|
7436
|
-
LAUNCHD_PLIST =
|
|
7437
|
-
SYSTEMD_UNIT_DIR =
|
|
7438
|
-
SYSTEMD_UNIT =
|
|
7695
|
+
LAUNCHD_PLIST = import_path23.default.join(import_os17.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
7696
|
+
SYSTEMD_UNIT_DIR = import_path23.default.join(import_os17.default.homedir(), ".config", "systemd", "user");
|
|
7697
|
+
SYSTEMD_UNIT = import_path23.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
|
|
7439
7698
|
}
|
|
7440
7699
|
});
|
|
7441
7700
|
|
|
7442
7701
|
// src/daemon/index.ts
|
|
7443
7702
|
function stopDaemon() {
|
|
7444
|
-
if (!
|
|
7703
|
+
if (!import_fs21.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
7445
7704
|
try {
|
|
7446
|
-
const data = JSON.parse(
|
|
7705
|
+
const data = JSON.parse(import_fs21.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7447
7706
|
const pid = data.pid;
|
|
7448
7707
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
7449
7708
|
console.log(import_chalk3.default.gray("Cleaned up invalid PID file."));
|
|
@@ -7455,7 +7714,7 @@ function stopDaemon() {
|
|
|
7455
7714
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
7456
7715
|
} finally {
|
|
7457
7716
|
try {
|
|
7458
|
-
|
|
7717
|
+
import_fs21.default.unlinkSync(DAEMON_PID_FILE);
|
|
7459
7718
|
} catch {
|
|
7460
7719
|
}
|
|
7461
7720
|
}
|
|
@@ -7464,9 +7723,9 @@ function daemonStatus() {
|
|
|
7464
7723
|
const serviceInstalled = isDaemonServiceInstalled();
|
|
7465
7724
|
const serviceLabel = serviceInstalled ? import_chalk3.default.green("installed (starts on login)") : import_chalk3.default.yellow("not installed \u2014 run: node9 daemon install");
|
|
7466
7725
|
let processStatus;
|
|
7467
|
-
if (
|
|
7726
|
+
if (import_fs21.default.existsSync(DAEMON_PID_FILE)) {
|
|
7468
7727
|
try {
|
|
7469
|
-
const data = JSON.parse(
|
|
7728
|
+
const data = JSON.parse(import_fs21.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
7470
7729
|
const pid = data.pid;
|
|
7471
7730
|
const port = data.port;
|
|
7472
7731
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
|
|
@@ -7496,11 +7755,11 @@ function daemonStatus() {
|
|
|
7496
7755
|
console.log(` Service : ${serviceLabel}
|
|
7497
7756
|
`);
|
|
7498
7757
|
}
|
|
7499
|
-
var
|
|
7758
|
+
var import_fs21, import_chalk3, import_child_process6, MAX_PID;
|
|
7500
7759
|
var init_daemon2 = __esm({
|
|
7501
7760
|
"src/daemon/index.ts"() {
|
|
7502
7761
|
"use strict";
|
|
7503
|
-
|
|
7762
|
+
import_fs21 = __toESM(require("fs"));
|
|
7504
7763
|
import_chalk3 = __toESM(require("chalk"));
|
|
7505
7764
|
import_child_process6 = require("child_process");
|
|
7506
7765
|
init_server();
|
|
@@ -7524,6 +7783,74 @@ function getIcon(tool) {
|
|
|
7524
7783
|
}
|
|
7525
7784
|
return "\u{1F6E0}\uFE0F";
|
|
7526
7785
|
}
|
|
7786
|
+
function getModelContextLimit(model) {
|
|
7787
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
7788
|
+
for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
|
|
7789
|
+
if (base.startsWith(key)) return limit;
|
|
7790
|
+
}
|
|
7791
|
+
return 2e5;
|
|
7792
|
+
}
|
|
7793
|
+
function readSessionUsage() {
|
|
7794
|
+
const projectsDir = import_path40.default.join(import_os33.default.homedir(), ".claude", "projects");
|
|
7795
|
+
if (!import_fs37.default.existsSync(projectsDir)) return null;
|
|
7796
|
+
let latestFile = null;
|
|
7797
|
+
let latestMtime = 0;
|
|
7798
|
+
try {
|
|
7799
|
+
for (const dir of import_fs37.default.readdirSync(projectsDir)) {
|
|
7800
|
+
const dirPath = import_path40.default.join(projectsDir, dir);
|
|
7801
|
+
try {
|
|
7802
|
+
if (!import_fs37.default.statSync(dirPath).isDirectory()) continue;
|
|
7803
|
+
for (const file of import_fs37.default.readdirSync(dirPath)) {
|
|
7804
|
+
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
7805
|
+
const filePath = import_path40.default.join(dirPath, file);
|
|
7806
|
+
try {
|
|
7807
|
+
const mtime = import_fs37.default.statSync(filePath).mtimeMs;
|
|
7808
|
+
if (mtime > latestMtime) {
|
|
7809
|
+
latestMtime = mtime;
|
|
7810
|
+
latestFile = filePath;
|
|
7811
|
+
}
|
|
7812
|
+
} catch {
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
} catch {
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
} catch {
|
|
7819
|
+
}
|
|
7820
|
+
if (!latestFile) return null;
|
|
7821
|
+
try {
|
|
7822
|
+
const lines = import_fs37.default.readFileSync(latestFile, "utf-8").split("\n");
|
|
7823
|
+
let lastModel = "";
|
|
7824
|
+
let lastInput = 0;
|
|
7825
|
+
let lastOutput = 0;
|
|
7826
|
+
for (const line of lines) {
|
|
7827
|
+
if (!line.trim()) continue;
|
|
7828
|
+
try {
|
|
7829
|
+
const entry = JSON.parse(line);
|
|
7830
|
+
if (entry.type !== "assistant" || !entry.message?.usage) continue;
|
|
7831
|
+
const u = entry.message.usage;
|
|
7832
|
+
lastInput = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
7833
|
+
lastOutput = u.output_tokens ?? 0;
|
|
7834
|
+
if (entry.message.model) lastModel = entry.message.model;
|
|
7835
|
+
} catch {
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
if (!lastModel || lastInput === 0) return null;
|
|
7839
|
+
const limit = getModelContextLimit(lastModel);
|
|
7840
|
+
const fillPct = Math.round(lastInput / limit * 100);
|
|
7841
|
+
return { inputTokens: lastInput, outputTokens: lastOutput, model: lastModel, fillPct };
|
|
7842
|
+
} catch {
|
|
7843
|
+
return null;
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
function formatContextStat(stat) {
|
|
7847
|
+
const pctColor = stat.fillPct >= 80 ? import_chalk25.default.red : stat.fillPct >= 50 ? import_chalk25.default.yellow : import_chalk25.default.cyan;
|
|
7848
|
+
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
7849
|
+
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
7850
|
+
return import_chalk25.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk25.default.dim(
|
|
7851
|
+
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
7852
|
+
);
|
|
7853
|
+
}
|
|
7527
7854
|
function visibleLength(s) {
|
|
7528
7855
|
return s.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
7529
7856
|
}
|
|
@@ -7533,26 +7860,31 @@ function wrappedLineCount(text) {
|
|
|
7533
7860
|
const len = visibleLength(text);
|
|
7534
7861
|
return Math.max(1, Math.ceil(len / cols));
|
|
7535
7862
|
}
|
|
7863
|
+
function agentLabel(agent) {
|
|
7864
|
+
if (!agent || agent === "Terminal") return "";
|
|
7865
|
+
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
7866
|
+
return short ? import_chalk25.default.dim(`[${short}] `) : "";
|
|
7867
|
+
}
|
|
7536
7868
|
function formatBase(activity) {
|
|
7537
7869
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
7538
7870
|
const icon = getIcon(activity.tool);
|
|
7539
7871
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
7540
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7872
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os33.default.homedir(), "~");
|
|
7541
7873
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
7542
|
-
return `${
|
|
7874
|
+
return `${import_chalk25.default.gray(time)} ${icon} ${agentLabel(activity.agent)}${import_chalk25.default.white.bold(toolName)} ${import_chalk25.default.dim(argsPreview)}`;
|
|
7543
7875
|
}
|
|
7544
7876
|
function renderResult(activity, result) {
|
|
7545
7877
|
const base = formatBase(activity);
|
|
7546
7878
|
let status;
|
|
7547
7879
|
if (result.status === "allow") {
|
|
7548
|
-
status =
|
|
7880
|
+
status = import_chalk25.default.green("\u2713 ALLOW");
|
|
7549
7881
|
} else if (result.status === "dlp") {
|
|
7550
|
-
status =
|
|
7882
|
+
status = import_chalk25.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
7551
7883
|
} else {
|
|
7552
|
-
status =
|
|
7884
|
+
status = import_chalk25.default.red("\u2717 BLOCK");
|
|
7553
7885
|
}
|
|
7554
7886
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
7555
|
-
const costSuffix = cost == null ? "" :
|
|
7887
|
+
const costSuffix = cost == null ? "" : import_chalk25.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
7556
7888
|
if (process.stdout.isTTY) {
|
|
7557
7889
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7558
7890
|
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -7569,19 +7901,19 @@ function renderResult(activity, result) {
|
|
|
7569
7901
|
}
|
|
7570
7902
|
function renderPending(activity) {
|
|
7571
7903
|
if (!process.stdout.isTTY) return;
|
|
7572
|
-
const line = `${formatBase(activity)} ${
|
|
7904
|
+
const line = `${formatBase(activity)} ${import_chalk25.default.yellow("\u25CF \u2026")}`;
|
|
7573
7905
|
pendingShownForId = activity.id;
|
|
7574
7906
|
pendingWrappedLines = wrappedLineCount(line);
|
|
7575
7907
|
process.stdout.write(`${line}\r`);
|
|
7576
7908
|
}
|
|
7577
7909
|
async function ensureDaemon() {
|
|
7578
7910
|
let pidPort = null;
|
|
7579
|
-
if (
|
|
7911
|
+
if (import_fs37.default.existsSync(PID_FILE)) {
|
|
7580
7912
|
try {
|
|
7581
|
-
const { port } = JSON.parse(
|
|
7913
|
+
const { port } = JSON.parse(import_fs37.default.readFileSync(PID_FILE, "utf-8"));
|
|
7582
7914
|
pidPort = port;
|
|
7583
7915
|
} catch {
|
|
7584
|
-
console.error(
|
|
7916
|
+
console.error(import_chalk25.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
7585
7917
|
}
|
|
7586
7918
|
}
|
|
7587
7919
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -7592,7 +7924,7 @@ async function ensureDaemon() {
|
|
|
7592
7924
|
if (res.ok) return checkPort;
|
|
7593
7925
|
} catch {
|
|
7594
7926
|
}
|
|
7595
|
-
console.log(
|
|
7927
|
+
console.log(import_chalk25.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
7596
7928
|
const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
7597
7929
|
detached: true,
|
|
7598
7930
|
stdio: "ignore",
|
|
@@ -7609,7 +7941,7 @@ async function ensureDaemon() {
|
|
|
7609
7941
|
} catch {
|
|
7610
7942
|
}
|
|
7611
7943
|
}
|
|
7612
|
-
console.error(
|
|
7944
|
+
console.error(import_chalk25.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
7613
7945
|
process.exit(1);
|
|
7614
7946
|
}
|
|
7615
7947
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -7675,10 +8007,11 @@ function buildCardLines(req, localCount = 0) {
|
|
|
7675
8007
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
7676
8008
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
7677
8009
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
8010
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk25.default.dim(`(${req.agent})`)}` : "";
|
|
7678
8011
|
const lines = [
|
|
7679
8012
|
``,
|
|
7680
8013
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
7681
|
-
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
8014
|
+
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}${agentSuffix}`,
|
|
7682
8015
|
`${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
|
|
7683
8016
|
];
|
|
7684
8017
|
if (description) {
|
|
@@ -7730,9 +8063,9 @@ function buildRecoveryCardLines(req) {
|
|
|
7730
8063
|
];
|
|
7731
8064
|
}
|
|
7732
8065
|
function readApproversFromDisk() {
|
|
7733
|
-
const configPath =
|
|
8066
|
+
const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
|
|
7734
8067
|
try {
|
|
7735
|
-
const raw = JSON.parse(
|
|
8068
|
+
const raw = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
|
|
7736
8069
|
const settings = raw.settings ?? {};
|
|
7737
8070
|
return settings.approvers ?? {};
|
|
7738
8071
|
} catch {
|
|
@@ -7743,20 +8076,20 @@ function approverStatusLine() {
|
|
|
7743
8076
|
const a = readApproversFromDisk();
|
|
7744
8077
|
const fmt = (label, key) => {
|
|
7745
8078
|
const on = a[key] !== false;
|
|
7746
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
8079
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk25.default.green("\u2713") : import_chalk25.default.dim("\u2717")}`;
|
|
7747
8080
|
};
|
|
7748
8081
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
7749
8082
|
}
|
|
7750
8083
|
function toggleApprover(channel) {
|
|
7751
|
-
const configPath =
|
|
8084
|
+
const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
|
|
7752
8085
|
try {
|
|
7753
|
-
const raw = JSON.parse(
|
|
8086
|
+
const raw = JSON.parse(import_fs37.default.readFileSync(configPath, "utf-8"));
|
|
7754
8087
|
const settings = raw.settings ?? {};
|
|
7755
8088
|
const approvers = settings.approvers ?? {};
|
|
7756
8089
|
approvers[channel] = approvers[channel] === false;
|
|
7757
8090
|
settings.approvers = approvers;
|
|
7758
8091
|
raw.settings = settings;
|
|
7759
|
-
|
|
8092
|
+
import_fs37.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7760
8093
|
} catch (err2) {
|
|
7761
8094
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
7762
8095
|
`);
|
|
@@ -7788,7 +8121,7 @@ async function startTail(options = {}) {
|
|
|
7788
8121
|
req2.end();
|
|
7789
8122
|
});
|
|
7790
8123
|
if (result.ok) {
|
|
7791
|
-
console.log(
|
|
8124
|
+
console.log(import_chalk25.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7792
8125
|
} else if (result.code === "ECONNREFUSED") {
|
|
7793
8126
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7794
8127
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7832,7 +8165,7 @@ async function startTail(options = {}) {
|
|
|
7832
8165
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7833
8166
|
if (channel) {
|
|
7834
8167
|
toggleApprover(channel);
|
|
7835
|
-
console.log(
|
|
8168
|
+
console.log(import_chalk25.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7836
8169
|
}
|
|
7837
8170
|
};
|
|
7838
8171
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7898,7 +8231,7 @@ async function startTail(options = {}) {
|
|
|
7898
8231
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7899
8232
|
)
|
|
7900
8233
|
);
|
|
7901
|
-
const decisionStamp = action === "always-allow" ?
|
|
8234
|
+
const decisionStamp = action === "always-allow" ? import_chalk25.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk25.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk25.default.yellow("\u21A9 REDIRECT AI") : import_chalk25.default.red("\u2717 DENIED");
|
|
7902
8235
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7903
8236
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7904
8237
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7926,8 +8259,8 @@ async function startTail(options = {}) {
|
|
|
7926
8259
|
}
|
|
7927
8260
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7928
8261
|
try {
|
|
7929
|
-
|
|
7930
|
-
|
|
8262
|
+
import_fs37.default.appendFileSync(
|
|
8263
|
+
import_path40.default.join(import_os33.default.homedir(), ".node9", "hook-debug.log"),
|
|
7931
8264
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7932
8265
|
`
|
|
7933
8266
|
);
|
|
@@ -7949,7 +8282,7 @@ async function startTail(options = {}) {
|
|
|
7949
8282
|
);
|
|
7950
8283
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7951
8284
|
if (externalDecision) {
|
|
7952
|
-
const source = externalDecision === "allow" ?
|
|
8285
|
+
const source = externalDecision === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : import_chalk25.default.red("\u2717 DENIED");
|
|
7953
8286
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7954
8287
|
}
|
|
7955
8288
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -8008,16 +8341,31 @@ async function startTail(options = {}) {
|
|
|
8008
8341
|
}
|
|
8009
8342
|
} catch {
|
|
8010
8343
|
}
|
|
8011
|
-
|
|
8012
|
-
|
|
8344
|
+
const auditLog = import_path40.default.join(import_os33.default.homedir(), ".node9", "audit.log");
|
|
8345
|
+
try {
|
|
8346
|
+
const unackedDlp = import_fs37.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
8347
|
+
if (unackedDlp > 0) {
|
|
8348
|
+
console.log("");
|
|
8349
|
+
console.log(
|
|
8350
|
+
import_chalk25.default.bgRed.white.bold(
|
|
8351
|
+
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
8352
|
+
)
|
|
8353
|
+
);
|
|
8354
|
+
}
|
|
8355
|
+
} catch {
|
|
8356
|
+
}
|
|
8357
|
+
console.log(import_chalk25.default.cyan.bold(`
|
|
8358
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk25.default.dim(`\u2192 ${dashboardUrl}`));
|
|
8013
8359
|
if (canApprove) {
|
|
8014
|
-
console.log(
|
|
8015
|
-
console.log(
|
|
8360
|
+
console.log(import_chalk25.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
8361
|
+
console.log(import_chalk25.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
8016
8362
|
}
|
|
8363
|
+
const ctxStat = readSessionUsage();
|
|
8364
|
+
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
8017
8365
|
if (options.history) {
|
|
8018
|
-
console.log(
|
|
8366
|
+
console.log(import_chalk25.default.dim("Showing history + live events.\n"));
|
|
8019
8367
|
} else {
|
|
8020
|
-
console.log(
|
|
8368
|
+
console.log(import_chalk25.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
8021
8369
|
}
|
|
8022
8370
|
process.on("SIGINT", () => {
|
|
8023
8371
|
exitIdleMode();
|
|
@@ -8027,13 +8375,13 @@ async function startTail(options = {}) {
|
|
|
8027
8375
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
8028
8376
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
8029
8377
|
}
|
|
8030
|
-
console.log(
|
|
8378
|
+
console.log(import_chalk25.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
8031
8379
|
process.exit(0);
|
|
8032
8380
|
});
|
|
8033
8381
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
8034
8382
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
8035
8383
|
if (res.statusCode !== 200) {
|
|
8036
|
-
console.error(
|
|
8384
|
+
console.error(import_chalk25.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
8037
8385
|
process.exit(1);
|
|
8038
8386
|
}
|
|
8039
8387
|
if (canApprove) enterIdleMode();
|
|
@@ -8064,7 +8412,7 @@ async function startTail(options = {}) {
|
|
|
8064
8412
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
8065
8413
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
8066
8414
|
}
|
|
8067
|
-
console.log(
|
|
8415
|
+
console.log(import_chalk25.default.red("\n\u274C Daemon disconnected."));
|
|
8068
8416
|
process.exit(1);
|
|
8069
8417
|
});
|
|
8070
8418
|
});
|
|
@@ -8156,9 +8504,9 @@ async function startTail(options = {}) {
|
|
|
8156
8504
|
const hash = data.hash ?? "";
|
|
8157
8505
|
const summary = data.argsSummary ?? data.tool;
|
|
8158
8506
|
const fileCount = data.fileCount ?? 0;
|
|
8159
|
-
const files = fileCount > 0 ?
|
|
8507
|
+
const files = fileCount > 0 ? import_chalk25.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
8160
8508
|
process.stdout.write(
|
|
8161
|
-
`${
|
|
8509
|
+
`${import_chalk25.default.dim(time)} ${import_chalk25.default.cyan("\u{1F4F8} snapshot")} ${import_chalk25.default.dim(hash)} ${summary}${files}
|
|
8162
8510
|
`
|
|
8163
8511
|
);
|
|
8164
8512
|
return;
|
|
@@ -8175,26 +8523,26 @@ async function startTail(options = {}) {
|
|
|
8175
8523
|
}
|
|
8176
8524
|
req.on("error", (err2) => {
|
|
8177
8525
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
8178
|
-
console.error(
|
|
8526
|
+
console.error(import_chalk25.default.red(`
|
|
8179
8527
|
\u274C ${msg}`));
|
|
8180
8528
|
process.exit(1);
|
|
8181
8529
|
});
|
|
8182
8530
|
}
|
|
8183
|
-
var import_http2,
|
|
8531
|
+
var import_http2, import_chalk25, import_fs37, import_os33, import_path40, import_readline5, import_child_process15, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
8184
8532
|
var init_tail = __esm({
|
|
8185
8533
|
"src/tui/tail.ts"() {
|
|
8186
8534
|
"use strict";
|
|
8187
8535
|
import_http2 = __toESM(require("http"));
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8536
|
+
import_chalk25 = __toESM(require("chalk"));
|
|
8537
|
+
import_fs37 = __toESM(require("fs"));
|
|
8538
|
+
import_os33 = __toESM(require("os"));
|
|
8539
|
+
import_path40 = __toESM(require("path"));
|
|
8192
8540
|
import_readline5 = __toESM(require("readline"));
|
|
8193
8541
|
import_child_process15 = require("child_process");
|
|
8194
8542
|
init_daemon2();
|
|
8195
8543
|
init_daemon();
|
|
8196
8544
|
init_core();
|
|
8197
|
-
PID_FILE =
|
|
8545
|
+
PID_FILE = import_path40.default.join(import_os33.default.homedir(), ".node9", "daemon.pid");
|
|
8198
8546
|
ICONS = {
|
|
8199
8547
|
bash: "\u{1F4BB}",
|
|
8200
8548
|
shell: "\u{1F4BB}",
|
|
@@ -8212,6 +8560,13 @@ var init_tail = __esm({
|
|
|
8212
8560
|
delete: "\u{1F5D1}\uFE0F",
|
|
8213
8561
|
web: "\u{1F310}"
|
|
8214
8562
|
};
|
|
8563
|
+
MODEL_CONTEXT_LIMITS = {
|
|
8564
|
+
"claude-opus-4": 2e5,
|
|
8565
|
+
"claude-sonnet-4": 2e5,
|
|
8566
|
+
"claude-haiku-4": 2e5,
|
|
8567
|
+
"claude-3-7": 2e5,
|
|
8568
|
+
"claude-3-5": 2e5
|
|
8569
|
+
};
|
|
8215
8570
|
RESET2 = "\x1B[0m";
|
|
8216
8571
|
BOLD2 = "\x1B[1m";
|
|
8217
8572
|
RED = "\x1B[31m";
|
|
@@ -8309,9 +8664,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
8309
8664
|
return ` (${m}m left)`;
|
|
8310
8665
|
}
|
|
8311
8666
|
function safeReadJson(filePath) {
|
|
8312
|
-
if (!
|
|
8667
|
+
if (!import_fs38.default.existsSync(filePath)) return null;
|
|
8313
8668
|
try {
|
|
8314
|
-
return JSON.parse(
|
|
8669
|
+
return JSON.parse(import_fs38.default.readFileSync(filePath, "utf-8"));
|
|
8315
8670
|
} catch {
|
|
8316
8671
|
return null;
|
|
8317
8672
|
}
|
|
@@ -8332,12 +8687,12 @@ function countHooksInFile(filePath) {
|
|
|
8332
8687
|
return Object.keys(cfg.hooks).length;
|
|
8333
8688
|
}
|
|
8334
8689
|
function countRulesInDir(rulesDir) {
|
|
8335
|
-
if (!
|
|
8690
|
+
if (!import_fs38.default.existsSync(rulesDir)) return 0;
|
|
8336
8691
|
let count = 0;
|
|
8337
8692
|
try {
|
|
8338
|
-
for (const entry of
|
|
8693
|
+
for (const entry of import_fs38.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
8339
8694
|
if (entry.isDirectory()) {
|
|
8340
|
-
count += countRulesInDir(
|
|
8695
|
+
count += countRulesInDir(import_path41.default.join(rulesDir, entry.name));
|
|
8341
8696
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
8342
8697
|
count++;
|
|
8343
8698
|
}
|
|
@@ -8348,46 +8703,46 @@ function countRulesInDir(rulesDir) {
|
|
|
8348
8703
|
}
|
|
8349
8704
|
function isSamePath(a, b) {
|
|
8350
8705
|
try {
|
|
8351
|
-
return
|
|
8706
|
+
return import_path41.default.resolve(a) === import_path41.default.resolve(b);
|
|
8352
8707
|
} catch {
|
|
8353
8708
|
return false;
|
|
8354
8709
|
}
|
|
8355
8710
|
}
|
|
8356
8711
|
function countConfigs(cwd) {
|
|
8357
|
-
const homeDir2 =
|
|
8358
|
-
const claudeDir =
|
|
8712
|
+
const homeDir2 = import_os34.default.homedir();
|
|
8713
|
+
const claudeDir = import_path41.default.join(homeDir2, ".claude");
|
|
8359
8714
|
let claudeMdCount = 0;
|
|
8360
8715
|
let rulesCount = 0;
|
|
8361
8716
|
let hooksCount = 0;
|
|
8362
8717
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
8363
8718
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
8364
|
-
if (
|
|
8365
|
-
rulesCount += countRulesInDir(
|
|
8366
|
-
const userSettings =
|
|
8719
|
+
if (import_fs38.default.existsSync(import_path41.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8720
|
+
rulesCount += countRulesInDir(import_path41.default.join(claudeDir, "rules"));
|
|
8721
|
+
const userSettings = import_path41.default.join(claudeDir, "settings.json");
|
|
8367
8722
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
8368
8723
|
hooksCount += countHooksInFile(userSettings);
|
|
8369
|
-
const userClaudeJson =
|
|
8724
|
+
const userClaudeJson = import_path41.default.join(homeDir2, ".claude.json");
|
|
8370
8725
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
8371
8726
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
8372
8727
|
userMcpServers.delete(name);
|
|
8373
8728
|
}
|
|
8374
8729
|
if (cwd) {
|
|
8375
|
-
if (
|
|
8376
|
-
if (
|
|
8377
|
-
const projectClaudeDir =
|
|
8730
|
+
if (import_fs38.default.existsSync(import_path41.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
8731
|
+
if (import_fs38.default.existsSync(import_path41.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8732
|
+
const projectClaudeDir = import_path41.default.join(cwd, ".claude");
|
|
8378
8733
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
8379
8734
|
if (!overlapsUserScope) {
|
|
8380
|
-
if (
|
|
8381
|
-
rulesCount += countRulesInDir(
|
|
8382
|
-
const projSettings =
|
|
8735
|
+
if (import_fs38.default.existsSync(import_path41.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
8736
|
+
rulesCount += countRulesInDir(import_path41.default.join(projectClaudeDir, "rules"));
|
|
8737
|
+
const projSettings = import_path41.default.join(projectClaudeDir, "settings.json");
|
|
8383
8738
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
8384
8739
|
hooksCount += countHooksInFile(projSettings);
|
|
8385
8740
|
}
|
|
8386
|
-
if (
|
|
8387
|
-
const localSettings =
|
|
8741
|
+
if (import_fs38.default.existsSync(import_path41.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
8742
|
+
const localSettings = import_path41.default.join(projectClaudeDir, "settings.local.json");
|
|
8388
8743
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
8389
8744
|
hooksCount += countHooksInFile(localSettings);
|
|
8390
|
-
const mcpJsonServers = getMcpServerNames(
|
|
8745
|
+
const mcpJsonServers = getMcpServerNames(import_path41.default.join(cwd, ".mcp.json"));
|
|
8391
8746
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
8392
8747
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
8393
8748
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -8420,12 +8775,12 @@ function readActiveShieldsHud() {
|
|
|
8420
8775
|
return shieldsCache.value;
|
|
8421
8776
|
}
|
|
8422
8777
|
try {
|
|
8423
|
-
const shieldsPath =
|
|
8424
|
-
if (!
|
|
8778
|
+
const shieldsPath = import_path41.default.join(import_os34.default.homedir(), ".node9", "shields.json");
|
|
8779
|
+
if (!import_fs38.default.existsSync(shieldsPath)) {
|
|
8425
8780
|
shieldsCache = { value: [], ts: now };
|
|
8426
8781
|
return [];
|
|
8427
8782
|
}
|
|
8428
|
-
const parsed = JSON.parse(
|
|
8783
|
+
const parsed = JSON.parse(import_fs38.default.readFileSync(shieldsPath, "utf-8"));
|
|
8429
8784
|
if (!Array.isArray(parsed.active)) {
|
|
8430
8785
|
shieldsCache = { value: [], ts: now };
|
|
8431
8786
|
return [];
|
|
@@ -8527,17 +8882,17 @@ function renderContextLine(stdin) {
|
|
|
8527
8882
|
async function main() {
|
|
8528
8883
|
try {
|
|
8529
8884
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
8530
|
-
if (
|
|
8885
|
+
if (import_fs38.default.existsSync(import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug"))) {
|
|
8531
8886
|
try {
|
|
8532
|
-
const logPath =
|
|
8887
|
+
const logPath = import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug.log");
|
|
8533
8888
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
8534
8889
|
let size = 0;
|
|
8535
8890
|
try {
|
|
8536
|
-
size =
|
|
8891
|
+
size = import_fs38.default.statSync(logPath).size;
|
|
8537
8892
|
} catch {
|
|
8538
8893
|
}
|
|
8539
8894
|
if (size < MAX_LOG_SIZE) {
|
|
8540
|
-
|
|
8895
|
+
import_fs38.default.appendFileSync(
|
|
8541
8896
|
logPath,
|
|
8542
8897
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
8543
8898
|
);
|
|
@@ -8558,11 +8913,11 @@ async function main() {
|
|
|
8558
8913
|
try {
|
|
8559
8914
|
const cwd = stdin.cwd ?? process.cwd();
|
|
8560
8915
|
for (const configPath of [
|
|
8561
|
-
|
|
8562
|
-
|
|
8916
|
+
import_path41.default.join(cwd, "node9.config.json"),
|
|
8917
|
+
import_path41.default.join(import_os34.default.homedir(), ".node9", "config.json")
|
|
8563
8918
|
]) {
|
|
8564
|
-
if (!
|
|
8565
|
-
const cfg = JSON.parse(
|
|
8919
|
+
if (!import_fs38.default.existsSync(configPath)) continue;
|
|
8920
|
+
const cfg = JSON.parse(import_fs38.default.readFileSync(configPath, "utf-8"));
|
|
8566
8921
|
const hud = cfg.settings?.hud;
|
|
8567
8922
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
8568
8923
|
}
|
|
@@ -8580,13 +8935,13 @@ async function main() {
|
|
|
8580
8935
|
renderOffline();
|
|
8581
8936
|
}
|
|
8582
8937
|
}
|
|
8583
|
-
var
|
|
8938
|
+
var import_fs38, import_path41, import_os34, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
8584
8939
|
var init_hud = __esm({
|
|
8585
8940
|
"src/cli/hud.ts"() {
|
|
8586
8941
|
"use strict";
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8942
|
+
import_fs38 = __toESM(require("fs"));
|
|
8943
|
+
import_path41 = __toESM(require("path"));
|
|
8944
|
+
import_os34 = __toESM(require("os"));
|
|
8590
8945
|
import_http3 = __toESM(require("http"));
|
|
8591
8946
|
init_daemon();
|
|
8592
8947
|
RESET3 = "\x1B[0m";
|
|
@@ -9501,10 +9856,10 @@ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
|
|
|
9501
9856
|
|
|
9502
9857
|
// src/cli.ts
|
|
9503
9858
|
init_daemon2();
|
|
9504
|
-
var
|
|
9505
|
-
var
|
|
9506
|
-
var
|
|
9507
|
-
var
|
|
9859
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
9860
|
+
var import_fs39 = __toESM(require("fs"));
|
|
9861
|
+
var import_path42 = __toESM(require("path"));
|
|
9862
|
+
var import_os35 = __toESM(require("os"));
|
|
9508
9863
|
var import_prompts2 = require("@inquirer/prompts");
|
|
9509
9864
|
|
|
9510
9865
|
// src/utils/duration.ts
|
|
@@ -9729,10 +10084,10 @@ async function autoStartDaemonAndWait() {
|
|
|
9729
10084
|
|
|
9730
10085
|
// src/cli/commands/check.ts
|
|
9731
10086
|
var import_chalk5 = __toESM(require("chalk"));
|
|
9732
|
-
var
|
|
10087
|
+
var import_fs24 = __toESM(require("fs"));
|
|
9733
10088
|
var import_child_process10 = require("child_process");
|
|
9734
|
-
var
|
|
9735
|
-
var
|
|
10089
|
+
var import_path26 = __toESM(require("path"));
|
|
10090
|
+
var import_os20 = __toESM(require("os"));
|
|
9736
10091
|
init_orchestrator();
|
|
9737
10092
|
init_daemon();
|
|
9738
10093
|
init_config();
|
|
@@ -9741,11 +10096,11 @@ init_policy();
|
|
|
9741
10096
|
// src/undo.ts
|
|
9742
10097
|
var import_child_process9 = require("child_process");
|
|
9743
10098
|
var import_crypto8 = __toESM(require("crypto"));
|
|
9744
|
-
var
|
|
10099
|
+
var import_fs22 = __toESM(require("fs"));
|
|
9745
10100
|
var import_net3 = __toESM(require("net"));
|
|
9746
|
-
var
|
|
9747
|
-
var
|
|
9748
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
10101
|
+
var import_path24 = __toESM(require("path"));
|
|
10102
|
+
var import_os18 = __toESM(require("os"));
|
|
10103
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path24.default.join(import_os18.default.tmpdir(), "node9-activity.sock");
|
|
9749
10104
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
9750
10105
|
try {
|
|
9751
10106
|
const payload = JSON.stringify({
|
|
@@ -9765,22 +10120,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
9765
10120
|
} catch {
|
|
9766
10121
|
}
|
|
9767
10122
|
}
|
|
9768
|
-
var SNAPSHOT_STACK_PATH =
|
|
9769
|
-
var UNDO_LATEST_PATH =
|
|
10123
|
+
var SNAPSHOT_STACK_PATH = import_path24.default.join(import_os18.default.homedir(), ".node9", "snapshots.json");
|
|
10124
|
+
var UNDO_LATEST_PATH = import_path24.default.join(import_os18.default.homedir(), ".node9", "undo_latest.txt");
|
|
9770
10125
|
var MAX_SNAPSHOTS = 10;
|
|
9771
10126
|
var GIT_TIMEOUT = 15e3;
|
|
9772
10127
|
function readStack() {
|
|
9773
10128
|
try {
|
|
9774
|
-
if (
|
|
9775
|
-
return JSON.parse(
|
|
10129
|
+
if (import_fs22.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
10130
|
+
return JSON.parse(import_fs22.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
9776
10131
|
} catch {
|
|
9777
10132
|
}
|
|
9778
10133
|
return [];
|
|
9779
10134
|
}
|
|
9780
10135
|
function writeStack(stack) {
|
|
9781
|
-
const dir =
|
|
9782
|
-
if (!
|
|
9783
|
-
|
|
10136
|
+
const dir = import_path24.default.dirname(SNAPSHOT_STACK_PATH);
|
|
10137
|
+
if (!import_fs22.default.existsSync(dir)) import_fs22.default.mkdirSync(dir, { recursive: true });
|
|
10138
|
+
import_fs22.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
9784
10139
|
}
|
|
9785
10140
|
function extractFilePath(args) {
|
|
9786
10141
|
if (!args || typeof args !== "object") return null;
|
|
@@ -9800,12 +10155,12 @@ function buildArgsSummary(tool, args) {
|
|
|
9800
10155
|
return "";
|
|
9801
10156
|
}
|
|
9802
10157
|
function findProjectRoot(filePath) {
|
|
9803
|
-
let dir =
|
|
10158
|
+
let dir = import_path24.default.dirname(filePath);
|
|
9804
10159
|
while (true) {
|
|
9805
|
-
if (
|
|
10160
|
+
if (import_fs22.default.existsSync(import_path24.default.join(dir, ".git")) || import_fs22.default.existsSync(import_path24.default.join(dir, "package.json"))) {
|
|
9806
10161
|
return dir;
|
|
9807
10162
|
}
|
|
9808
|
-
const parent =
|
|
10163
|
+
const parent = import_path24.default.dirname(dir);
|
|
9809
10164
|
if (parent === dir) return process.cwd();
|
|
9810
10165
|
dir = parent;
|
|
9811
10166
|
}
|
|
@@ -9813,7 +10168,7 @@ function findProjectRoot(filePath) {
|
|
|
9813
10168
|
function normalizeCwdForHash(cwd) {
|
|
9814
10169
|
let normalized;
|
|
9815
10170
|
try {
|
|
9816
|
-
normalized =
|
|
10171
|
+
normalized = import_fs22.default.realpathSync(cwd);
|
|
9817
10172
|
} catch {
|
|
9818
10173
|
normalized = cwd;
|
|
9819
10174
|
}
|
|
@@ -9823,16 +10178,16 @@ function normalizeCwdForHash(cwd) {
|
|
|
9823
10178
|
}
|
|
9824
10179
|
function getShadowRepoDir(cwd) {
|
|
9825
10180
|
const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
9826
|
-
return
|
|
10181
|
+
return import_path24.default.join(import_os18.default.homedir(), ".node9", "snapshots", hash);
|
|
9827
10182
|
}
|
|
9828
10183
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
9829
10184
|
try {
|
|
9830
10185
|
const cutoff = Date.now() - 6e4;
|
|
9831
|
-
for (const f of
|
|
10186
|
+
for (const f of import_fs22.default.readdirSync(shadowDir)) {
|
|
9832
10187
|
if (f.startsWith("index_")) {
|
|
9833
|
-
const fp =
|
|
10188
|
+
const fp = import_path24.default.join(shadowDir, f);
|
|
9834
10189
|
try {
|
|
9835
|
-
if (
|
|
10190
|
+
if (import_fs22.default.statSync(fp).mtimeMs < cutoff) import_fs22.default.unlinkSync(fp);
|
|
9836
10191
|
} catch {
|
|
9837
10192
|
}
|
|
9838
10193
|
}
|
|
@@ -9844,7 +10199,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
9844
10199
|
const hardcoded = [".git", ".node9"];
|
|
9845
10200
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
9846
10201
|
try {
|
|
9847
|
-
|
|
10202
|
+
import_fs22.default.writeFileSync(import_path24.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
9848
10203
|
} catch {
|
|
9849
10204
|
}
|
|
9850
10205
|
}
|
|
@@ -9857,25 +10212,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9857
10212
|
timeout: 3e3
|
|
9858
10213
|
});
|
|
9859
10214
|
if (check.status === 0) {
|
|
9860
|
-
const ptPath =
|
|
10215
|
+
const ptPath = import_path24.default.join(shadowDir, "project-path.txt");
|
|
9861
10216
|
try {
|
|
9862
|
-
const stored =
|
|
10217
|
+
const stored = import_fs22.default.readFileSync(ptPath, "utf8").trim();
|
|
9863
10218
|
if (stored === normalizedCwd) return true;
|
|
9864
10219
|
if (process.env.NODE9_DEBUG === "1")
|
|
9865
10220
|
console.error(
|
|
9866
10221
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
9867
10222
|
);
|
|
9868
|
-
|
|
10223
|
+
import_fs22.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
9869
10224
|
} catch {
|
|
9870
10225
|
try {
|
|
9871
|
-
|
|
10226
|
+
import_fs22.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
9872
10227
|
} catch {
|
|
9873
10228
|
}
|
|
9874
10229
|
return true;
|
|
9875
10230
|
}
|
|
9876
10231
|
}
|
|
9877
10232
|
try {
|
|
9878
|
-
|
|
10233
|
+
import_fs22.default.mkdirSync(shadowDir, { recursive: true });
|
|
9879
10234
|
} catch {
|
|
9880
10235
|
}
|
|
9881
10236
|
const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -9884,7 +10239,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9884
10239
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
9885
10240
|
return false;
|
|
9886
10241
|
}
|
|
9887
|
-
const configFile =
|
|
10242
|
+
const configFile = import_path24.default.join(shadowDir, "config");
|
|
9888
10243
|
(0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
9889
10244
|
timeout: 3e3
|
|
9890
10245
|
});
|
|
@@ -9892,7 +10247,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
9892
10247
|
timeout: 3e3
|
|
9893
10248
|
});
|
|
9894
10249
|
try {
|
|
9895
|
-
|
|
10250
|
+
import_fs22.default.writeFileSync(import_path24.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
9896
10251
|
} catch {
|
|
9897
10252
|
}
|
|
9898
10253
|
return true;
|
|
@@ -9912,12 +10267,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9912
10267
|
let indexFile = null;
|
|
9913
10268
|
try {
|
|
9914
10269
|
const rawFilePath = extractFilePath(args);
|
|
9915
|
-
const absFilePath = rawFilePath &&
|
|
10270
|
+
const absFilePath = rawFilePath && import_path24.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
9916
10271
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
9917
10272
|
const shadowDir = getShadowRepoDir(cwd);
|
|
9918
10273
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
9919
10274
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
9920
|
-
indexFile =
|
|
10275
|
+
indexFile = import_path24.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
9921
10276
|
const shadowEnv = {
|
|
9922
10277
|
...process.env,
|
|
9923
10278
|
GIT_DIR: shadowDir,
|
|
@@ -9989,7 +10344,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
9989
10344
|
writeStack(stack);
|
|
9990
10345
|
const entry = stack[stack.length - 1];
|
|
9991
10346
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
9992
|
-
|
|
10347
|
+
import_fs22.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
9993
10348
|
if (shouldGc) {
|
|
9994
10349
|
(0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
9995
10350
|
}
|
|
@@ -10000,7 +10355,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
10000
10355
|
} finally {
|
|
10001
10356
|
if (indexFile) {
|
|
10002
10357
|
try {
|
|
10003
|
-
|
|
10358
|
+
import_fs22.default.unlinkSync(indexFile);
|
|
10004
10359
|
} catch {
|
|
10005
10360
|
}
|
|
10006
10361
|
}
|
|
@@ -10076,9 +10431,9 @@ function applyUndo(hash, cwd) {
|
|
|
10076
10431
|
timeout: GIT_TIMEOUT
|
|
10077
10432
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
10078
10433
|
for (const file of [...tracked, ...untracked]) {
|
|
10079
|
-
const fullPath =
|
|
10080
|
-
if (!snapshotFiles.has(file) &&
|
|
10081
|
-
|
|
10434
|
+
const fullPath = import_path24.default.join(dir, file);
|
|
10435
|
+
if (!snapshotFiles.has(file) && import_fs22.default.existsSync(fullPath)) {
|
|
10436
|
+
import_fs22.default.unlinkSync(fullPath);
|
|
10082
10437
|
}
|
|
10083
10438
|
}
|
|
10084
10439
|
return true;
|
|
@@ -10087,6 +10442,187 @@ function applyUndo(hash, cwd) {
|
|
|
10087
10442
|
}
|
|
10088
10443
|
}
|
|
10089
10444
|
|
|
10445
|
+
// src/skill-pin.ts
|
|
10446
|
+
var import_fs23 = __toESM(require("fs"));
|
|
10447
|
+
var import_path25 = __toESM(require("path"));
|
|
10448
|
+
var import_os19 = __toESM(require("os"));
|
|
10449
|
+
var import_crypto9 = __toESM(require("crypto"));
|
|
10450
|
+
function getPinsFilePath() {
|
|
10451
|
+
return import_path25.default.join(import_os19.default.homedir(), ".node9", "skill-pins.json");
|
|
10452
|
+
}
|
|
10453
|
+
var MAX_FILES = 5e3;
|
|
10454
|
+
var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
10455
|
+
function sha256Bytes(buf) {
|
|
10456
|
+
return import_crypto9.default.createHash("sha256").update(buf).digest("hex");
|
|
10457
|
+
}
|
|
10458
|
+
function walkDir(root) {
|
|
10459
|
+
const out = [];
|
|
10460
|
+
let totalBytes = 0;
|
|
10461
|
+
const visit = (dir, relDir) => {
|
|
10462
|
+
if (out.length >= MAX_FILES) return;
|
|
10463
|
+
let entries;
|
|
10464
|
+
try {
|
|
10465
|
+
entries = import_fs23.default.readdirSync(dir, { withFileTypes: true });
|
|
10466
|
+
} catch {
|
|
10467
|
+
return;
|
|
10468
|
+
}
|
|
10469
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
10470
|
+
for (const entry of entries) {
|
|
10471
|
+
if (out.length >= MAX_FILES) return;
|
|
10472
|
+
const full = import_path25.default.join(dir, entry.name);
|
|
10473
|
+
const rel = relDir ? import_path25.default.posix.join(relDir, entry.name) : entry.name;
|
|
10474
|
+
let lst;
|
|
10475
|
+
try {
|
|
10476
|
+
lst = import_fs23.default.lstatSync(full);
|
|
10477
|
+
} catch {
|
|
10478
|
+
continue;
|
|
10479
|
+
}
|
|
10480
|
+
if (lst.isSymbolicLink()) continue;
|
|
10481
|
+
if (lst.isDirectory()) {
|
|
10482
|
+
visit(full, rel);
|
|
10483
|
+
continue;
|
|
10484
|
+
}
|
|
10485
|
+
if (!lst.isFile()) continue;
|
|
10486
|
+
if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
|
|
10487
|
+
try {
|
|
10488
|
+
const buf = import_fs23.default.readFileSync(full);
|
|
10489
|
+
totalBytes += buf.length;
|
|
10490
|
+
out.push({ rel, hash: sha256Bytes(buf) });
|
|
10491
|
+
} catch {
|
|
10492
|
+
}
|
|
10493
|
+
}
|
|
10494
|
+
};
|
|
10495
|
+
visit(root, "");
|
|
10496
|
+
out.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
10497
|
+
return out.map((e) => `${e.rel}\0${e.hash}`);
|
|
10498
|
+
}
|
|
10499
|
+
function hashSkillRoot(absPath) {
|
|
10500
|
+
let lst;
|
|
10501
|
+
try {
|
|
10502
|
+
lst = import_fs23.default.lstatSync(absPath);
|
|
10503
|
+
} catch {
|
|
10504
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10505
|
+
}
|
|
10506
|
+
if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
|
|
10507
|
+
if (lst.isFile()) {
|
|
10508
|
+
try {
|
|
10509
|
+
return { exists: true, contentHash: sha256Bytes(import_fs23.default.readFileSync(absPath)), fileCount: 1 };
|
|
10510
|
+
} catch {
|
|
10511
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10512
|
+
}
|
|
10513
|
+
}
|
|
10514
|
+
if (lst.isDirectory()) {
|
|
10515
|
+
const entries = walkDir(absPath);
|
|
10516
|
+
const contentHash = import_crypto9.default.createHash("sha256").update(entries.join("\n")).digest("hex");
|
|
10517
|
+
return { exists: true, contentHash, fileCount: entries.length };
|
|
10518
|
+
}
|
|
10519
|
+
return { exists: false, contentHash: "", fileCount: 0 };
|
|
10520
|
+
}
|
|
10521
|
+
function getRootKey(absPath) {
|
|
10522
|
+
return import_crypto9.default.createHash("sha256").update(absPath).digest("hex").slice(0, 16);
|
|
10523
|
+
}
|
|
10524
|
+
function readSkillPinsSafe() {
|
|
10525
|
+
const filePath = getPinsFilePath();
|
|
10526
|
+
try {
|
|
10527
|
+
const raw = import_fs23.default.readFileSync(filePath, "utf-8");
|
|
10528
|
+
if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10529
|
+
const parsed = JSON.parse(raw);
|
|
10530
|
+
if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
|
|
10531
|
+
return { ok: false, reason: "corrupt", detail: "invalid structure: missing roots object" };
|
|
10532
|
+
}
|
|
10533
|
+
return { ok: true, pins: { roots: parsed.roots } };
|
|
10534
|
+
} catch (err2) {
|
|
10535
|
+
if (err2.code === "ENOENT") return { ok: false, reason: "missing" };
|
|
10536
|
+
return { ok: false, reason: "corrupt", detail: String(err2) };
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
function readSkillPins() {
|
|
10540
|
+
const result = readSkillPinsSafe();
|
|
10541
|
+
if (result.ok) return result.pins;
|
|
10542
|
+
if (result.reason === "missing") return { roots: {} };
|
|
10543
|
+
throw new Error(`[node9] skill pin file is corrupt: ${result.detail}`);
|
|
10544
|
+
}
|
|
10545
|
+
function writeSkillPins(data) {
|
|
10546
|
+
const filePath = getPinsFilePath();
|
|
10547
|
+
import_fs23.default.mkdirSync(import_path25.default.dirname(filePath), { recursive: true });
|
|
10548
|
+
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
10549
|
+
import_fs23.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10550
|
+
import_fs23.default.renameSync(tmp, filePath);
|
|
10551
|
+
}
|
|
10552
|
+
function removePin(rootKey) {
|
|
10553
|
+
const pins = readSkillPins();
|
|
10554
|
+
delete pins.roots[rootKey];
|
|
10555
|
+
writeSkillPins(pins);
|
|
10556
|
+
}
|
|
10557
|
+
function clearAllPins() {
|
|
10558
|
+
writeSkillPins({ roots: {} });
|
|
10559
|
+
}
|
|
10560
|
+
function verifyAndPinRoots(roots) {
|
|
10561
|
+
const pinsRead = readSkillPinsSafe();
|
|
10562
|
+
if (!pinsRead.ok && pinsRead.reason === "corrupt") {
|
|
10563
|
+
return { kind: "corrupt", detail: pinsRead.detail };
|
|
10564
|
+
}
|
|
10565
|
+
const pins = pinsRead.ok ? pinsRead.pins : { roots: {} };
|
|
10566
|
+
let mutated = false;
|
|
10567
|
+
for (const rootPath of new Set(roots)) {
|
|
10568
|
+
const rootKey = getRootKey(rootPath);
|
|
10569
|
+
const current = hashSkillRoot(rootPath);
|
|
10570
|
+
const existing = pins.roots[rootKey];
|
|
10571
|
+
if (!existing) {
|
|
10572
|
+
pins.roots[rootKey] = {
|
|
10573
|
+
rootPath,
|
|
10574
|
+
exists: current.exists,
|
|
10575
|
+
contentHash: current.contentHash,
|
|
10576
|
+
fileCount: current.fileCount,
|
|
10577
|
+
pinnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10578
|
+
};
|
|
10579
|
+
mutated = true;
|
|
10580
|
+
continue;
|
|
10581
|
+
}
|
|
10582
|
+
if (existing.exists !== current.exists || existing.contentHash !== current.contentHash) {
|
|
10583
|
+
let summary;
|
|
10584
|
+
if (existing.exists && !current.exists) summary = `vanished: ${rootPath}`;
|
|
10585
|
+
else if (!existing.exists && current.exists) summary = `appeared: ${rootPath}`;
|
|
10586
|
+
else summary = `changed: ${rootPath}`;
|
|
10587
|
+
return { kind: "drift", changedRootKey: rootKey, changedRootPath: rootPath, summary };
|
|
10588
|
+
}
|
|
10589
|
+
}
|
|
10590
|
+
if (mutated) writeSkillPins(pins);
|
|
10591
|
+
return { kind: "verified" };
|
|
10592
|
+
}
|
|
10593
|
+
function defaultSkillRoots(_cwd) {
|
|
10594
|
+
const marketplaces = import_path25.default.join(import_os19.default.homedir(), ".claude", "plugins", "marketplaces");
|
|
10595
|
+
const roots = [];
|
|
10596
|
+
let registries;
|
|
10597
|
+
try {
|
|
10598
|
+
registries = import_fs23.default.readdirSync(marketplaces, { withFileTypes: true });
|
|
10599
|
+
} catch {
|
|
10600
|
+
return [];
|
|
10601
|
+
}
|
|
10602
|
+
for (const registry of registries) {
|
|
10603
|
+
if (!registry.isDirectory()) continue;
|
|
10604
|
+
const pluginsDir = import_path25.default.join(marketplaces, registry.name, "plugins");
|
|
10605
|
+
let plugins;
|
|
10606
|
+
try {
|
|
10607
|
+
plugins = import_fs23.default.readdirSync(pluginsDir, { withFileTypes: true });
|
|
10608
|
+
} catch {
|
|
10609
|
+
continue;
|
|
10610
|
+
}
|
|
10611
|
+
for (const plugin of plugins) {
|
|
10612
|
+
if (!plugin.isDirectory()) continue;
|
|
10613
|
+
roots.push(import_path25.default.join(pluginsDir, plugin.name));
|
|
10614
|
+
}
|
|
10615
|
+
}
|
|
10616
|
+
return roots;
|
|
10617
|
+
}
|
|
10618
|
+
function resolveUserSkillRoot(entry, cwd) {
|
|
10619
|
+
if (!entry) return null;
|
|
10620
|
+
if (entry.startsWith("~/") || entry === "~") return import_path25.default.join(import_os19.default.homedir(), entry.slice(1));
|
|
10621
|
+
if (import_path25.default.isAbsolute(entry)) return entry;
|
|
10622
|
+
if (!cwd || !import_path25.default.isAbsolute(cwd)) return null;
|
|
10623
|
+
return import_path25.default.join(cwd, entry);
|
|
10624
|
+
}
|
|
10625
|
+
|
|
10090
10626
|
// src/cli/commands/check.ts
|
|
10091
10627
|
function sanitize2(value) {
|
|
10092
10628
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
@@ -10102,9 +10638,9 @@ function registerCheckCommand(program2) {
|
|
|
10102
10638
|
} catch (err2) {
|
|
10103
10639
|
const tempConfig = getConfig();
|
|
10104
10640
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
10105
|
-
const logPath =
|
|
10641
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10106
10642
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10107
|
-
|
|
10643
|
+
import_fs24.default.appendFileSync(
|
|
10108
10644
|
logPath,
|
|
10109
10645
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
10110
10646
|
RAW: ${raw}
|
|
@@ -10117,11 +10653,11 @@ RAW: ${raw}
|
|
|
10117
10653
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
10118
10654
|
try {
|
|
10119
10655
|
const scriptPath = process.argv[1];
|
|
10120
|
-
if (typeof scriptPath !== "string" || !
|
|
10656
|
+
if (typeof scriptPath !== "string" || !import_path26.default.isAbsolute(scriptPath))
|
|
10121
10657
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
10122
|
-
const resolvedScript =
|
|
10123
|
-
const packageDist =
|
|
10124
|
-
if (!resolvedScript.startsWith(packageDist +
|
|
10658
|
+
const resolvedScript = import_fs24.default.realpathSync(scriptPath);
|
|
10659
|
+
const packageDist = import_fs24.default.realpathSync(import_path26.default.resolve(__dirname, "../.."));
|
|
10660
|
+
if (!resolvedScript.startsWith(packageDist + import_path26.default.sep) && resolvedScript !== packageDist)
|
|
10125
10661
|
throw new Error(
|
|
10126
10662
|
`node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
|
|
10127
10663
|
);
|
|
@@ -10143,10 +10679,10 @@ RAW: ${raw}
|
|
|
10143
10679
|
});
|
|
10144
10680
|
d.unref();
|
|
10145
10681
|
} catch (spawnErr) {
|
|
10146
|
-
const logPath =
|
|
10682
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10147
10683
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
10148
10684
|
try {
|
|
10149
|
-
|
|
10685
|
+
import_fs24.default.appendFileSync(
|
|
10150
10686
|
logPath,
|
|
10151
10687
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
|
|
10152
10688
|
`
|
|
@@ -10156,10 +10692,10 @@ RAW: ${raw}
|
|
|
10156
10692
|
}
|
|
10157
10693
|
}
|
|
10158
10694
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
10159
|
-
const logPath =
|
|
10160
|
-
if (!
|
|
10161
|
-
|
|
10162
|
-
|
|
10695
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10696
|
+
if (!import_fs24.default.existsSync(import_path26.default.dirname(logPath)))
|
|
10697
|
+
import_fs24.default.mkdirSync(import_path26.default.dirname(logPath), { recursive: true });
|
|
10698
|
+
import_fs24.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
10163
10699
|
`);
|
|
10164
10700
|
}
|
|
10165
10701
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -10172,8 +10708,8 @@ RAW: ${raw}
|
|
|
10172
10708
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
10173
10709
|
let ttyFd = null;
|
|
10174
10710
|
try {
|
|
10175
|
-
ttyFd =
|
|
10176
|
-
const writeTty = (line) =>
|
|
10711
|
+
ttyFd = import_fs24.default.openSync("/dev/tty", "w");
|
|
10712
|
+
const writeTty = (line) => import_fs24.default.writeSync(ttyFd, line + "\n");
|
|
10177
10713
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
10178
10714
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
10179
10715
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -10192,7 +10728,7 @@ RAW: ${raw}
|
|
|
10192
10728
|
} finally {
|
|
10193
10729
|
if (ttyFd !== null)
|
|
10194
10730
|
try {
|
|
10195
|
-
|
|
10731
|
+
import_fs24.default.closeSync(ttyFd);
|
|
10196
10732
|
} catch {
|
|
10197
10733
|
}
|
|
10198
10734
|
}
|
|
@@ -10221,10 +10757,131 @@ RAW: ${raw}
|
|
|
10221
10757
|
return;
|
|
10222
10758
|
}
|
|
10223
10759
|
const meta = { agent, mcpServer };
|
|
10760
|
+
const skillPinCfg = config.policy.skillPinning;
|
|
10761
|
+
const rawSessionId = typeof payload.session_id === "string" ? payload.session_id : "";
|
|
10762
|
+
const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
|
|
10763
|
+
if (skillPinCfg.enabled && safeSessionId) {
|
|
10764
|
+
try {
|
|
10765
|
+
const sessionsDir = import_path26.default.join(import_os20.default.homedir(), ".node9", "skill-sessions");
|
|
10766
|
+
const flagPath = import_path26.default.join(sessionsDir, `${safeSessionId}.json`);
|
|
10767
|
+
let flag = null;
|
|
10768
|
+
try {
|
|
10769
|
+
flag = JSON.parse(import_fs24.default.readFileSync(flagPath, "utf-8"));
|
|
10770
|
+
} catch {
|
|
10771
|
+
}
|
|
10772
|
+
const writeFlag = (data2) => {
|
|
10773
|
+
try {
|
|
10774
|
+
import_fs24.default.mkdirSync(sessionsDir, { recursive: true });
|
|
10775
|
+
import_fs24.default.writeFileSync(
|
|
10776
|
+
flagPath,
|
|
10777
|
+
JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
10778
|
+
{ mode: 384 }
|
|
10779
|
+
);
|
|
10780
|
+
} catch {
|
|
10781
|
+
}
|
|
10782
|
+
};
|
|
10783
|
+
const sendSkillWarn = (detail, recoveryCmd) => {
|
|
10784
|
+
let ttyFd = null;
|
|
10785
|
+
try {
|
|
10786
|
+
ttyFd = import_fs24.default.openSync("/dev/tty", "w");
|
|
10787
|
+
const w = (line) => import_fs24.default.writeSync(ttyFd, line + "\n");
|
|
10788
|
+
w(import_chalk5.default.yellow(`
|
|
10789
|
+
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
10790
|
+
w(import_chalk5.default.gray(` ${detail}`));
|
|
10791
|
+
w(
|
|
10792
|
+
import_chalk5.default.gray(
|
|
10793
|
+
` If you updated a plugin, acknowledge the change to clear this warning.`
|
|
10794
|
+
)
|
|
10795
|
+
);
|
|
10796
|
+
if (recoveryCmd) w(import_chalk5.default.green(` \u{1F4A1} Run: ${recoveryCmd}`));
|
|
10797
|
+
w("");
|
|
10798
|
+
} catch {
|
|
10799
|
+
} finally {
|
|
10800
|
+
if (ttyFd !== null)
|
|
10801
|
+
try {
|
|
10802
|
+
import_fs24.default.closeSync(ttyFd);
|
|
10803
|
+
} catch {
|
|
10804
|
+
}
|
|
10805
|
+
}
|
|
10806
|
+
};
|
|
10807
|
+
if (flag && flag.state === "quarantined" && skillPinCfg.mode === "block") {
|
|
10808
|
+
sendBlock(
|
|
10809
|
+
`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.`,
|
|
10810
|
+
{
|
|
10811
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10812
|
+
recoveryCommand: "node9 skill pin list"
|
|
10813
|
+
}
|
|
10814
|
+
);
|
|
10815
|
+
return;
|
|
10816
|
+
}
|
|
10817
|
+
if (!flag || flag.state !== "verified" && flag.state !== "warned") {
|
|
10818
|
+
const absoluteCwd = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10819
|
+
const extraRoots = skillPinCfg.roots;
|
|
10820
|
+
const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
|
|
10821
|
+
const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
|
|
10822
|
+
const result2 = verifyAndPinRoots(roots);
|
|
10823
|
+
if (result2.kind === "corrupt") {
|
|
10824
|
+
if (skillPinCfg.mode === "block") {
|
|
10825
|
+
writeFlag({
|
|
10826
|
+
state: "quarantined",
|
|
10827
|
+
detail: `pin file corrupt: ${result2.detail}`
|
|
10828
|
+
});
|
|
10829
|
+
sendBlock("Node9: skill pin file is corrupt \u2014 fail-closed.", {
|
|
10830
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10831
|
+
recoveryCommand: "node9 skill pin reset"
|
|
10832
|
+
});
|
|
10833
|
+
return;
|
|
10834
|
+
}
|
|
10835
|
+
writeFlag({ state: "warned", detail: `pin file corrupt: ${result2.detail}` });
|
|
10836
|
+
sendSkillWarn(
|
|
10837
|
+
`Skill pin file is corrupt: ${result2.detail}`,
|
|
10838
|
+
"node9 skill pin reset"
|
|
10839
|
+
);
|
|
10840
|
+
} else if (result2.kind === "drift") {
|
|
10841
|
+
if (skillPinCfg.mode === "block") {
|
|
10842
|
+
writeFlag({ state: "quarantined", detail: result2.summary });
|
|
10843
|
+
sendBlock(
|
|
10844
|
+
`Node9: installed skill changed \u2014 ${result2.summary}. If you updated a plugin, open a separate terminal and run: node9 skill pin update ${result2.changedRootKey}`,
|
|
10845
|
+
{
|
|
10846
|
+
blockedByLabel: "Skill Pin Quarantine",
|
|
10847
|
+
recoveryCommand: `node9 skill pin update ${result2.changedRootKey}`
|
|
10848
|
+
}
|
|
10849
|
+
);
|
|
10850
|
+
return;
|
|
10851
|
+
}
|
|
10852
|
+
writeFlag({ state: "warned", detail: result2.summary });
|
|
10853
|
+
sendSkillWarn(result2.summary, `node9 skill pin update ${result2.changedRootKey}`);
|
|
10854
|
+
} else {
|
|
10855
|
+
writeFlag({ state: "verified" });
|
|
10856
|
+
}
|
|
10857
|
+
try {
|
|
10858
|
+
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
10859
|
+
for (const name of import_fs24.default.readdirSync(sessionsDir)) {
|
|
10860
|
+
const p = import_path26.default.join(sessionsDir, name);
|
|
10861
|
+
try {
|
|
10862
|
+
if (import_fs24.default.statSync(p).mtimeMs < cutoff) import_fs24.default.unlinkSync(p);
|
|
10863
|
+
} catch {
|
|
10864
|
+
}
|
|
10865
|
+
}
|
|
10866
|
+
} catch {
|
|
10867
|
+
}
|
|
10868
|
+
}
|
|
10869
|
+
} catch (err2) {
|
|
10870
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
10871
|
+
try {
|
|
10872
|
+
const dbg = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10873
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10874
|
+
import_fs24.default.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
|
|
10875
|
+
`);
|
|
10876
|
+
} catch {
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
}
|
|
10224
10881
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
10225
10882
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
10226
10883
|
}
|
|
10227
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
10884
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path26.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10228
10885
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
10229
10886
|
cwd: safeCwdForAuth
|
|
10230
10887
|
});
|
|
@@ -10236,12 +10893,12 @@ RAW: ${raw}
|
|
|
10236
10893
|
}
|
|
10237
10894
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
10238
10895
|
try {
|
|
10239
|
-
const tty =
|
|
10240
|
-
|
|
10896
|
+
const tty = import_fs24.default.openSync("/dev/tty", "w");
|
|
10897
|
+
import_fs24.default.writeSync(
|
|
10241
10898
|
tty,
|
|
10242
10899
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
10243
10900
|
);
|
|
10244
|
-
|
|
10901
|
+
import_fs24.default.closeSync(tty);
|
|
10245
10902
|
} catch {
|
|
10246
10903
|
}
|
|
10247
10904
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -10268,9 +10925,9 @@ RAW: ${raw}
|
|
|
10268
10925
|
});
|
|
10269
10926
|
} catch (err2) {
|
|
10270
10927
|
if (process.env.NODE9_DEBUG === "1") {
|
|
10271
|
-
const logPath =
|
|
10928
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log");
|
|
10272
10929
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
10273
|
-
|
|
10930
|
+
import_fs24.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
10274
10931
|
`);
|
|
10275
10932
|
}
|
|
10276
10933
|
process.exit(0);
|
|
@@ -10304,9 +10961,9 @@ RAW: ${raw}
|
|
|
10304
10961
|
}
|
|
10305
10962
|
|
|
10306
10963
|
// src/cli/commands/log.ts
|
|
10307
|
-
var
|
|
10308
|
-
var
|
|
10309
|
-
var
|
|
10964
|
+
var import_fs25 = __toESM(require("fs"));
|
|
10965
|
+
var import_path27 = __toESM(require("path"));
|
|
10966
|
+
var import_os21 = __toESM(require("os"));
|
|
10310
10967
|
init_audit();
|
|
10311
10968
|
init_config();
|
|
10312
10969
|
init_policy();
|
|
@@ -10382,10 +11039,10 @@ function registerLogCommand(program2) {
|
|
|
10382
11039
|
decision: "allowed",
|
|
10383
11040
|
source: "post-hook"
|
|
10384
11041
|
};
|
|
10385
|
-
const logPath =
|
|
10386
|
-
if (!
|
|
10387
|
-
|
|
10388
|
-
|
|
11042
|
+
const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
|
|
11043
|
+
if (!import_fs25.default.existsSync(import_path27.default.dirname(logPath)))
|
|
11044
|
+
import_fs25.default.mkdirSync(import_path27.default.dirname(logPath), { recursive: true });
|
|
11045
|
+
import_fs25.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
10389
11046
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
10390
11047
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
10391
11048
|
if (command) {
|
|
@@ -10418,7 +11075,7 @@ function registerLogCommand(program2) {
|
|
|
10418
11075
|
}
|
|
10419
11076
|
}
|
|
10420
11077
|
}
|
|
10421
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
11078
|
+
const safeCwd = typeof payload.cwd === "string" && import_path27.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
10422
11079
|
const config = getConfig(safeCwd);
|
|
10423
11080
|
if (shouldSnapshot(tool, {}, config)) {
|
|
10424
11081
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -10427,9 +11084,9 @@ function registerLogCommand(program2) {
|
|
|
10427
11084
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
10428
11085
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
10429
11086
|
`);
|
|
10430
|
-
const debugPath =
|
|
11087
|
+
const debugPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "hook-debug.log");
|
|
10431
11088
|
try {
|
|
10432
|
-
|
|
11089
|
+
import_fs25.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
10433
11090
|
`);
|
|
10434
11091
|
} catch {
|
|
10435
11092
|
}
|
|
@@ -10829,14 +11486,14 @@ function registerConfigShowCommand(program2) {
|
|
|
10829
11486
|
|
|
10830
11487
|
// src/cli/commands/doctor.ts
|
|
10831
11488
|
var import_chalk7 = __toESM(require("chalk"));
|
|
10832
|
-
var
|
|
10833
|
-
var
|
|
10834
|
-
var
|
|
11489
|
+
var import_fs26 = __toESM(require("fs"));
|
|
11490
|
+
var import_path28 = __toESM(require("path"));
|
|
11491
|
+
var import_os22 = __toESM(require("os"));
|
|
10835
11492
|
var import_child_process11 = require("child_process");
|
|
10836
11493
|
init_daemon();
|
|
10837
11494
|
function registerDoctorCommand(program2, version2) {
|
|
10838
11495
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
10839
|
-
const homeDir2 =
|
|
11496
|
+
const homeDir2 = import_os22.default.homedir();
|
|
10840
11497
|
let failures = 0;
|
|
10841
11498
|
function pass(msg) {
|
|
10842
11499
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -10885,10 +11542,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10885
11542
|
);
|
|
10886
11543
|
}
|
|
10887
11544
|
section("Configuration");
|
|
10888
|
-
const globalConfigPath =
|
|
10889
|
-
if (
|
|
11545
|
+
const globalConfigPath = import_path28.default.join(homeDir2, ".node9", "config.json");
|
|
11546
|
+
if (import_fs26.default.existsSync(globalConfigPath)) {
|
|
10890
11547
|
try {
|
|
10891
|
-
JSON.parse(
|
|
11548
|
+
JSON.parse(import_fs26.default.readFileSync(globalConfigPath, "utf-8"));
|
|
10892
11549
|
pass("~/.node9/config.json found and valid");
|
|
10893
11550
|
} catch {
|
|
10894
11551
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -10896,10 +11553,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10896
11553
|
} else {
|
|
10897
11554
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
10898
11555
|
}
|
|
10899
|
-
const projectConfigPath =
|
|
10900
|
-
if (
|
|
11556
|
+
const projectConfigPath = import_path28.default.join(process.cwd(), "node9.config.json");
|
|
11557
|
+
if (import_fs26.default.existsSync(projectConfigPath)) {
|
|
10901
11558
|
try {
|
|
10902
|
-
JSON.parse(
|
|
11559
|
+
JSON.parse(import_fs26.default.readFileSync(projectConfigPath, "utf-8"));
|
|
10903
11560
|
pass("node9.config.json found and valid (project)");
|
|
10904
11561
|
} catch {
|
|
10905
11562
|
fail(
|
|
@@ -10908,8 +11565,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10908
11565
|
);
|
|
10909
11566
|
}
|
|
10910
11567
|
}
|
|
10911
|
-
const credsPath =
|
|
10912
|
-
if (
|
|
11568
|
+
const credsPath = import_path28.default.join(homeDir2, ".node9", "credentials.json");
|
|
11569
|
+
if (import_fs26.default.existsSync(credsPath)) {
|
|
10913
11570
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
10914
11571
|
} else {
|
|
10915
11572
|
warn(
|
|
@@ -10918,10 +11575,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10918
11575
|
);
|
|
10919
11576
|
}
|
|
10920
11577
|
section("Agent Hooks");
|
|
10921
|
-
const claudeSettingsPath =
|
|
10922
|
-
if (
|
|
11578
|
+
const claudeSettingsPath = import_path28.default.join(homeDir2, ".claude", "settings.json");
|
|
11579
|
+
if (import_fs26.default.existsSync(claudeSettingsPath)) {
|
|
10923
11580
|
try {
|
|
10924
|
-
const cs = JSON.parse(
|
|
11581
|
+
const cs = JSON.parse(import_fs26.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
10925
11582
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
10926
11583
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10927
11584
|
);
|
|
@@ -10937,10 +11594,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10937
11594
|
} else {
|
|
10938
11595
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
10939
11596
|
}
|
|
10940
|
-
const geminiSettingsPath =
|
|
10941
|
-
if (
|
|
11597
|
+
const geminiSettingsPath = import_path28.default.join(homeDir2, ".gemini", "settings.json");
|
|
11598
|
+
if (import_fs26.default.existsSync(geminiSettingsPath)) {
|
|
10942
11599
|
try {
|
|
10943
|
-
const gs = JSON.parse(
|
|
11600
|
+
const gs = JSON.parse(import_fs26.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
10944
11601
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
10945
11602
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
10946
11603
|
);
|
|
@@ -10956,10 +11613,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10956
11613
|
} else {
|
|
10957
11614
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
10958
11615
|
}
|
|
10959
|
-
const cursorHooksPath =
|
|
10960
|
-
if (
|
|
11616
|
+
const cursorHooksPath = import_path28.default.join(homeDir2, ".cursor", "hooks.json");
|
|
11617
|
+
if (import_fs26.default.existsSync(cursorHooksPath)) {
|
|
10961
11618
|
try {
|
|
10962
|
-
const cur = JSON.parse(
|
|
11619
|
+
const cur = JSON.parse(import_fs26.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
10963
11620
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
10964
11621
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
10965
11622
|
);
|
|
@@ -10997,9 +11654,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
10997
11654
|
|
|
10998
11655
|
// src/cli/commands/audit.ts
|
|
10999
11656
|
var import_chalk8 = __toESM(require("chalk"));
|
|
11000
|
-
var
|
|
11001
|
-
var
|
|
11002
|
-
var
|
|
11657
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11658
|
+
var import_path29 = __toESM(require("path"));
|
|
11659
|
+
var import_os23 = __toESM(require("os"));
|
|
11003
11660
|
function formatRelativeTime(timestamp) {
|
|
11004
11661
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
11005
11662
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -11012,14 +11669,14 @@ function formatRelativeTime(timestamp) {
|
|
|
11012
11669
|
}
|
|
11013
11670
|
function registerAuditCommand(program2) {
|
|
11014
11671
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
11015
|
-
const logPath =
|
|
11016
|
-
if (!
|
|
11672
|
+
const logPath = import_path29.default.join(import_os23.default.homedir(), ".node9", "audit.log");
|
|
11673
|
+
if (!import_fs27.default.existsSync(logPath)) {
|
|
11017
11674
|
console.log(
|
|
11018
11675
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
11019
11676
|
);
|
|
11020
11677
|
return;
|
|
11021
11678
|
}
|
|
11022
|
-
const raw =
|
|
11679
|
+
const raw = import_fs27.default.readFileSync(logPath, "utf-8");
|
|
11023
11680
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
11024
11681
|
let entries = lines.flatMap((line) => {
|
|
11025
11682
|
try {
|
|
@@ -11073,9 +11730,9 @@ function registerAuditCommand(program2) {
|
|
|
11073
11730
|
|
|
11074
11731
|
// src/cli/commands/report.ts
|
|
11075
11732
|
var import_chalk9 = __toESM(require("chalk"));
|
|
11076
|
-
var
|
|
11077
|
-
var
|
|
11078
|
-
var
|
|
11733
|
+
var import_fs28 = __toESM(require("fs"));
|
|
11734
|
+
var import_path30 = __toESM(require("path"));
|
|
11735
|
+
var import_os24 = __toESM(require("os"));
|
|
11079
11736
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
11080
11737
|
function buildTestTimestamps(allEntries) {
|
|
11081
11738
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -11122,8 +11779,8 @@ function getDateRange(period) {
|
|
|
11122
11779
|
}
|
|
11123
11780
|
}
|
|
11124
11781
|
function parseAuditLog(logPath) {
|
|
11125
|
-
if (!
|
|
11126
|
-
const raw =
|
|
11782
|
+
if (!import_fs28.default.existsSync(logPath)) return [];
|
|
11783
|
+
const raw = import_fs28.default.readFileSync(logPath, "utf-8");
|
|
11127
11784
|
return raw.split("\n").flatMap((line) => {
|
|
11128
11785
|
if (!line.trim()) return [];
|
|
11129
11786
|
try {
|
|
@@ -11190,34 +11847,38 @@ function loadClaudeCost(start, end) {
|
|
|
11190
11847
|
byDay: /* @__PURE__ */ new Map(),
|
|
11191
11848
|
byModel: /* @__PURE__ */ new Map(),
|
|
11192
11849
|
inputTokens: 0,
|
|
11850
|
+
outputTokens: 0,
|
|
11851
|
+
cacheWriteTokens: 0,
|
|
11193
11852
|
cacheReadTokens: 0
|
|
11194
11853
|
};
|
|
11195
|
-
const projectsDir =
|
|
11196
|
-
if (!
|
|
11854
|
+
const projectsDir = import_path30.default.join(import_os24.default.homedir(), ".claude", "projects");
|
|
11855
|
+
if (!import_fs28.default.existsSync(projectsDir)) return empty;
|
|
11197
11856
|
let dirs;
|
|
11198
11857
|
try {
|
|
11199
|
-
dirs =
|
|
11858
|
+
dirs = import_fs28.default.readdirSync(projectsDir);
|
|
11200
11859
|
} catch {
|
|
11201
11860
|
return empty;
|
|
11202
11861
|
}
|
|
11203
11862
|
let total = 0;
|
|
11204
11863
|
let inputTokens = 0;
|
|
11864
|
+
let outputTokens = 0;
|
|
11865
|
+
let cacheWriteTokens = 0;
|
|
11205
11866
|
let cacheReadTokens = 0;
|
|
11206
11867
|
const byDay = /* @__PURE__ */ new Map();
|
|
11207
11868
|
const byModel = /* @__PURE__ */ new Map();
|
|
11208
11869
|
for (const proj of dirs) {
|
|
11209
|
-
const projPath =
|
|
11870
|
+
const projPath = import_path30.default.join(projectsDir, proj);
|
|
11210
11871
|
let files;
|
|
11211
11872
|
try {
|
|
11212
|
-
const stat =
|
|
11873
|
+
const stat = import_fs28.default.statSync(projPath);
|
|
11213
11874
|
if (!stat.isDirectory()) continue;
|
|
11214
|
-
files =
|
|
11875
|
+
files = import_fs28.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
11215
11876
|
} catch {
|
|
11216
11877
|
continue;
|
|
11217
11878
|
}
|
|
11218
11879
|
for (const file of files) {
|
|
11219
11880
|
try {
|
|
11220
|
-
const raw =
|
|
11881
|
+
const raw = import_fs28.default.readFileSync(import_path30.default.join(projPath, file), "utf-8");
|
|
11221
11882
|
for (const line of raw.split("\n")) {
|
|
11222
11883
|
if (!line.trim()) continue;
|
|
11223
11884
|
let entry;
|
|
@@ -11242,6 +11903,8 @@ function loadClaudeCost(start, end) {
|
|
|
11242
11903
|
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
11243
11904
|
total += cost;
|
|
11244
11905
|
inputTokens += inp;
|
|
11906
|
+
outputTokens += out;
|
|
11907
|
+
cacheWriteTokens += cw;
|
|
11245
11908
|
cacheReadTokens += cr;
|
|
11246
11909
|
const dateKey = entry.timestamp.slice(0, 10);
|
|
11247
11910
|
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
@@ -11253,15 +11916,24 @@ function loadClaudeCost(start, end) {
|
|
|
11253
11916
|
}
|
|
11254
11917
|
}
|
|
11255
11918
|
}
|
|
11256
|
-
return { total, byDay, byModel, inputTokens, cacheReadTokens };
|
|
11919
|
+
return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
|
|
11257
11920
|
}
|
|
11258
11921
|
function registerReportCommand(program2) {
|
|
11259
11922
|
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").action((options) => {
|
|
11260
11923
|
const period = ["today", "7d", "30d", "month"].includes(
|
|
11261
11924
|
options.period
|
|
11262
11925
|
) ? options.period : "7d";
|
|
11263
|
-
const logPath =
|
|
11926
|
+
const logPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "audit.log");
|
|
11264
11927
|
const allEntries = parseAuditLog(logPath);
|
|
11928
|
+
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
11929
|
+
if (unackedDlp.length > 0) {
|
|
11930
|
+
console.log("");
|
|
11931
|
+
console.log(
|
|
11932
|
+
import_chalk9.default.bgRed.white.bold(
|
|
11933
|
+
` \u26A0\uFE0F DLP ALERT: ${unackedDlp.length} secret${unackedDlp.length !== 1 ? "s" : ""} found in Claude response text `
|
|
11934
|
+
) + " " + import_chalk9.default.yellow("\u2192 run: node9 dlp")
|
|
11935
|
+
);
|
|
11936
|
+
}
|
|
11265
11937
|
if (allEntries.length === 0) {
|
|
11266
11938
|
console.log(
|
|
11267
11939
|
import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
@@ -11274,6 +11946,8 @@ function registerReportCommand(program2) {
|
|
|
11274
11946
|
byDay: costByDay,
|
|
11275
11947
|
byModel: costByModel,
|
|
11276
11948
|
inputTokens: costInputTokens,
|
|
11949
|
+
outputTokens: costOutputTokens,
|
|
11950
|
+
cacheWriteTokens: costCacheWrite,
|
|
11277
11951
|
cacheReadTokens: costCacheRead
|
|
11278
11952
|
} = loadClaudeCost(start, end);
|
|
11279
11953
|
const periodMs = end.getTime() - start.getTime();
|
|
@@ -11291,6 +11965,7 @@ function registerReportCommand(program2) {
|
|
|
11291
11965
|
let filteredTestCount = 0;
|
|
11292
11966
|
const entries = allEntries.filter((e) => {
|
|
11293
11967
|
if (e.source === "post-hook") return false;
|
|
11968
|
+
if (e.source === "response-dlp") return false;
|
|
11294
11969
|
const ts = new Date(e.ts);
|
|
11295
11970
|
if (ts < start || ts > end) return false;
|
|
11296
11971
|
if (excludeTests && isTestEntry(e, testTs)) {
|
|
@@ -11424,7 +12099,7 @@ function registerReportCommand(program2) {
|
|
|
11424
12099
|
if (topBlocks.length === 0) {
|
|
11425
12100
|
console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
|
|
11426
12101
|
}
|
|
11427
|
-
if (agentMap.size
|
|
12102
|
+
if (agentMap.size >= 1) {
|
|
11428
12103
|
console.log("");
|
|
11429
12104
|
console.log(" " + import_chalk9.default.bold("Agents"));
|
|
11430
12105
|
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
@@ -11474,6 +12149,40 @@ function registerReportCommand(program2) {
|
|
|
11474
12149
|
);
|
|
11475
12150
|
}
|
|
11476
12151
|
}
|
|
12152
|
+
const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
|
|
12153
|
+
if (totalTokens > 0) {
|
|
12154
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
12155
|
+
console.log("");
|
|
12156
|
+
console.log(" " + import_chalk9.default.bold("Tokens") + " " + import_chalk9.default.dim(`${num(totalTokens)} total`));
|
|
12157
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
12158
|
+
const tokenRows = [
|
|
12159
|
+
["Input", costInputTokens, import_chalk9.default.cyan(num(costInputTokens))],
|
|
12160
|
+
["Output", costOutputTokens, import_chalk9.default.white(num(costOutputTokens))],
|
|
12161
|
+
["Cache write", costCacheWrite, import_chalk9.default.yellow(num(costCacheWrite))],
|
|
12162
|
+
["Cache read", costCacheRead, import_chalk9.default.green(num(costCacheRead))]
|
|
12163
|
+
];
|
|
12164
|
+
const maxTok = Math.max(
|
|
12165
|
+
costInputTokens,
|
|
12166
|
+
costOutputTokens,
|
|
12167
|
+
costCacheWrite,
|
|
12168
|
+
costCacheRead,
|
|
12169
|
+
1
|
|
12170
|
+
);
|
|
12171
|
+
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
12172
|
+
const TOK_LABEL = 14;
|
|
12173
|
+
for (const [label, count, colored] of tokenRows) {
|
|
12174
|
+
if (count === 0) continue;
|
|
12175
|
+
const b = colorBar(count, maxTok, TOK_BAR);
|
|
12176
|
+
console.log(" " + import_chalk9.default.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
12177
|
+
}
|
|
12178
|
+
if (cacheHitPct > 0) {
|
|
12179
|
+
console.log(
|
|
12180
|
+
" " + import_chalk9.default.dim(
|
|
12181
|
+
`Cache hit rate: ${cacheHitPct}% (saves ~${fmtCost(costCacheRead * 27e-7)} vs fresh input)`
|
|
12182
|
+
)
|
|
12183
|
+
);
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
11477
12186
|
if (costUSD > 0) {
|
|
11478
12187
|
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
11479
12188
|
const avgPerDay = costUSD / periodDays;
|
|
@@ -11498,6 +12207,33 @@ function registerReportCommand(program2) {
|
|
|
11498
12207
|
);
|
|
11499
12208
|
}
|
|
11500
12209
|
}
|
|
12210
|
+
const responseDlpEntries = allEntries.filter((e) => {
|
|
12211
|
+
if (e.source !== "response-dlp") return false;
|
|
12212
|
+
const ts = new Date(e.ts);
|
|
12213
|
+
return ts >= start && ts <= end;
|
|
12214
|
+
});
|
|
12215
|
+
if (responseDlpEntries.length > 0) {
|
|
12216
|
+
console.log("");
|
|
12217
|
+
console.log(
|
|
12218
|
+
" " + import_chalk9.default.red.bold("\u26A0\uFE0F Response DLP") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.red(
|
|
12219
|
+
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
12220
|
+
)
|
|
12221
|
+
);
|
|
12222
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
12223
|
+
console.log(
|
|
12224
|
+
" " + import_chalk9.default.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
12225
|
+
);
|
|
12226
|
+
console.log(" " + import_chalk9.default.yellow("Rotate affected keys immediately."));
|
|
12227
|
+
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
12228
|
+
const ts = import_chalk9.default.dim(fmtDate(e.ts) + " ");
|
|
12229
|
+
const pattern = import_chalk9.default.red(e.dlpPattern ?? "DLP");
|
|
12230
|
+
const sample = import_chalk9.default.gray(e.dlpSample ?? "");
|
|
12231
|
+
console.log(` ${ts}${pattern} ${sample}`);
|
|
12232
|
+
}
|
|
12233
|
+
if (responseDlpEntries.length > 5) {
|
|
12234
|
+
console.log(import_chalk9.default.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
12235
|
+
}
|
|
12236
|
+
}
|
|
11501
12237
|
console.log("");
|
|
11502
12238
|
console.log(
|
|
11503
12239
|
" " + import_chalk9.default.dim("node9 audit --deny") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
@@ -11613,14 +12349,14 @@ function registerDaemonCommand(program2) {
|
|
|
11613
12349
|
|
|
11614
12350
|
// src/cli/commands/status.ts
|
|
11615
12351
|
var import_chalk11 = __toESM(require("chalk"));
|
|
11616
|
-
var
|
|
11617
|
-
var
|
|
11618
|
-
var
|
|
12352
|
+
var import_fs29 = __toESM(require("fs"));
|
|
12353
|
+
var import_path31 = __toESM(require("path"));
|
|
12354
|
+
var import_os25 = __toESM(require("os"));
|
|
11619
12355
|
init_core();
|
|
11620
12356
|
init_daemon();
|
|
11621
12357
|
function readJson2(filePath) {
|
|
11622
12358
|
try {
|
|
11623
|
-
if (
|
|
12359
|
+
if (import_fs29.default.existsSync(filePath)) return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
|
|
11624
12360
|
} catch {
|
|
11625
12361
|
}
|
|
11626
12362
|
return null;
|
|
@@ -11685,28 +12421,28 @@ function registerStatusCommand(program2) {
|
|
|
11685
12421
|
console.log("");
|
|
11686
12422
|
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
11687
12423
|
console.log(` Mode: ${modeLabel}`);
|
|
11688
|
-
const projectConfig =
|
|
11689
|
-
const globalConfig =
|
|
12424
|
+
const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
|
|
12425
|
+
const globalConfig = import_path31.default.join(import_os25.default.homedir(), ".node9", "config.json");
|
|
11690
12426
|
console.log(
|
|
11691
|
-
` Local: ${
|
|
12427
|
+
` Local: ${import_fs29.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
11692
12428
|
);
|
|
11693
12429
|
console.log(
|
|
11694
|
-
` Global: ${
|
|
12430
|
+
` Global: ${import_fs29.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
11695
12431
|
);
|
|
11696
12432
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
11697
12433
|
console.log(
|
|
11698
12434
|
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
11699
12435
|
);
|
|
11700
12436
|
}
|
|
11701
|
-
const homeDir2 =
|
|
12437
|
+
const homeDir2 = import_os25.default.homedir();
|
|
11702
12438
|
const claudeSettings = readJson2(
|
|
11703
|
-
|
|
12439
|
+
import_path31.default.join(homeDir2, ".claude", "settings.json")
|
|
11704
12440
|
);
|
|
11705
|
-
const claudeConfig = readJson2(
|
|
12441
|
+
const claudeConfig = readJson2(import_path31.default.join(homeDir2, ".claude.json"));
|
|
11706
12442
|
const geminiSettings = readJson2(
|
|
11707
|
-
|
|
12443
|
+
import_path31.default.join(homeDir2, ".gemini", "settings.json")
|
|
11708
12444
|
);
|
|
11709
|
-
const cursorConfig = readJson2(
|
|
12445
|
+
const cursorConfig = readJson2(import_path31.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
11710
12446
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
11711
12447
|
if (agentFound) {
|
|
11712
12448
|
console.log("");
|
|
@@ -11765,9 +12501,9 @@ function registerStatusCommand(program2) {
|
|
|
11765
12501
|
|
|
11766
12502
|
// src/cli/commands/init.ts
|
|
11767
12503
|
var import_chalk12 = __toESM(require("chalk"));
|
|
11768
|
-
var
|
|
11769
|
-
var
|
|
11770
|
-
var
|
|
12504
|
+
var import_fs30 = __toESM(require("fs"));
|
|
12505
|
+
var import_path32 = __toESM(require("path"));
|
|
12506
|
+
var import_os26 = __toESM(require("os"));
|
|
11771
12507
|
var import_https3 = __toESM(require("https"));
|
|
11772
12508
|
init_core();
|
|
11773
12509
|
init_shields();
|
|
@@ -11828,15 +12564,15 @@ function registerInitCommand(program2) {
|
|
|
11828
12564
|
}
|
|
11829
12565
|
console.log("");
|
|
11830
12566
|
}
|
|
11831
|
-
const configPath =
|
|
11832
|
-
if (
|
|
12567
|
+
const configPath = import_path32.default.join(import_os26.default.homedir(), ".node9", "config.json");
|
|
12568
|
+
if (import_fs30.default.existsSync(configPath) && !options.force) {
|
|
11833
12569
|
try {
|
|
11834
|
-
const existing = JSON.parse(
|
|
12570
|
+
const existing = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
|
|
11835
12571
|
const settings = existing.settings ?? {};
|
|
11836
12572
|
if (settings.mode !== chosenMode) {
|
|
11837
12573
|
settings.mode = chosenMode;
|
|
11838
12574
|
existing.settings = settings;
|
|
11839
|
-
|
|
12575
|
+
import_fs30.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
11840
12576
|
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
11841
12577
|
} else {
|
|
11842
12578
|
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -11849,9 +12585,9 @@ function registerInitCommand(program2) {
|
|
|
11849
12585
|
...DEFAULT_CONFIG,
|
|
11850
12586
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
11851
12587
|
};
|
|
11852
|
-
const dir =
|
|
11853
|
-
if (!
|
|
11854
|
-
|
|
12588
|
+
const dir = import_path32.default.dirname(configPath);
|
|
12589
|
+
if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
|
|
12590
|
+
import_fs30.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
11855
12591
|
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
11856
12592
|
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
11857
12593
|
}
|
|
@@ -11935,7 +12671,7 @@ function registerInitCommand(program2) {
|
|
|
11935
12671
|
}
|
|
11936
12672
|
|
|
11937
12673
|
// src/cli/commands/undo.ts
|
|
11938
|
-
var
|
|
12674
|
+
var import_path33 = __toESM(require("path"));
|
|
11939
12675
|
var import_chalk14 = __toESM(require("chalk"));
|
|
11940
12676
|
|
|
11941
12677
|
// src/tui/undo-navigator.ts
|
|
@@ -12094,7 +12830,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
12094
12830
|
let dir = startDir;
|
|
12095
12831
|
while (true) {
|
|
12096
12832
|
if (cwds.has(dir)) return dir;
|
|
12097
|
-
const parent =
|
|
12833
|
+
const parent = import_path33.default.dirname(dir);
|
|
12098
12834
|
if (parent === dir) return null;
|
|
12099
12835
|
dir = parent;
|
|
12100
12836
|
}
|
|
@@ -12290,12 +13026,12 @@ init_orchestrator();
|
|
|
12290
13026
|
init_provenance();
|
|
12291
13027
|
|
|
12292
13028
|
// src/mcp-pin.ts
|
|
12293
|
-
var
|
|
12294
|
-
var
|
|
12295
|
-
var
|
|
12296
|
-
var
|
|
12297
|
-
function
|
|
12298
|
-
return
|
|
13029
|
+
var import_fs31 = __toESM(require("fs"));
|
|
13030
|
+
var import_path34 = __toESM(require("path"));
|
|
13031
|
+
var import_os27 = __toESM(require("os"));
|
|
13032
|
+
var import_crypto10 = __toESM(require("crypto"));
|
|
13033
|
+
function getPinsFilePath2() {
|
|
13034
|
+
return import_path34.default.join(import_os27.default.homedir(), ".node9", "mcp-pins.json");
|
|
12299
13035
|
}
|
|
12300
13036
|
function hashToolDefinitions(tools) {
|
|
12301
13037
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -12304,15 +13040,15 @@ function hashToolDefinitions(tools) {
|
|
|
12304
13040
|
return nameA.localeCompare(nameB);
|
|
12305
13041
|
});
|
|
12306
13042
|
const canonical = JSON.stringify(sorted);
|
|
12307
|
-
return
|
|
13043
|
+
return import_crypto10.default.createHash("sha256").update(canonical).digest("hex");
|
|
12308
13044
|
}
|
|
12309
13045
|
function getServerKey(upstreamCommand) {
|
|
12310
|
-
return
|
|
13046
|
+
return import_crypto10.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
12311
13047
|
}
|
|
12312
13048
|
function readMcpPinsSafe() {
|
|
12313
|
-
const filePath =
|
|
13049
|
+
const filePath = getPinsFilePath2();
|
|
12314
13050
|
try {
|
|
12315
|
-
const raw =
|
|
13051
|
+
const raw = import_fs31.default.readFileSync(filePath, "utf-8");
|
|
12316
13052
|
if (!raw.trim()) {
|
|
12317
13053
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
12318
13054
|
}
|
|
@@ -12335,11 +13071,11 @@ function readMcpPins() {
|
|
|
12335
13071
|
throw new Error(`[node9] MCP pin file is corrupt: ${result.detail}`);
|
|
12336
13072
|
}
|
|
12337
13073
|
function writeMcpPins(data) {
|
|
12338
|
-
const filePath =
|
|
12339
|
-
|
|
12340
|
-
const tmp = `${filePath}.${
|
|
12341
|
-
|
|
12342
|
-
|
|
13074
|
+
const filePath = getPinsFilePath2();
|
|
13075
|
+
import_fs31.default.mkdirSync(import_path34.default.dirname(filePath), { recursive: true });
|
|
13076
|
+
const tmp = `${filePath}.${import_crypto10.default.randomBytes(6).toString("hex")}.tmp`;
|
|
13077
|
+
import_fs31.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
13078
|
+
import_fs31.default.renameSync(tmp, filePath);
|
|
12343
13079
|
}
|
|
12344
13080
|
function checkPin(serverKey, currentHash) {
|
|
12345
13081
|
const result = readMcpPinsSafe();
|
|
@@ -12362,12 +13098,12 @@ function updatePin(serverKey, label, toolsHash, toolNames) {
|
|
|
12362
13098
|
};
|
|
12363
13099
|
writeMcpPins(pins);
|
|
12364
13100
|
}
|
|
12365
|
-
function
|
|
13101
|
+
function removePin2(serverKey) {
|
|
12366
13102
|
const pins = readMcpPins();
|
|
12367
13103
|
delete pins.servers[serverKey];
|
|
12368
13104
|
writeMcpPins(pins);
|
|
12369
13105
|
}
|
|
12370
|
-
function
|
|
13106
|
+
function clearAllPins2() {
|
|
12371
13107
|
writeMcpPins({ servers: {} });
|
|
12372
13108
|
}
|
|
12373
13109
|
|
|
@@ -12711,9 +13447,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
12711
13447
|
|
|
12712
13448
|
// src/mcp-server/index.ts
|
|
12713
13449
|
var import_readline4 = __toESM(require("readline"));
|
|
12714
|
-
var
|
|
12715
|
-
var
|
|
12716
|
-
var
|
|
13450
|
+
var import_fs32 = __toESM(require("fs"));
|
|
13451
|
+
var import_os28 = __toESM(require("os"));
|
|
13452
|
+
var import_path35 = __toESM(require("path"));
|
|
12717
13453
|
init_core();
|
|
12718
13454
|
init_daemon();
|
|
12719
13455
|
init_shields();
|
|
@@ -12888,13 +13624,13 @@ function handleStatus() {
|
|
|
12888
13624
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
12889
13625
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
12890
13626
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
12891
|
-
const projectConfig =
|
|
12892
|
-
const globalConfig =
|
|
13627
|
+
const projectConfig = import_path35.default.join(process.cwd(), "node9.config.json");
|
|
13628
|
+
const globalConfig = import_path35.default.join(import_os28.default.homedir(), ".node9", "config.json");
|
|
12893
13629
|
lines.push(
|
|
12894
|
-
`Project config (node9.config.json): ${
|
|
13630
|
+
`Project config (node9.config.json): ${import_fs32.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
12895
13631
|
);
|
|
12896
13632
|
lines.push(
|
|
12897
|
-
`Global config (~/.node9/config.json): ${
|
|
13633
|
+
`Global config (~/.node9/config.json): ${import_fs32.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
12898
13634
|
);
|
|
12899
13635
|
return lines.join("\n");
|
|
12900
13636
|
}
|
|
@@ -12968,21 +13704,21 @@ function handleShieldDisable(args) {
|
|
|
12968
13704
|
writeActiveShields(active.filter((s) => s !== name));
|
|
12969
13705
|
return `Shield "${name}" disabled.`;
|
|
12970
13706
|
}
|
|
12971
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
13707
|
+
var GLOBAL_CONFIG_PATH2 = import_path35.default.join(import_os28.default.homedir(), ".node9", "config.json");
|
|
12972
13708
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
12973
13709
|
function readGlobalConfigRaw() {
|
|
12974
13710
|
try {
|
|
12975
|
-
if (
|
|
12976
|
-
return JSON.parse(
|
|
13711
|
+
if (import_fs32.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
13712
|
+
return JSON.parse(import_fs32.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
12977
13713
|
}
|
|
12978
13714
|
} catch {
|
|
12979
13715
|
}
|
|
12980
13716
|
return {};
|
|
12981
13717
|
}
|
|
12982
13718
|
function writeGlobalConfigRaw(data) {
|
|
12983
|
-
const dir =
|
|
12984
|
-
if (!
|
|
12985
|
-
|
|
13719
|
+
const dir = import_path35.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
13720
|
+
if (!import_fs32.default.existsSync(dir)) import_fs32.default.mkdirSync(dir, { recursive: true });
|
|
13721
|
+
import_fs32.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
12986
13722
|
}
|
|
12987
13723
|
function handleApproverList() {
|
|
12988
13724
|
const config = getConfig();
|
|
@@ -13025,9 +13761,9 @@ function handleApproverSet(args) {
|
|
|
13025
13761
|
}
|
|
13026
13762
|
function handleAuditGet(args) {
|
|
13027
13763
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
13028
|
-
const auditPath =
|
|
13029
|
-
if (!
|
|
13030
|
-
const lines =
|
|
13764
|
+
const auditPath = import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
|
|
13765
|
+
if (!import_fs32.default.existsSync(auditPath)) return "No audit log found.";
|
|
13766
|
+
const lines = import_fs32.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
13031
13767
|
const recent = lines.slice(-limit);
|
|
13032
13768
|
const entries = recent.map((line) => {
|
|
13033
13769
|
try {
|
|
@@ -13325,7 +14061,7 @@ function registerMcpPinCommand(program2) {
|
|
|
13325
14061
|
process.exit(1);
|
|
13326
14062
|
}
|
|
13327
14063
|
const label = pins.servers[serverKey].label;
|
|
13328
|
-
|
|
14064
|
+
removePin2(serverKey);
|
|
13329
14065
|
console.log(import_chalk18.default.green(`
|
|
13330
14066
|
\u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
|
|
13331
14067
|
console.log(import_chalk18.default.gray(` Server: ${label}`));
|
|
@@ -13338,7 +14074,7 @@ function registerMcpPinCommand(program2) {
|
|
|
13338
14074
|
return;
|
|
13339
14075
|
}
|
|
13340
14076
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
13341
|
-
|
|
14077
|
+
clearAllPins2();
|
|
13342
14078
|
console.log(import_chalk18.default.green(`
|
|
13343
14079
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
13344
14080
|
console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
|
|
@@ -13485,9 +14221,9 @@ function registerAgentsCommand(program2) {
|
|
|
13485
14221
|
|
|
13486
14222
|
// src/cli/commands/scan.ts
|
|
13487
14223
|
var import_chalk21 = __toESM(require("chalk"));
|
|
13488
|
-
var
|
|
13489
|
-
var
|
|
13490
|
-
var
|
|
14224
|
+
var import_fs33 = __toESM(require("fs"));
|
|
14225
|
+
var import_path36 = __toESM(require("path"));
|
|
14226
|
+
var import_os29 = __toESM(require("os"));
|
|
13491
14227
|
init_shields();
|
|
13492
14228
|
init_config();
|
|
13493
14229
|
init_policy();
|
|
@@ -13559,7 +14295,7 @@ function buildRuleSources() {
|
|
|
13559
14295
|
return sources;
|
|
13560
14296
|
}
|
|
13561
14297
|
function scanClaudeHistory(startDate) {
|
|
13562
|
-
const projectsDir =
|
|
14298
|
+
const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
|
|
13563
14299
|
const result = {
|
|
13564
14300
|
filesScanned: 0,
|
|
13565
14301
|
sessions: 0,
|
|
@@ -13571,25 +14307,25 @@ function scanClaudeHistory(startDate) {
|
|
|
13571
14307
|
firstDate: null,
|
|
13572
14308
|
lastDate: null
|
|
13573
14309
|
};
|
|
13574
|
-
if (!
|
|
14310
|
+
if (!import_fs33.default.existsSync(projectsDir)) return result;
|
|
13575
14311
|
let projDirs;
|
|
13576
14312
|
try {
|
|
13577
|
-
projDirs =
|
|
14313
|
+
projDirs = import_fs33.default.readdirSync(projectsDir);
|
|
13578
14314
|
} catch {
|
|
13579
14315
|
return result;
|
|
13580
14316
|
}
|
|
13581
14317
|
const ruleSources = buildRuleSources();
|
|
13582
14318
|
for (const proj of projDirs) {
|
|
13583
|
-
const projPath =
|
|
14319
|
+
const projPath = import_path36.default.join(projectsDir, proj);
|
|
13584
14320
|
try {
|
|
13585
|
-
if (!
|
|
14321
|
+
if (!import_fs33.default.statSync(projPath).isDirectory()) continue;
|
|
13586
14322
|
} catch {
|
|
13587
14323
|
continue;
|
|
13588
14324
|
}
|
|
13589
|
-
const projLabel = decodeURIComponent(proj).replace(
|
|
14325
|
+
const projLabel = decodeURIComponent(proj).replace(import_os29.default.homedir(), "~").slice(0, 40);
|
|
13590
14326
|
let files;
|
|
13591
14327
|
try {
|
|
13592
|
-
files =
|
|
14328
|
+
files = import_fs33.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
13593
14329
|
} catch {
|
|
13594
14330
|
continue;
|
|
13595
14331
|
}
|
|
@@ -13598,7 +14334,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13598
14334
|
result.sessions++;
|
|
13599
14335
|
let raw;
|
|
13600
14336
|
try {
|
|
13601
|
-
raw =
|
|
14337
|
+
raw = import_fs33.default.readFileSync(import_path36.default.join(projPath, file), "utf-8");
|
|
13602
14338
|
} catch {
|
|
13603
14339
|
continue;
|
|
13604
14340
|
}
|
|
@@ -13639,6 +14375,9 @@ function scanClaudeHistory(startDate) {
|
|
|
13639
14375
|
if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
|
|
13640
14376
|
result.bashCalls++;
|
|
13641
14377
|
}
|
|
14378
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
14379
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
14380
|
+
continue;
|
|
13642
14381
|
const dlpMatch = scanArgs(input);
|
|
13643
14382
|
if (dlpMatch) {
|
|
13644
14383
|
const isDupe = result.dlpFindings.some(
|
|
@@ -13656,6 +14395,7 @@ function scanClaudeHistory(startDate) {
|
|
|
13656
14395
|
}
|
|
13657
14396
|
for (const source of ruleSources) {
|
|
13658
14397
|
const { rule } = source;
|
|
14398
|
+
if (rule.verdict === "allow") continue;
|
|
13659
14399
|
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
13660
14400
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
13661
14401
|
const inputPreview = preview(input, 120);
|
|
@@ -13691,8 +14431,8 @@ function registerScanCommand(program2) {
|
|
|
13691
14431
|
console.log("");
|
|
13692
14432
|
console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
|
|
13693
14433
|
console.log("");
|
|
13694
|
-
const projectsDir =
|
|
13695
|
-
if (!
|
|
14434
|
+
const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
|
|
14435
|
+
if (!import_fs33.default.existsSync(projectsDir)) {
|
|
13696
14436
|
console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
13697
14437
|
console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
13698
14438
|
return;
|
|
@@ -13804,8 +14544,8 @@ function registerScanCommand(program2) {
|
|
|
13804
14544
|
);
|
|
13805
14545
|
console.log("");
|
|
13806
14546
|
}
|
|
13807
|
-
const auditLog =
|
|
13808
|
-
if (
|
|
14547
|
+
const auditLog = import_path36.default.join(import_os29.default.homedir(), ".node9", "audit.log");
|
|
14548
|
+
if (import_fs33.default.existsSync(auditLog)) {
|
|
13809
14549
|
console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
|
|
13810
14550
|
console.log(
|
|
13811
14551
|
import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
|
|
@@ -13822,9 +14562,9 @@ function registerScanCommand(program2) {
|
|
|
13822
14562
|
|
|
13823
14563
|
// src/cli/commands/sessions.ts
|
|
13824
14564
|
var import_chalk22 = __toESM(require("chalk"));
|
|
13825
|
-
var
|
|
13826
|
-
var
|
|
13827
|
-
var
|
|
14565
|
+
var import_fs34 = __toESM(require("fs"));
|
|
14566
|
+
var import_path37 = __toESM(require("path"));
|
|
14567
|
+
var import_os30 = __toESM(require("os"));
|
|
13828
14568
|
var CLAUDE_PRICING3 = {
|
|
13829
14569
|
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13830
14570
|
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
@@ -13849,10 +14589,10 @@ function encodeProjectPath(projectPath) {
|
|
|
13849
14589
|
}
|
|
13850
14590
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
13851
14591
|
const encoded = encodeProjectPath(projectPath);
|
|
13852
|
-
return
|
|
14592
|
+
return import_path37.default.join(import_os30.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
13853
14593
|
}
|
|
13854
14594
|
function projectLabel(projectPath) {
|
|
13855
|
-
return projectPath.replace(
|
|
14595
|
+
return projectPath.replace(import_os30.default.homedir(), "~");
|
|
13856
14596
|
}
|
|
13857
14597
|
function parseHistoryLines(lines) {
|
|
13858
14598
|
const entries = [];
|
|
@@ -13921,10 +14661,10 @@ function parseSessionLines(lines) {
|
|
|
13921
14661
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
13922
14662
|
}
|
|
13923
14663
|
function loadAuditEntries(auditPath) {
|
|
13924
|
-
const aPath = auditPath ??
|
|
14664
|
+
const aPath = auditPath ?? import_path37.default.join(import_os30.default.homedir(), ".node9", "audit.log");
|
|
13925
14665
|
let raw;
|
|
13926
14666
|
try {
|
|
13927
|
-
raw =
|
|
14667
|
+
raw = import_fs34.default.readFileSync(aPath, "utf-8");
|
|
13928
14668
|
} catch {
|
|
13929
14669
|
return [];
|
|
13930
14670
|
}
|
|
@@ -13960,10 +14700,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
13960
14700
|
return result;
|
|
13961
14701
|
}
|
|
13962
14702
|
function buildSessions(days, historyPath) {
|
|
13963
|
-
const hPath = historyPath ??
|
|
14703
|
+
const hPath = historyPath ?? import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
|
|
13964
14704
|
let historyRaw;
|
|
13965
14705
|
try {
|
|
13966
|
-
historyRaw =
|
|
14706
|
+
historyRaw = import_fs34.default.readFileSync(hPath, "utf-8");
|
|
13967
14707
|
} catch {
|
|
13968
14708
|
return [];
|
|
13969
14709
|
}
|
|
@@ -13988,7 +14728,7 @@ function buildSessions(days, historyPath) {
|
|
|
13988
14728
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
13989
14729
|
let sessionLines = [];
|
|
13990
14730
|
try {
|
|
13991
|
-
sessionLines =
|
|
14731
|
+
sessionLines = import_fs34.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
13992
14732
|
} catch {
|
|
13993
14733
|
}
|
|
13994
14734
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -14239,8 +14979,8 @@ function registerSessionsCommand(program2) {
|
|
|
14239
14979
|
console.log("");
|
|
14240
14980
|
console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
|
|
14241
14981
|
console.log("");
|
|
14242
|
-
const historyPath =
|
|
14243
|
-
if (!
|
|
14982
|
+
const historyPath = import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
|
|
14983
|
+
if (!import_fs34.default.existsSync(historyPath)) {
|
|
14244
14984
|
console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
14245
14985
|
console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14246
14986
|
return;
|
|
@@ -14270,22 +15010,233 @@ function registerSessionsCommand(program2) {
|
|
|
14270
15010
|
});
|
|
14271
15011
|
}
|
|
14272
15012
|
|
|
15013
|
+
// src/cli/commands/skill-pin.ts
|
|
15014
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
15015
|
+
var import_fs35 = __toESM(require("fs"));
|
|
15016
|
+
var import_os31 = __toESM(require("os"));
|
|
15017
|
+
var import_path38 = __toESM(require("path"));
|
|
15018
|
+
function wipeSkillSessions() {
|
|
15019
|
+
try {
|
|
15020
|
+
import_fs35.default.rmSync(import_path38.default.join(import_os31.default.homedir(), ".node9", "skill-sessions"), {
|
|
15021
|
+
recursive: true,
|
|
15022
|
+
force: true
|
|
15023
|
+
});
|
|
15024
|
+
} catch {
|
|
15025
|
+
}
|
|
15026
|
+
}
|
|
15027
|
+
function registerSkillPinCommand(program2) {
|
|
15028
|
+
const skillCmd = program2.command("skill").description("Manage skill pinning (supply chain & update drift defense, AST 02 + AST 07)");
|
|
15029
|
+
const pinSubCmd = skillCmd.command("pin").description("Manage pinned skill roots");
|
|
15030
|
+
pinSubCmd.command("list").description("Show all pinned skill roots and their content hashes").action(() => {
|
|
15031
|
+
const result = readSkillPinsSafe();
|
|
15032
|
+
if (!result.ok) {
|
|
15033
|
+
if (result.reason === "missing") {
|
|
15034
|
+
console.log(import_chalk23.default.gray("\nNo skill roots are pinned yet."));
|
|
15035
|
+
console.log(
|
|
15036
|
+
import_chalk23.default.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
15037
|
+
);
|
|
15038
|
+
return;
|
|
15039
|
+
}
|
|
15040
|
+
console.error(import_chalk23.default.red(`
|
|
15041
|
+
\u274C Pin file is corrupt: ${result.detail}`));
|
|
15042
|
+
console.error(import_chalk23.default.yellow(" Run: node9 skill pin reset\n"));
|
|
15043
|
+
process.exit(1);
|
|
15044
|
+
}
|
|
15045
|
+
const entries = Object.entries(result.pins.roots);
|
|
15046
|
+
if (entries.length === 0) {
|
|
15047
|
+
console.log(import_chalk23.default.gray("\nNo skill roots are pinned yet.\n"));
|
|
15048
|
+
return;
|
|
15049
|
+
}
|
|
15050
|
+
console.log(import_chalk23.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
15051
|
+
for (const [key, entry] of entries) {
|
|
15052
|
+
const missing = entry.exists ? "" : import_chalk23.default.yellow(" (not present at pin time)");
|
|
15053
|
+
console.log(` ${import_chalk23.default.cyan(key)} ${import_chalk23.default.gray(entry.rootPath)}${missing}`);
|
|
15054
|
+
console.log(` Files (${entry.fileCount})`);
|
|
15055
|
+
console.log(` Hash: ${import_chalk23.default.gray(entry.contentHash.slice(0, 16))}...`);
|
|
15056
|
+
console.log(` Pinned: ${import_chalk23.default.gray(entry.pinnedAt)}
|
|
15057
|
+
`);
|
|
15058
|
+
}
|
|
15059
|
+
});
|
|
15060
|
+
pinSubCmd.command("update <rootKey>").description("Remove a pin so the next session re-pins with current state").action((rootKey) => {
|
|
15061
|
+
let pins;
|
|
15062
|
+
try {
|
|
15063
|
+
pins = readSkillPins();
|
|
15064
|
+
} catch {
|
|
15065
|
+
console.error(import_chalk23.default.red("\n\u274C Pin file is corrupt."));
|
|
15066
|
+
console.error(import_chalk23.default.yellow(" Run: node9 skill pin reset\n"));
|
|
15067
|
+
process.exit(1);
|
|
15068
|
+
}
|
|
15069
|
+
if (!pins.roots[rootKey]) {
|
|
15070
|
+
console.error(import_chalk23.default.red(`
|
|
15071
|
+
\u274C No pin found for root key "${rootKey}"
|
|
15072
|
+
`));
|
|
15073
|
+
console.error(`Run ${import_chalk23.default.cyan("node9 skill pin list")} to see pinned roots.
|
|
15074
|
+
`);
|
|
15075
|
+
process.exit(1);
|
|
15076
|
+
}
|
|
15077
|
+
const rootPath = pins.roots[rootKey].rootPath;
|
|
15078
|
+
removePin(rootKey);
|
|
15079
|
+
wipeSkillSessions();
|
|
15080
|
+
console.log(import_chalk23.default.green(`
|
|
15081
|
+
\u{1F513} Pin removed for ${import_chalk23.default.cyan(rootKey)}`));
|
|
15082
|
+
console.log(import_chalk23.default.gray(` ${rootPath}`));
|
|
15083
|
+
console.log(import_chalk23.default.gray(" Next session will re-pin with current state.\n"));
|
|
15084
|
+
});
|
|
15085
|
+
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
15086
|
+
const result = readSkillPinsSafe();
|
|
15087
|
+
if (!result.ok && result.reason === "missing") {
|
|
15088
|
+
wipeSkillSessions();
|
|
15089
|
+
console.log(import_chalk23.default.gray("\nNo pins to clear.\n"));
|
|
15090
|
+
return;
|
|
15091
|
+
}
|
|
15092
|
+
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
15093
|
+
clearAllPins();
|
|
15094
|
+
wipeSkillSessions();
|
|
15095
|
+
console.log(import_chalk23.default.green(`
|
|
15096
|
+
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
15097
|
+
console.log(import_chalk23.default.gray(" Next session will re-pin with current state.\n"));
|
|
15098
|
+
});
|
|
15099
|
+
}
|
|
15100
|
+
|
|
15101
|
+
// src/cli/commands/dlp.ts
|
|
15102
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
15103
|
+
var import_fs36 = __toESM(require("fs"));
|
|
15104
|
+
var import_path39 = __toESM(require("path"));
|
|
15105
|
+
var import_os32 = __toESM(require("os"));
|
|
15106
|
+
var AUDIT_LOG = import_path39.default.join(import_os32.default.homedir(), ".node9", "audit.log");
|
|
15107
|
+
var RESOLVED_FILE = import_path39.default.join(import_os32.default.homedir(), ".node9", "dlp-resolved.json");
|
|
15108
|
+
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
15109
|
+
function stripAnsi(s) {
|
|
15110
|
+
return s.replace(ANSI_RE, "");
|
|
15111
|
+
}
|
|
15112
|
+
function loadResolved() {
|
|
15113
|
+
try {
|
|
15114
|
+
const raw = JSON.parse(import_fs36.default.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
15115
|
+
return new Set(raw);
|
|
15116
|
+
} catch {
|
|
15117
|
+
return /* @__PURE__ */ new Set();
|
|
15118
|
+
}
|
|
15119
|
+
}
|
|
15120
|
+
function saveResolved(resolved) {
|
|
15121
|
+
try {
|
|
15122
|
+
import_fs36.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
15123
|
+
} catch {
|
|
15124
|
+
}
|
|
15125
|
+
}
|
|
15126
|
+
function loadDlpFindings() {
|
|
15127
|
+
if (!import_fs36.default.existsSync(AUDIT_LOG)) return [];
|
|
15128
|
+
return import_fs36.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
15129
|
+
if (!line.trim()) return [];
|
|
15130
|
+
try {
|
|
15131
|
+
const e = JSON.parse(line);
|
|
15132
|
+
return e.source === "response-dlp" ? [e] : [];
|
|
15133
|
+
} catch {
|
|
15134
|
+
return [];
|
|
15135
|
+
}
|
|
15136
|
+
});
|
|
15137
|
+
}
|
|
15138
|
+
function entryKey(e) {
|
|
15139
|
+
return `${e.ts}:${e.dlpPattern}:${e.dlpSample}`;
|
|
15140
|
+
}
|
|
15141
|
+
function fmtDate3(ts) {
|
|
15142
|
+
try {
|
|
15143
|
+
return new Date(ts).toLocaleDateString("en-US", {
|
|
15144
|
+
month: "short",
|
|
15145
|
+
day: "numeric",
|
|
15146
|
+
year: "numeric"
|
|
15147
|
+
});
|
|
15148
|
+
} catch {
|
|
15149
|
+
return ts.slice(0, 10);
|
|
15150
|
+
}
|
|
15151
|
+
}
|
|
15152
|
+
function registerDlpCommand(program2) {
|
|
15153
|
+
const cmd = program2.command("dlp").description("Show secrets detected in Claude response text and mark them resolved");
|
|
15154
|
+
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
15155
|
+
const findings = loadDlpFindings();
|
|
15156
|
+
if (findings.length === 0) {
|
|
15157
|
+
console.log(import_chalk24.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
15158
|
+
return;
|
|
15159
|
+
}
|
|
15160
|
+
const resolved = loadResolved();
|
|
15161
|
+
for (const e of findings) resolved.add(entryKey(e));
|
|
15162
|
+
saveResolved(resolved);
|
|
15163
|
+
console.log(
|
|
15164
|
+
import_chalk24.default.green(
|
|
15165
|
+
`
|
|
15166
|
+
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
15167
|
+
`
|
|
15168
|
+
)
|
|
15169
|
+
);
|
|
15170
|
+
});
|
|
15171
|
+
cmd.action(() => {
|
|
15172
|
+
const findings = loadDlpFindings();
|
|
15173
|
+
const resolved = loadResolved();
|
|
15174
|
+
const open = findings.filter((e) => !resolved.has(entryKey(e)));
|
|
15175
|
+
const resolvedCount = findings.length - open.length;
|
|
15176
|
+
console.log("");
|
|
15177
|
+
console.log(
|
|
15178
|
+
import_chalk24.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk24.default.dim(" \u2014 secrets found in Claude response text")
|
|
15179
|
+
);
|
|
15180
|
+
console.log("");
|
|
15181
|
+
if (open.length === 0) {
|
|
15182
|
+
if (resolvedCount > 0) {
|
|
15183
|
+
console.log(import_chalk24.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
15184
|
+
} else {
|
|
15185
|
+
console.log(
|
|
15186
|
+
import_chalk24.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
15187
|
+
);
|
|
15188
|
+
}
|
|
15189
|
+
console.log("");
|
|
15190
|
+
return;
|
|
15191
|
+
}
|
|
15192
|
+
console.log(
|
|
15193
|
+
import_chalk24.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk24.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
15194
|
+
);
|
|
15195
|
+
console.log("");
|
|
15196
|
+
console.log(
|
|
15197
|
+
import_chalk24.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
15198
|
+
);
|
|
15199
|
+
console.log(import_chalk24.default.dim(" Rotate each affected key immediately.\n"));
|
|
15200
|
+
for (const e of open) {
|
|
15201
|
+
console.log(
|
|
15202
|
+
" " + import_chalk24.default.red("\u25CF") + " " + import_chalk24.default.white(e.dlpPattern ?? "Secret") + import_chalk24.default.dim(" " + fmtDate3(e.ts))
|
|
15203
|
+
);
|
|
15204
|
+
if (e.dlpSample) {
|
|
15205
|
+
console.log(" " + import_chalk24.default.dim("Sample: ") + import_chalk24.default.yellow(stripAnsi(e.dlpSample)));
|
|
15206
|
+
}
|
|
15207
|
+
if (e.project) {
|
|
15208
|
+
console.log(" " + import_chalk24.default.dim("Project: ") + import_chalk24.default.dim(stripAnsi(e.project)));
|
|
15209
|
+
}
|
|
15210
|
+
console.log("");
|
|
15211
|
+
}
|
|
15212
|
+
console.log(" " + import_chalk24.default.bold("Next steps:"));
|
|
15213
|
+
console.log(" " + import_chalk24.default.cyan("1.") + " Rotate any exposed keys shown above");
|
|
15214
|
+
console.log(
|
|
15215
|
+
" " + import_chalk24.default.cyan("2.") + " Run " + import_chalk24.default.white("node9 dlp resolve") + " to acknowledge"
|
|
15216
|
+
);
|
|
15217
|
+
console.log(
|
|
15218
|
+
" " + import_chalk24.default.cyan("3.") + " Run " + import_chalk24.default.white("node9 report") + " for full audit history"
|
|
15219
|
+
);
|
|
15220
|
+
console.log("");
|
|
15221
|
+
});
|
|
15222
|
+
}
|
|
15223
|
+
|
|
14273
15224
|
// src/cli.ts
|
|
14274
15225
|
var { version } = JSON.parse(
|
|
14275
|
-
|
|
15226
|
+
import_fs39.default.readFileSync(import_path42.default.join(__dirname, "../package.json"), "utf-8")
|
|
14276
15227
|
);
|
|
14277
15228
|
var program = new import_commander.Command();
|
|
14278
15229
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
14279
15230
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
14280
15231
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
14281
|
-
const credPath =
|
|
14282
|
-
if (!
|
|
14283
|
-
|
|
15232
|
+
const credPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "credentials.json");
|
|
15233
|
+
if (!import_fs39.default.existsSync(import_path42.default.dirname(credPath)))
|
|
15234
|
+
import_fs39.default.mkdirSync(import_path42.default.dirname(credPath), { recursive: true });
|
|
14284
15235
|
const profileName = options.profile || "default";
|
|
14285
15236
|
let existingCreds = {};
|
|
14286
15237
|
try {
|
|
14287
|
-
if (
|
|
14288
|
-
const raw = JSON.parse(
|
|
15238
|
+
if (import_fs39.default.existsSync(credPath)) {
|
|
15239
|
+
const raw = JSON.parse(import_fs39.default.readFileSync(credPath, "utf-8"));
|
|
14289
15240
|
if (raw.apiKey) {
|
|
14290
15241
|
existingCreds = {
|
|
14291
15242
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -14297,13 +15248,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14297
15248
|
} catch {
|
|
14298
15249
|
}
|
|
14299
15250
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
14300
|
-
|
|
15251
|
+
import_fs39.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
14301
15252
|
if (profileName === "default") {
|
|
14302
|
-
const configPath =
|
|
15253
|
+
const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
|
|
14303
15254
|
let config = {};
|
|
14304
15255
|
try {
|
|
14305
|
-
if (
|
|
14306
|
-
config = JSON.parse(
|
|
15256
|
+
if (import_fs39.default.existsSync(configPath))
|
|
15257
|
+
config = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
|
|
14307
15258
|
} catch {
|
|
14308
15259
|
}
|
|
14309
15260
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -14318,47 +15269,61 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
14318
15269
|
approvers.cloud = false;
|
|
14319
15270
|
}
|
|
14320
15271
|
s.approvers = approvers;
|
|
14321
|
-
if (!
|
|
14322
|
-
|
|
14323
|
-
|
|
15272
|
+
if (!import_fs39.default.existsSync(import_path42.default.dirname(configPath)))
|
|
15273
|
+
import_fs39.default.mkdirSync(import_path42.default.dirname(configPath), { recursive: true });
|
|
15274
|
+
import_fs39.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
14324
15275
|
}
|
|
14325
15276
|
if (options.profile && profileName !== "default") {
|
|
14326
|
-
console.log(
|
|
14327
|
-
console.log(
|
|
15277
|
+
console.log(import_chalk26.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
15278
|
+
console.log(import_chalk26.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
14328
15279
|
} else if (options.local) {
|
|
14329
|
-
console.log(
|
|
14330
|
-
console.log(
|
|
15280
|
+
console.log(import_chalk26.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
15281
|
+
console.log(import_chalk26.default.gray(` All decisions stay on this machine.`));
|
|
14331
15282
|
} else {
|
|
14332
|
-
console.log(
|
|
14333
|
-
console.log(
|
|
15283
|
+
console.log(import_chalk26.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
15284
|
+
console.log(import_chalk26.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
14334
15285
|
}
|
|
14335
15286
|
});
|
|
14336
|
-
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
15287
|
+
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
15288
|
+
"after",
|
|
15289
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15290
|
+
).argument(
|
|
15291
|
+
"<target>",
|
|
15292
|
+
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
15293
|
+
).action(async (target) => {
|
|
14337
15294
|
if (target === "gemini") return await setupGemini();
|
|
14338
15295
|
if (target === "claude") return await setupClaude();
|
|
14339
15296
|
if (target === "cursor") return await setupCursor();
|
|
15297
|
+
if (target === "codex") return await setupCodex();
|
|
14340
15298
|
if (target === "windsurf") return await setupWindsurf();
|
|
14341
15299
|
if (target === "vscode") return await setupVSCode();
|
|
14342
15300
|
if (target === "hud") return setupHud();
|
|
14343
15301
|
console.error(
|
|
14344
|
-
|
|
14345
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15302
|
+
import_chalk26.default.red(
|
|
15303
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14346
15304
|
)
|
|
14347
15305
|
);
|
|
14348
15306
|
process.exit(1);
|
|
14349
15307
|
});
|
|
14350
|
-
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
15308
|
+
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
15309
|
+
"after",
|
|
15310
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15311
|
+
).argument(
|
|
15312
|
+
"[target]",
|
|
15313
|
+
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
15314
|
+
).action(async (target) => {
|
|
14351
15315
|
if (!target) {
|
|
14352
|
-
console.log(
|
|
14353
|
-
console.log(" Usage: " +
|
|
15316
|
+
console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
15317
|
+
console.log(" Usage: " + import_chalk26.default.white("node9 setup <target>") + "\n");
|
|
14354
15318
|
console.log(" Targets:");
|
|
14355
|
-
console.log(" " +
|
|
14356
|
-
console.log(" " +
|
|
14357
|
-
console.log(" " +
|
|
14358
|
-
console.log(" " +
|
|
14359
|
-
console.log(" " +
|
|
15319
|
+
console.log(" " + import_chalk26.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
15320
|
+
console.log(" " + import_chalk26.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
15321
|
+
console.log(" " + import_chalk26.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
15322
|
+
console.log(" " + import_chalk26.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
15323
|
+
console.log(" " + import_chalk26.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
15324
|
+
console.log(" " + import_chalk26.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
14360
15325
|
process.stdout.write(
|
|
14361
|
-
" " +
|
|
15326
|
+
" " + import_chalk26.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
14362
15327
|
);
|
|
14363
15328
|
console.log("");
|
|
14364
15329
|
return;
|
|
@@ -14367,61 +15332,67 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
14367
15332
|
if (t === "gemini") return await setupGemini();
|
|
14368
15333
|
if (t === "claude") return await setupClaude();
|
|
14369
15334
|
if (t === "cursor") return await setupCursor();
|
|
15335
|
+
if (t === "codex") return await setupCodex();
|
|
14370
15336
|
if (t === "windsurf") return await setupWindsurf();
|
|
14371
15337
|
if (t === "vscode") return await setupVSCode();
|
|
14372
15338
|
if (t === "hud") return setupHud();
|
|
14373
15339
|
console.error(
|
|
14374
|
-
|
|
14375
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15340
|
+
import_chalk26.default.red(
|
|
15341
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14376
15342
|
)
|
|
14377
15343
|
);
|
|
14378
15344
|
process.exit(1);
|
|
14379
15345
|
});
|
|
14380
|
-
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
15346
|
+
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
15347
|
+
"after",
|
|
15348
|
+
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
15349
|
+
).argument(
|
|
14381
15350
|
"<target>",
|
|
14382
|
-
"The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
|
|
15351
|
+
"The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
14383
15352
|
).action((target) => {
|
|
14384
15353
|
let fn;
|
|
14385
15354
|
if (target === "claude") fn = teardownClaude;
|
|
14386
15355
|
else if (target === "gemini") fn = teardownGemini;
|
|
14387
15356
|
else if (target === "cursor") fn = teardownCursor;
|
|
15357
|
+
else if (target === "codex") fn = teardownCodex;
|
|
14388
15358
|
else if (target === "windsurf") fn = teardownWindsurf;
|
|
14389
15359
|
else if (target === "vscode") fn = teardownVSCode;
|
|
14390
15360
|
else if (target === "hud") fn = teardownHud;
|
|
14391
15361
|
else {
|
|
14392
15362
|
console.error(
|
|
14393
|
-
|
|
14394
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
|
|
15363
|
+
import_chalk26.default.red(
|
|
15364
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
14395
15365
|
)
|
|
14396
15366
|
);
|
|
14397
15367
|
process.exit(1);
|
|
14398
15368
|
}
|
|
14399
|
-
console.log(
|
|
15369
|
+
console.log(import_chalk26.default.cyan(`
|
|
14400
15370
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
14401
15371
|
`));
|
|
14402
15372
|
try {
|
|
14403
15373
|
fn();
|
|
14404
15374
|
} catch (err2) {
|
|
14405
|
-
console.error(
|
|
15375
|
+
console.error(import_chalk26.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14406
15376
|
process.exit(1);
|
|
14407
15377
|
}
|
|
14408
|
-
console.log(
|
|
15378
|
+
console.log(import_chalk26.default.gray("\n Restart the agent for changes to take effect."));
|
|
14409
15379
|
});
|
|
14410
15380
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
14411
|
-
console.log(
|
|
14412
|
-
console.log(
|
|
15381
|
+
console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
15382
|
+
console.log(import_chalk26.default.bold("Stopping daemon..."));
|
|
14413
15383
|
try {
|
|
14414
15384
|
stopDaemon();
|
|
14415
|
-
console.log(
|
|
15385
|
+
console.log(import_chalk26.default.green(" \u2705 Daemon stopped"));
|
|
14416
15386
|
} catch {
|
|
14417
|
-
console.log(
|
|
15387
|
+
console.log(import_chalk26.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
14418
15388
|
}
|
|
14419
|
-
console.log(
|
|
15389
|
+
console.log(import_chalk26.default.bold("\nRemoving hooks..."));
|
|
14420
15390
|
let teardownFailed = false;
|
|
14421
15391
|
for (const [label, fn] of [
|
|
14422
15392
|
["Claude", teardownClaude],
|
|
14423
15393
|
["Gemini", teardownGemini],
|
|
14424
15394
|
["Cursor", teardownCursor],
|
|
15395
|
+
["Codex", teardownCodex],
|
|
14425
15396
|
["Windsurf", teardownWindsurf],
|
|
14426
15397
|
["VSCode", teardownVSCode]
|
|
14427
15398
|
]) {
|
|
@@ -14430,45 +15401,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
14430
15401
|
} catch (err2) {
|
|
14431
15402
|
teardownFailed = true;
|
|
14432
15403
|
console.error(
|
|
14433
|
-
|
|
15404
|
+
import_chalk26.default.red(
|
|
14434
15405
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
14435
15406
|
)
|
|
14436
15407
|
);
|
|
14437
15408
|
}
|
|
14438
15409
|
}
|
|
14439
15410
|
if (options.purge) {
|
|
14440
|
-
const node9Dir =
|
|
14441
|
-
if (
|
|
15411
|
+
const node9Dir = import_path42.default.join(import_os35.default.homedir(), ".node9");
|
|
15412
|
+
if (import_fs39.default.existsSync(node9Dir)) {
|
|
14442
15413
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
14443
15414
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
14444
15415
|
default: false
|
|
14445
15416
|
});
|
|
14446
15417
|
if (confirmed) {
|
|
14447
|
-
|
|
14448
|
-
if (
|
|
15418
|
+
import_fs39.default.rmSync(node9Dir, { recursive: true });
|
|
15419
|
+
if (import_fs39.default.existsSync(node9Dir)) {
|
|
14449
15420
|
console.error(
|
|
14450
|
-
|
|
15421
|
+
import_chalk26.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
14451
15422
|
);
|
|
14452
15423
|
} else {
|
|
14453
|
-
console.log(
|
|
15424
|
+
console.log(import_chalk26.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
14454
15425
|
}
|
|
14455
15426
|
} else {
|
|
14456
|
-
console.log(
|
|
15427
|
+
console.log(import_chalk26.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
14457
15428
|
}
|
|
14458
15429
|
} else {
|
|
14459
|
-
console.log(
|
|
15430
|
+
console.log(import_chalk26.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
14460
15431
|
}
|
|
14461
15432
|
} else {
|
|
14462
15433
|
console.log(
|
|
14463
|
-
|
|
15434
|
+
import_chalk26.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
14464
15435
|
);
|
|
14465
15436
|
}
|
|
14466
15437
|
if (teardownFailed) {
|
|
14467
|
-
console.error(
|
|
15438
|
+
console.error(import_chalk26.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
14468
15439
|
process.exit(1);
|
|
14469
15440
|
}
|
|
14470
|
-
console.log(
|
|
14471
|
-
console.log(
|
|
15441
|
+
console.log(import_chalk26.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
15442
|
+
console.log(import_chalk26.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
14472
15443
|
});
|
|
14473
15444
|
registerDoctorCommand(program, version);
|
|
14474
15445
|
program.command("explain").description(
|
|
@@ -14481,7 +15452,7 @@ program.command("explain").description(
|
|
|
14481
15452
|
try {
|
|
14482
15453
|
args = JSON.parse(trimmed);
|
|
14483
15454
|
} catch {
|
|
14484
|
-
console.error(
|
|
15455
|
+
console.error(import_chalk26.default.red(`
|
|
14485
15456
|
\u274C Invalid JSON: ${trimmed}
|
|
14486
15457
|
`));
|
|
14487
15458
|
process.exit(1);
|
|
@@ -14492,54 +15463,54 @@ program.command("explain").description(
|
|
|
14492
15463
|
}
|
|
14493
15464
|
const result = await explainPolicy(tool, args);
|
|
14494
15465
|
console.log("");
|
|
14495
|
-
console.log(
|
|
15466
|
+
console.log(import_chalk26.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
14496
15467
|
console.log("");
|
|
14497
|
-
console.log(` ${
|
|
15468
|
+
console.log(` ${import_chalk26.default.bold("Tool:")} ${import_chalk26.default.white(result.tool)}`);
|
|
14498
15469
|
if (argsRaw) {
|
|
14499
15470
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
14500
|
-
console.log(` ${
|
|
15471
|
+
console.log(` ${import_chalk26.default.bold("Input:")} ${import_chalk26.default.gray(preview2)}`);
|
|
14501
15472
|
}
|
|
14502
15473
|
console.log("");
|
|
14503
|
-
console.log(
|
|
15474
|
+
console.log(import_chalk26.default.bold("Config Sources (Waterfall):"));
|
|
14504
15475
|
for (const tier of result.waterfall) {
|
|
14505
|
-
const num3 =
|
|
15476
|
+
const num3 = import_chalk26.default.gray(` ${tier.tier}.`);
|
|
14506
15477
|
const label = tier.label.padEnd(16);
|
|
14507
15478
|
let statusStr;
|
|
14508
15479
|
if (tier.tier === 1) {
|
|
14509
|
-
statusStr =
|
|
15480
|
+
statusStr = import_chalk26.default.gray(tier.note ?? "");
|
|
14510
15481
|
} else if (tier.status === "active") {
|
|
14511
|
-
const loc = tier.path ?
|
|
14512
|
-
const note = tier.note ?
|
|
14513
|
-
statusStr =
|
|
15482
|
+
const loc = tier.path ? import_chalk26.default.gray(tier.path) : "";
|
|
15483
|
+
const note = tier.note ? import_chalk26.default.gray(`(${tier.note})`) : "";
|
|
15484
|
+
statusStr = import_chalk26.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
14514
15485
|
} else {
|
|
14515
|
-
statusStr =
|
|
15486
|
+
statusStr = import_chalk26.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
14516
15487
|
}
|
|
14517
|
-
console.log(`${num3} ${
|
|
15488
|
+
console.log(`${num3} ${import_chalk26.default.white(label)} ${statusStr}`);
|
|
14518
15489
|
}
|
|
14519
15490
|
console.log("");
|
|
14520
|
-
console.log(
|
|
15491
|
+
console.log(import_chalk26.default.bold("Policy Evaluation:"));
|
|
14521
15492
|
for (const step of result.steps) {
|
|
14522
15493
|
const isFinal = step.isFinal;
|
|
14523
15494
|
let icon;
|
|
14524
|
-
if (step.outcome === "allow") icon =
|
|
14525
|
-
else if (step.outcome === "review") icon =
|
|
14526
|
-
else if (step.outcome === "skip") icon =
|
|
14527
|
-
else icon =
|
|
15495
|
+
if (step.outcome === "allow") icon = import_chalk26.default.green(" \u2705");
|
|
15496
|
+
else if (step.outcome === "review") icon = import_chalk26.default.red(" \u{1F534}");
|
|
15497
|
+
else if (step.outcome === "skip") icon = import_chalk26.default.gray(" \u2500 ");
|
|
15498
|
+
else icon = import_chalk26.default.gray(" \u25CB ");
|
|
14528
15499
|
const name = step.name.padEnd(18);
|
|
14529
|
-
const nameStr = isFinal ?
|
|
14530
|
-
const detail = isFinal ?
|
|
14531
|
-
const arrow = isFinal ?
|
|
15500
|
+
const nameStr = isFinal ? import_chalk26.default.white.bold(name) : import_chalk26.default.white(name);
|
|
15501
|
+
const detail = isFinal ? import_chalk26.default.white(step.detail) : import_chalk26.default.gray(step.detail);
|
|
15502
|
+
const arrow = isFinal ? import_chalk26.default.yellow(" \u2190 STOP") : "";
|
|
14532
15503
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
14533
15504
|
}
|
|
14534
15505
|
console.log("");
|
|
14535
15506
|
if (result.decision === "allow") {
|
|
14536
|
-
console.log(
|
|
15507
|
+
console.log(import_chalk26.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk26.default.gray(" \u2014 no approval needed"));
|
|
14537
15508
|
} else {
|
|
14538
15509
|
console.log(
|
|
14539
|
-
|
|
15510
|
+
import_chalk26.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk26.default.gray(" \u2014 human approval required")
|
|
14540
15511
|
);
|
|
14541
15512
|
if (result.blockedByLabel) {
|
|
14542
|
-
console.log(
|
|
15513
|
+
console.log(import_chalk26.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
14543
15514
|
}
|
|
14544
15515
|
}
|
|
14545
15516
|
console.log("");
|
|
@@ -14554,7 +15525,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
14554
15525
|
try {
|
|
14555
15526
|
await startTail2(options);
|
|
14556
15527
|
} catch (err2) {
|
|
14557
|
-
console.error(
|
|
15528
|
+
console.error(import_chalk26.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
14558
15529
|
process.exit(1);
|
|
14559
15530
|
}
|
|
14560
15531
|
});
|
|
@@ -14562,6 +15533,7 @@ registerWatchCommand(program);
|
|
|
14562
15533
|
registerMcpGatewayCommand(program);
|
|
14563
15534
|
registerMcpServerCommand(program);
|
|
14564
15535
|
registerMcpPinCommand(program);
|
|
15536
|
+
registerSkillPinCommand(program);
|
|
14565
15537
|
registerCheckCommand(program);
|
|
14566
15538
|
registerLogCommand(program);
|
|
14567
15539
|
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
@@ -14586,14 +15558,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
14586
15558
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
14587
15559
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
14588
15560
|
if (subcommand === "debug") {
|
|
14589
|
-
const flagFile =
|
|
15561
|
+
const flagFile = import_path42.default.join(import_os35.default.homedir(), ".node9", "hud-debug");
|
|
14590
15562
|
if (state === "on") {
|
|
14591
|
-
|
|
14592
|
-
|
|
15563
|
+
import_fs39.default.mkdirSync(import_path42.default.dirname(flagFile), { recursive: true });
|
|
15564
|
+
import_fs39.default.writeFileSync(flagFile, "");
|
|
14593
15565
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
14594
15566
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
14595
15567
|
} else if (state === "off") {
|
|
14596
|
-
if (
|
|
15568
|
+
if (import_fs39.default.existsSync(flagFile)) import_fs39.default.unlinkSync(flagFile);
|
|
14597
15569
|
console.log("HUD debug logging disabled.");
|
|
14598
15570
|
} else {
|
|
14599
15571
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -14608,7 +15580,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
14608
15580
|
const ms = parseDuration(options.duration);
|
|
14609
15581
|
if (ms === null) {
|
|
14610
15582
|
console.error(
|
|
14611
|
-
|
|
15583
|
+
import_chalk26.default.red(`
|
|
14612
15584
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
14613
15585
|
`)
|
|
14614
15586
|
);
|
|
@@ -14616,20 +15588,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
14616
15588
|
}
|
|
14617
15589
|
pauseNode9(ms, options.duration);
|
|
14618
15590
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
14619
|
-
console.log(
|
|
15591
|
+
console.log(import_chalk26.default.yellow(`
|
|
14620
15592
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
14621
|
-
console.log(
|
|
14622
|
-
console.log(
|
|
15593
|
+
console.log(import_chalk26.default.gray(` All tool calls will be allowed without review.`));
|
|
15594
|
+
console.log(import_chalk26.default.gray(` Run "node9 resume" to re-enable early.
|
|
14623
15595
|
`));
|
|
14624
15596
|
});
|
|
14625
15597
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
14626
15598
|
const { paused } = checkPause();
|
|
14627
15599
|
if (!paused) {
|
|
14628
|
-
console.log(
|
|
15600
|
+
console.log(import_chalk26.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
14629
15601
|
return;
|
|
14630
15602
|
}
|
|
14631
15603
|
resumeNode9();
|
|
14632
|
-
console.log(
|
|
15604
|
+
console.log(import_chalk26.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
14633
15605
|
});
|
|
14634
15606
|
var HOOK_BASED_AGENTS = {
|
|
14635
15607
|
claude: "claude",
|
|
@@ -14642,15 +15614,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14642
15614
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
14643
15615
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
14644
15616
|
console.error(
|
|
14645
|
-
|
|
15617
|
+
import_chalk26.default.yellow(`
|
|
14646
15618
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
14647
15619
|
);
|
|
14648
|
-
console.error(
|
|
15620
|
+
console.error(import_chalk26.default.white(`
|
|
14649
15621
|
"${target}" uses its own hook system. Use:`));
|
|
14650
15622
|
console.error(
|
|
14651
|
-
|
|
15623
|
+
import_chalk26.default.green(` node9 addto ${target} `) + import_chalk26.default.gray("# one-time setup")
|
|
14652
15624
|
);
|
|
14653
|
-
console.error(
|
|
15625
|
+
console.error(import_chalk26.default.green(` ${target} `) + import_chalk26.default.gray("# run normally"));
|
|
14654
15626
|
process.exit(1);
|
|
14655
15627
|
}
|
|
14656
15628
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -14667,7 +15639,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14667
15639
|
}
|
|
14668
15640
|
);
|
|
14669
15641
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
14670
|
-
console.error(
|
|
15642
|
+
console.error(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
14671
15643
|
const daemonReady = await autoStartDaemonAndWait();
|
|
14672
15644
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
14673
15645
|
}
|
|
@@ -14680,12 +15652,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
14680
15652
|
}
|
|
14681
15653
|
if (!result.approved) {
|
|
14682
15654
|
console.error(
|
|
14683
|
-
|
|
15655
|
+
import_chalk26.default.red(`
|
|
14684
15656
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
14685
15657
|
);
|
|
14686
15658
|
process.exit(1);
|
|
14687
15659
|
}
|
|
14688
|
-
console.error(
|
|
15660
|
+
console.error(import_chalk26.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
14689
15661
|
await runProxy(fullCommand);
|
|
14690
15662
|
} else {
|
|
14691
15663
|
program.help();
|
|
@@ -14699,14 +15671,15 @@ registerSyncCommand(program);
|
|
|
14699
15671
|
registerAgentsCommand(program);
|
|
14700
15672
|
registerScanCommand(program);
|
|
14701
15673
|
registerSessionsCommand(program);
|
|
15674
|
+
registerDlpCommand(program);
|
|
14702
15675
|
if (process.argv[2] !== "daemon") {
|
|
14703
15676
|
process.on("unhandledRejection", (reason) => {
|
|
14704
15677
|
const isCheckHook = process.argv[2] === "check";
|
|
14705
15678
|
if (isCheckHook) {
|
|
14706
15679
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
14707
|
-
const logPath =
|
|
15680
|
+
const logPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "hook-debug.log");
|
|
14708
15681
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
14709
|
-
|
|
15682
|
+
import_fs39.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
14710
15683
|
`);
|
|
14711
15684
|
}
|
|
14712
15685
|
process.exit(0);
|