@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/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 path39 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path39}: ${issue.message}`;
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
- // Require the recursive flag to be preceded by whitespace so that
826
- // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
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
- value: "\\bgit\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
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-push",
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\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
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: "matches",
908
- value: "\\bgit\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
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
- value: "(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
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\-._~+/]+=*/i, severity: "review" }
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, path39) {
1785
+ function getNestedValue(obj, path43) {
1750
1786
  if (!obj || typeof obj !== "object") return null;
1751
- return path39.split(".").reduce((prev, curr) => prev?.[curr], obj);
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 val = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
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) => e.tool === toolName || matchesPattern(toolName, e.tool));
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((e) => e.tool !== toolName && e.expiry > now);
2404
- trust.entries.push({ tool: toolName, expiry: now + durationMs });
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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
- lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
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({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
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((e) => e.tool !== toolName && e.expiry > Date.now());
5804
- trust.entries.push({ tool: toolName, expiry: Date.now() + durationMs });
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
- import_fs18.default.unlinkSync(DAEMON_PID_FILE);
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" && import_path21.default.isAbsolute(cwd) ? cwd : void 0;
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
- writeTrustEntry(entry.toolName, ms);
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 = import_path21.default.dirname(GLOBAL_CONFIG_PATH);
6997
- if (!import_path21.default.resolve(configPath).startsWith(node9Dir + import_path21.default.sep)) {
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 (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
7109
- const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
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
- import_fs18.default.unlinkSync(DAEMON_PID_FILE);
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, import_fs18, import_path21, import_crypto7, import_child_process4, import_chalk2;
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
- import_fs18 = __toESM(require("fs"));
7180
- import_path21 = __toESM(require("path"));
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" && import_path22.default.isAbsolute(script) && import_fs19.default.existsSync(script)) {
7200
- return import_fs19.default.realpathSync(script);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7216
7475
  }
7217
7476
  function launchdPlist(binaryPath) {
7218
- const logDir = import_path22.default.join(import_os16.default.homedir(), ".node9");
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(import_path22.default.join(logDir, "daemon.log"));
7222
- const errLog = xmlEscape(import_path22.default.join(logDir, "daemon-error.log"));
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 = import_path22.default.dirname(LAUNCHD_PLIST);
7258
- if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
7259
- import_fs19.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
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 (import_fs19.default.existsSync(LAUNCHD_PLIST)) {
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
- import_fs19.default.unlinkSync(LAUNCHD_PLIST);
7531
+ import_fs20.default.unlinkSync(LAUNCHD_PLIST);
7273
7532
  }
7274
7533
  }
7275
7534
  function isLaunchdInstalled() {
7276
- return import_fs19.default.existsSync(LAUNCHD_PLIST);
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 (!import_fs19.default.existsSync(SYSTEMD_UNIT_DIR)) {
7297
- import_fs19.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7555
+ if (!import_fs20.default.existsSync(SYSTEMD_UNIT_DIR)) {
7556
+ import_fs20.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7298
7557
  }
7299
- import_fs19.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7558
+ import_fs20.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7300
7559
  try {
7301
- (0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os16.default.userInfo().username], { timeout: 3e3 });
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 (import_fs19.default.existsSync(SYSTEMD_UNIT)) {
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
- import_fs19.default.unlinkSync(SYSTEMD_UNIT);
7586
+ import_fs20.default.unlinkSync(SYSTEMD_UNIT);
7328
7587
  }
7329
7588
  }
7330
7589
  function isSystemdInstalled() {
7331
- return import_fs19.default.existsSync(SYSTEMD_UNIT);
7590
+ return import_fs20.default.existsSync(SYSTEMD_UNIT);
7332
7591
  }
7333
7592
  function stopRunningDaemon() {
7334
- const pidFile = import_path22.default.join(import_os16.default.homedir(), ".node9", "daemon.pid");
7335
- if (!import_fs19.default.existsSync(pidFile)) return;
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(import_fs19.default.readFileSync(pidFile, "utf-8"));
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
- import_fs19.default.unlinkSync(pidFile);
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 import_fs19, import_path22, import_os16, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
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
- import_fs19 = __toESM(require("fs"));
7432
- import_path22 = __toESM(require("path"));
7433
- import_os16 = __toESM(require("os"));
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 = import_path22.default.join(import_os16.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7437
- SYSTEMD_UNIT_DIR = import_path22.default.join(import_os16.default.homedir(), ".config", "systemd", "user");
7438
- SYSTEMD_UNIT = import_path22.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
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 (!import_fs20.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
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(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
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
- import_fs20.default.unlinkSync(DAEMON_PID_FILE);
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 (import_fs20.default.existsSync(DAEMON_PID_FILE)) {
7726
+ if (import_fs21.default.existsSync(DAEMON_PID_FILE)) {
7468
7727
  try {
7469
- const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
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 import_fs20, import_chalk3, import_child_process6, MAX_PID;
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
- import_fs20 = __toESM(require("fs"));
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(import_os29.default.homedir(), "~");
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 `${import_chalk23.default.gray(time)} ${icon} ${import_chalk23.default.white.bold(toolName)} ${import_chalk23.default.dim(argsPreview)}`;
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 = import_chalk23.default.green("\u2713 ALLOW");
7880
+ status = import_chalk25.default.green("\u2713 ALLOW");
7549
7881
  } else if (result.status === "dlp") {
7550
- status = import_chalk23.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7882
+ status = import_chalk25.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7551
7883
  } else {
7552
- status = import_chalk23.default.red("\u2717 BLOCK");
7884
+ status = import_chalk25.default.red("\u2717 BLOCK");
7553
7885
  }
7554
7886
  const cost = result.costEstimate ?? activity.costEstimate;
7555
- const costSuffix = cost == null ? "" : import_chalk23.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
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)} ${import_chalk23.default.yellow("\u25CF \u2026")}`;
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 (import_fs33.default.existsSync(PID_FILE)) {
7911
+ if (import_fs37.default.existsSync(PID_FILE)) {
7580
7912
  try {
7581
- const { port } = JSON.parse(import_fs33.default.readFileSync(PID_FILE, "utf-8"));
7913
+ const { port } = JSON.parse(import_fs37.default.readFileSync(PID_FILE, "utf-8"));
7582
7914
  pidPort = port;
7583
7915
  } catch {
7584
- console.error(import_chalk23.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
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(import_chalk23.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
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(import_chalk23.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
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 = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
8066
+ const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
7734
8067
  try {
7735
- const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
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 ? import_chalk23.default.green("\u2713") : import_chalk23.default.dim("\u2717")}`;
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 = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
8084
+ const configPath = import_path40.default.join(import_os33.default.homedir(), ".node9", "config.json");
7752
8085
  try {
7753
- const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
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
- import_fs33.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
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(import_chalk23.default.green("\u2713 Flight Recorder buffer cleared."));
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(import_chalk23.default.dim(` Approvers: ${approverStatusLine()}`));
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" ? import_chalk23.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk23.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk23.default.yellow("\u21A9 REDIRECT AI") : import_chalk23.default.red("\u2717 DENIED");
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
- import_fs33.default.appendFileSync(
7930
- import_path36.default.join(import_os29.default.homedir(), ".node9", "hook-debug.log"),
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" ? import_chalk23.default.green("\u2713 ALLOWED") : import_chalk23.default.red("\u2717 DENIED");
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
- console.log(import_chalk23.default.cyan.bold(`
8012
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk23.default.dim(`\u2192 ${dashboardUrl}`));
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(import_chalk23.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8015
- console.log(import_chalk23.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
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(import_chalk23.default.dim("Showing history + live events.\n"));
8366
+ console.log(import_chalk25.default.dim("Showing history + live events.\n"));
8019
8367
  } else {
8020
- console.log(import_chalk23.default.dim("Showing live events only. Use --history to include past.\n"));
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(import_chalk23.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
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(import_chalk23.default.red(`Failed to connect: HTTP ${res.statusCode}`));
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(import_chalk23.default.red("\n\u274C Daemon disconnected."));
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 ? import_chalk23.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8507
+ const files = fileCount > 0 ? import_chalk25.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8160
8508
  process.stdout.write(
8161
- `${import_chalk23.default.dim(time)} ${import_chalk23.default.cyan("\u{1F4F8} snapshot")} ${import_chalk23.default.dim(hash)} ${summary}${files}
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(import_chalk23.default.red(`
8526
+ console.error(import_chalk25.default.red(`
8179
8527
  \u274C ${msg}`));
8180
8528
  process.exit(1);
8181
8529
  });
8182
8530
  }
8183
- var import_http2, import_chalk23, import_fs33, import_os29, import_path36, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
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
- import_chalk23 = __toESM(require("chalk"));
8189
- import_fs33 = __toESM(require("fs"));
8190
- import_os29 = __toESM(require("os"));
8191
- import_path36 = __toESM(require("path"));
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 = import_path36.default.join(import_os29.default.homedir(), ".node9", "daemon.pid");
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 (!import_fs34.default.existsSync(filePath)) return null;
8667
+ if (!import_fs38.default.existsSync(filePath)) return null;
8313
8668
  try {
8314
- return JSON.parse(import_fs34.default.readFileSync(filePath, "utf-8"));
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 (!import_fs34.default.existsSync(rulesDir)) return 0;
8690
+ if (!import_fs38.default.existsSync(rulesDir)) return 0;
8336
8691
  let count = 0;
8337
8692
  try {
8338
- for (const entry of import_fs34.default.readdirSync(rulesDir, { withFileTypes: true })) {
8693
+ for (const entry of import_fs38.default.readdirSync(rulesDir, { withFileTypes: true })) {
8339
8694
  if (entry.isDirectory()) {
8340
- count += countRulesInDir(import_path37.default.join(rulesDir, entry.name));
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 import_path37.default.resolve(a) === import_path37.default.resolve(b);
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 = import_os30.default.homedir();
8358
- const claudeDir = import_path37.default.join(homeDir2, ".claude");
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 (import_fs34.default.existsSync(import_path37.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8365
- rulesCount += countRulesInDir(import_path37.default.join(claudeDir, "rules"));
8366
- const userSettings = import_path37.default.join(claudeDir, "settings.json");
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 = import_path37.default.join(homeDir2, ".claude.json");
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 (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8376
- if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8377
- const projectClaudeDir = import_path37.default.join(cwd, ".claude");
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 (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8381
- rulesCount += countRulesInDir(import_path37.default.join(projectClaudeDir, "rules"));
8382
- const projSettings = import_path37.default.join(projectClaudeDir, "settings.json");
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 (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8387
- const localSettings = import_path37.default.join(projectClaudeDir, "settings.local.json");
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(import_path37.default.join(cwd, ".mcp.json"));
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 = import_path37.default.join(import_os30.default.homedir(), ".node9", "shields.json");
8424
- if (!import_fs34.default.existsSync(shieldsPath)) {
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(import_fs34.default.readFileSync(shieldsPath, "utf-8"));
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 (import_fs34.default.existsSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug"))) {
8885
+ if (import_fs38.default.existsSync(import_path41.default.join(import_os34.default.homedir(), ".node9", "hud-debug"))) {
8531
8886
  try {
8532
- const logPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug.log");
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 = import_fs34.default.statSync(logPath).size;
8891
+ size = import_fs38.default.statSync(logPath).size;
8537
8892
  } catch {
8538
8893
  }
8539
8894
  if (size < MAX_LOG_SIZE) {
8540
- import_fs34.default.appendFileSync(
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
- import_path37.default.join(cwd, "node9.config.json"),
8562
- import_path37.default.join(import_os30.default.homedir(), ".node9", "config.json")
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 (!import_fs34.default.existsSync(configPath)) continue;
8565
- const cfg = JSON.parse(import_fs34.default.readFileSync(configPath, "utf-8"));
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 import_fs34, import_path37, import_os30, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
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
- import_fs34 = __toESM(require("fs"));
8588
- import_path37 = __toESM(require("path"));
8589
- import_os30 = __toESM(require("os"));
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 import_chalk24 = __toESM(require("chalk"));
9505
- var import_fs35 = __toESM(require("fs"));
9506
- var import_path38 = __toESM(require("path"));
9507
- var import_os31 = __toESM(require("os"));
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 import_fs22 = __toESM(require("fs"));
10087
+ var import_fs24 = __toESM(require("fs"));
9733
10088
  var import_child_process10 = require("child_process");
9734
- var import_path24 = __toESM(require("path"));
9735
- var import_os18 = __toESM(require("os"));
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 import_fs21 = __toESM(require("fs"));
10099
+ var import_fs22 = __toESM(require("fs"));
9745
10100
  var import_net3 = __toESM(require("net"));
9746
- var import_path23 = __toESM(require("path"));
9747
- var import_os17 = __toESM(require("os"));
9748
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path23.default.join(import_os17.default.tmpdir(), "node9-activity.sock");
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 = import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots.json");
9769
- var UNDO_LATEST_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "undo_latest.txt");
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 (import_fs21.default.existsSync(SNAPSHOT_STACK_PATH))
9775
- return JSON.parse(import_fs21.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
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 = import_path23.default.dirname(SNAPSHOT_STACK_PATH);
9782
- if (!import_fs21.default.existsSync(dir)) import_fs21.default.mkdirSync(dir, { recursive: true });
9783
- import_fs21.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
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 = import_path23.default.dirname(filePath);
10158
+ let dir = import_path24.default.dirname(filePath);
9804
10159
  while (true) {
9805
- if (import_fs21.default.existsSync(import_path23.default.join(dir, ".git")) || import_fs21.default.existsSync(import_path23.default.join(dir, "package.json"))) {
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 = import_path23.default.dirname(dir);
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 = import_fs21.default.realpathSync(cwd);
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 import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots", hash);
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 import_fs21.default.readdirSync(shadowDir)) {
10186
+ for (const f of import_fs22.default.readdirSync(shadowDir)) {
9832
10187
  if (f.startsWith("index_")) {
9833
- const fp = import_path23.default.join(shadowDir, f);
10188
+ const fp = import_path24.default.join(shadowDir, f);
9834
10189
  try {
9835
- if (import_fs21.default.statSync(fp).mtimeMs < cutoff) import_fs21.default.unlinkSync(fp);
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
- import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
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 = import_path23.default.join(shadowDir, "project-path.txt");
10215
+ const ptPath = import_path24.default.join(shadowDir, "project-path.txt");
9861
10216
  try {
9862
- const stored = import_fs21.default.readFileSync(ptPath, "utf8").trim();
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
- import_fs21.default.rmSync(shadowDir, { recursive: true, force: true });
10223
+ import_fs22.default.rmSync(shadowDir, { recursive: true, force: true });
9869
10224
  } catch {
9870
10225
  try {
9871
- import_fs21.default.writeFileSync(ptPath, normalizedCwd, "utf8");
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
- import_fs21.default.mkdirSync(shadowDir, { recursive: true });
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 = import_path23.default.join(shadowDir, "config");
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
- import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
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 && import_path23.default.isAbsolute(rawFilePath) ? rawFilePath : null;
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 = import_path23.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
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
- import_fs21.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
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
- import_fs21.default.unlinkSync(indexFile);
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 = import_path23.default.join(dir, file);
10080
- if (!snapshotFiles.has(file) && import_fs21.default.existsSync(fullPath)) {
10081
- import_fs21.default.unlinkSync(fullPath);
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 = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
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
- import_fs22.default.appendFileSync(
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" || !import_path24.default.isAbsolute(scriptPath))
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 = import_fs22.default.realpathSync(scriptPath);
10123
- const packageDist = import_fs22.default.realpathSync(import_path24.default.resolve(__dirname, "../.."));
10124
- if (!resolvedScript.startsWith(packageDist + import_path24.default.sep) && resolvedScript !== 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 = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
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
- import_fs22.default.appendFileSync(
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 = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10160
- if (!import_fs22.default.existsSync(import_path24.default.dirname(logPath)))
10161
- import_fs22.default.mkdirSync(import_path24.default.dirname(logPath), { recursive: true });
10162
- import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
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 = import_fs22.default.openSync("/dev/tty", "w");
10176
- const writeTty = (line) => import_fs22.default.writeSync(ttyFd, line + "\n");
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
- import_fs22.default.closeSync(ttyFd);
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" && import_path24.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
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 = import_fs22.default.openSync("/dev/tty", "w");
10240
- import_fs22.default.writeSync(
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
- import_fs22.default.closeSync(tty);
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 = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
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
- import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
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 import_fs23 = __toESM(require("fs"));
10308
- var import_path25 = __toESM(require("path"));
10309
- var import_os19 = __toESM(require("os"));
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 = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10386
- if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
10387
- import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
10388
- import_fs23.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
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" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
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 = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
11087
+ const debugPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "hook-debug.log");
10431
11088
  try {
10432
- import_fs23.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
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 import_fs24 = __toESM(require("fs"));
10833
- var import_path26 = __toESM(require("path"));
10834
- var import_os20 = __toESM(require("os"));
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 = import_os20.default.homedir();
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 = import_path26.default.join(homeDir2, ".node9", "config.json");
10889
- if (import_fs24.default.existsSync(globalConfigPath)) {
11545
+ const globalConfigPath = import_path28.default.join(homeDir2, ".node9", "config.json");
11546
+ if (import_fs26.default.existsSync(globalConfigPath)) {
10890
11547
  try {
10891
- JSON.parse(import_fs24.default.readFileSync(globalConfigPath, "utf-8"));
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 = import_path26.default.join(process.cwd(), "node9.config.json");
10900
- if (import_fs24.default.existsSync(projectConfigPath)) {
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(import_fs24.default.readFileSync(projectConfigPath, "utf-8"));
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 = import_path26.default.join(homeDir2, ".node9", "credentials.json");
10912
- if (import_fs24.default.existsSync(credsPath)) {
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 = import_path26.default.join(homeDir2, ".claude", "settings.json");
10922
- if (import_fs24.default.existsSync(claudeSettingsPath)) {
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(import_fs24.default.readFileSync(claudeSettingsPath, "utf-8"));
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 = import_path26.default.join(homeDir2, ".gemini", "settings.json");
10941
- if (import_fs24.default.existsSync(geminiSettingsPath)) {
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(import_fs24.default.readFileSync(geminiSettingsPath, "utf-8"));
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 = import_path26.default.join(homeDir2, ".cursor", "hooks.json");
10960
- if (import_fs24.default.existsSync(cursorHooksPath)) {
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(import_fs24.default.readFileSync(cursorHooksPath, "utf-8"));
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 import_fs25 = __toESM(require("fs"));
11001
- var import_path27 = __toESM(require("path"));
11002
- var import_os21 = __toESM(require("os"));
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 = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11016
- if (!import_fs25.default.existsSync(logPath)) {
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 = import_fs25.default.readFileSync(logPath, "utf-8");
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 import_fs26 = __toESM(require("fs"));
11077
- var import_path28 = __toESM(require("path"));
11078
- var import_os22 = __toESM(require("os"));
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 (!import_fs26.default.existsSync(logPath)) return [];
11126
- const raw = import_fs26.default.readFileSync(logPath, "utf-8");
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 = import_path28.default.join(import_os22.default.homedir(), ".claude", "projects");
11196
- if (!import_fs26.default.existsSync(projectsDir)) return empty;
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 = import_fs26.default.readdirSync(projectsDir);
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 = import_path28.default.join(projectsDir, proj);
11870
+ const projPath = import_path30.default.join(projectsDir, proj);
11210
11871
  let files;
11211
11872
  try {
11212
- const stat = import_fs26.default.statSync(projPath);
11873
+ const stat = import_fs28.default.statSync(projPath);
11213
11874
  if (!stat.isDirectory()) continue;
11214
- files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
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 = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
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 = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
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 > 1) {
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 import_fs27 = __toESM(require("fs"));
11617
- var import_path29 = __toESM(require("path"));
11618
- var import_os23 = __toESM(require("os"));
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 (import_fs27.default.existsSync(filePath)) return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
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 = import_path29.default.join(process.cwd(), "node9.config.json");
11689
- const globalConfig = import_path29.default.join(import_os23.default.homedir(), ".node9", "config.json");
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: ${import_fs27.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
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: ${import_fs27.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
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 = import_os23.default.homedir();
12437
+ const homeDir2 = import_os25.default.homedir();
11702
12438
  const claudeSettings = readJson2(
11703
- import_path29.default.join(homeDir2, ".claude", "settings.json")
12439
+ import_path31.default.join(homeDir2, ".claude", "settings.json")
11704
12440
  );
11705
- const claudeConfig = readJson2(import_path29.default.join(homeDir2, ".claude.json"));
12441
+ const claudeConfig = readJson2(import_path31.default.join(homeDir2, ".claude.json"));
11706
12442
  const geminiSettings = readJson2(
11707
- import_path29.default.join(homeDir2, ".gemini", "settings.json")
12443
+ import_path31.default.join(homeDir2, ".gemini", "settings.json")
11708
12444
  );
11709
- const cursorConfig = readJson2(import_path29.default.join(homeDir2, ".cursor", "mcp.json"));
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 import_fs28 = __toESM(require("fs"));
11769
- var import_path30 = __toESM(require("path"));
11770
- var import_os24 = __toESM(require("os"));
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 = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
11832
- if (import_fs28.default.existsSync(configPath) && !options.force) {
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(import_fs28.default.readFileSync(configPath, "utf-8"));
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
- import_fs28.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
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 = import_path30.default.dirname(configPath);
11853
- if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
11854
- import_fs28.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
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 import_path31 = __toESM(require("path"));
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 = import_path31.default.dirname(dir);
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 import_fs29 = __toESM(require("fs"));
12294
- var import_path32 = __toESM(require("path"));
12295
- var import_os25 = __toESM(require("os"));
12296
- var import_crypto9 = __toESM(require("crypto"));
12297
- function getPinsFilePath() {
12298
- return import_path32.default.join(import_os25.default.homedir(), ".node9", "mcp-pins.json");
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 import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
13043
+ return import_crypto10.default.createHash("sha256").update(canonical).digest("hex");
12308
13044
  }
12309
13045
  function getServerKey(upstreamCommand) {
12310
- return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
13046
+ return import_crypto10.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
12311
13047
  }
12312
13048
  function readMcpPinsSafe() {
12313
- const filePath = getPinsFilePath();
13049
+ const filePath = getPinsFilePath2();
12314
13050
  try {
12315
- const raw = import_fs29.default.readFileSync(filePath, "utf-8");
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 = getPinsFilePath();
12339
- import_fs29.default.mkdirSync(import_path32.default.dirname(filePath), { recursive: true });
12340
- const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
12341
- import_fs29.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12342
- import_fs29.default.renameSync(tmp, filePath);
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 removePin(serverKey) {
13101
+ function removePin2(serverKey) {
12366
13102
  const pins = readMcpPins();
12367
13103
  delete pins.servers[serverKey];
12368
13104
  writeMcpPins(pins);
12369
13105
  }
12370
- function clearAllPins() {
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 import_fs30 = __toESM(require("fs"));
12715
- var import_os26 = __toESM(require("os"));
12716
- var import_path33 = __toESM(require("path"));
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 = import_path33.default.join(process.cwd(), "node9.config.json");
12892
- const globalConfig = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
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): ${import_fs30.default.existsSync(projectConfig) ? "present" : "not found"}`
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): ${import_fs30.default.existsSync(globalConfig) ? "present" : "not found"}`
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 = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
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 (import_fs30.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12976
- return JSON.parse(import_fs30.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
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 = import_path33.default.dirname(GLOBAL_CONFIG_PATH2);
12984
- if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
12985
- import_fs30.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
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 = import_path33.default.join(import_os26.default.homedir(), ".node9", "audit.log");
13029
- if (!import_fs30.default.existsSync(auditPath)) return "No audit log found.";
13030
- const lines = import_fs30.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
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
- removePin(serverKey);
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
- clearAllPins();
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 import_fs31 = __toESM(require("fs"));
13489
- var import_path34 = __toESM(require("path"));
13490
- var import_os27 = __toESM(require("os"));
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 = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
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 (!import_fs31.default.existsSync(projectsDir)) return result;
14310
+ if (!import_fs33.default.existsSync(projectsDir)) return result;
13575
14311
  let projDirs;
13576
14312
  try {
13577
- projDirs = import_fs31.default.readdirSync(projectsDir);
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 = import_path34.default.join(projectsDir, proj);
14319
+ const projPath = import_path36.default.join(projectsDir, proj);
13584
14320
  try {
13585
- if (!import_fs31.default.statSync(projPath).isDirectory()) continue;
14321
+ if (!import_fs33.default.statSync(projPath).isDirectory()) continue;
13586
14322
  } catch {
13587
14323
  continue;
13588
14324
  }
13589
- const projLabel = decodeURIComponent(proj).replace(import_os27.default.homedir(), "~").slice(0, 40);
14325
+ const projLabel = decodeURIComponent(proj).replace(import_os29.default.homedir(), "~").slice(0, 40);
13590
14326
  let files;
13591
14327
  try {
13592
- files = import_fs31.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
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 = import_fs31.default.readFileSync(import_path34.default.join(projPath, file), "utf-8");
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 = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13695
- if (!import_fs31.default.existsSync(projectsDir)) {
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 = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
13808
- if (import_fs31.default.existsSync(auditLog)) {
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 import_fs32 = __toESM(require("fs"));
13826
- var import_path35 = __toESM(require("path"));
13827
- var import_os28 = __toESM(require("os"));
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 import_path35.default.join(import_os28.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
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(import_os28.default.homedir(), "~");
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 ?? import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
14664
+ const aPath = auditPath ?? import_path37.default.join(import_os30.default.homedir(), ".node9", "audit.log");
13925
14665
  let raw;
13926
14666
  try {
13927
- raw = import_fs32.default.readFileSync(aPath, "utf-8");
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 ?? import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
14703
+ const hPath = historyPath ?? import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
13964
14704
  let historyRaw;
13965
14705
  try {
13966
- historyRaw = import_fs32.default.readFileSync(hPath, "utf-8");
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 = import_fs32.default.readFileSync(jsonlFile, "utf-8").split("\n");
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 = import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
14243
- if (!import_fs32.default.existsSync(historyPath)) {
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
- import_fs35.default.readFileSync(import_path38.default.join(__dirname, "../package.json"), "utf-8")
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 = import_path38.default.join(import_os31.default.homedir(), ".node9", "credentials.json");
14282
- if (!import_fs35.default.existsSync(import_path38.default.dirname(credPath)))
14283
- import_fs35.default.mkdirSync(import_path38.default.dirname(credPath), { recursive: true });
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 (import_fs35.default.existsSync(credPath)) {
14288
- const raw = JSON.parse(import_fs35.default.readFileSync(credPath, "utf-8"));
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
- import_fs35.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
15251
+ import_fs39.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14301
15252
  if (profileName === "default") {
14302
- const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
15253
+ const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
14303
15254
  let config = {};
14304
15255
  try {
14305
- if (import_fs35.default.existsSync(configPath))
14306
- config = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
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 (!import_fs35.default.existsSync(import_path38.default.dirname(configPath)))
14322
- import_fs35.default.mkdirSync(import_path38.default.dirname(configPath), { recursive: true });
14323
- import_fs35.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
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(import_chalk24.default.green(`\u2705 Profile "${profileName}" saved`));
14327
- console.log(import_chalk24.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
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(import_chalk24.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
14330
- console.log(import_chalk24.default.gray(` All decisions stay on this machine.`));
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(import_chalk24.default.green(`\u2705 Logged in \u2014 agent mode`));
14333
- console.log(import_chalk24.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
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("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
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
- import_chalk24.default.red(
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("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
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(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
14353
- console.log(" Usage: " + import_chalk24.default.white("node9 setup <target>") + "\n");
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(" " + import_chalk24.default.green("claude") + " \u2014 Claude Code (hook mode)");
14356
- console.log(" " + import_chalk24.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
14357
- console.log(" " + import_chalk24.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
14358
- console.log(" " + import_chalk24.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
14359
- console.log(" " + import_chalk24.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
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
- " " + import_chalk24.default.green("hud") + " \u2014 Claude Code security statusline\n"
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
- import_chalk24.default.red(
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("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument(
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
- import_chalk24.default.red(
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(import_chalk24.default.cyan(`
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(import_chalk24.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
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(import_chalk24.default.gray("\n Restart the agent for changes to take effect."));
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(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14412
- console.log(import_chalk24.default.bold("Stopping daemon..."));
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(import_chalk24.default.green(" \u2705 Daemon stopped"));
15385
+ console.log(import_chalk26.default.green(" \u2705 Daemon stopped"));
14416
15386
  } catch {
14417
- console.log(import_chalk24.default.blue(" \u2139\uFE0F Daemon was not running"));
15387
+ console.log(import_chalk26.default.blue(" \u2139\uFE0F Daemon was not running"));
14418
15388
  }
14419
- console.log(import_chalk24.default.bold("\nRemoving hooks..."));
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
- import_chalk24.default.red(
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 = import_path38.default.join(import_os31.default.homedir(), ".node9");
14441
- if (import_fs35.default.existsSync(node9Dir)) {
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
- import_fs35.default.rmSync(node9Dir, { recursive: true });
14448
- if (import_fs35.default.existsSync(node9Dir)) {
15418
+ import_fs39.default.rmSync(node9Dir, { recursive: true });
15419
+ if (import_fs39.default.existsSync(node9Dir)) {
14449
15420
  console.error(
14450
- import_chalk24.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
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(import_chalk24.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
15424
+ console.log(import_chalk26.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14454
15425
  }
14455
15426
  } else {
14456
- console.log(import_chalk24.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
15427
+ console.log(import_chalk26.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14457
15428
  }
14458
15429
  } else {
14459
- console.log(import_chalk24.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
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
- import_chalk24.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
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(import_chalk24.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
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(import_chalk24.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
14471
- console.log(import_chalk24.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
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(import_chalk24.default.red(`
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(import_chalk24.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
15466
+ console.log(import_chalk26.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14496
15467
  console.log("");
14497
- console.log(` ${import_chalk24.default.bold("Tool:")} ${import_chalk24.default.white(result.tool)}`);
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(` ${import_chalk24.default.bold("Input:")} ${import_chalk24.default.gray(preview2)}`);
15471
+ console.log(` ${import_chalk26.default.bold("Input:")} ${import_chalk26.default.gray(preview2)}`);
14501
15472
  }
14502
15473
  console.log("");
14503
- console.log(import_chalk24.default.bold("Config Sources (Waterfall):"));
15474
+ console.log(import_chalk26.default.bold("Config Sources (Waterfall):"));
14504
15475
  for (const tier of result.waterfall) {
14505
- const num3 = import_chalk24.default.gray(` ${tier.tier}.`);
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 = import_chalk24.default.gray(tier.note ?? "");
15480
+ statusStr = import_chalk26.default.gray(tier.note ?? "");
14510
15481
  } else if (tier.status === "active") {
14511
- const loc = tier.path ? import_chalk24.default.gray(tier.path) : "";
14512
- const note = tier.note ? import_chalk24.default.gray(`(${tier.note})`) : "";
14513
- statusStr = import_chalk24.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
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 = import_chalk24.default.gray("\u25CB " + (tier.note ?? "not found"));
15486
+ statusStr = import_chalk26.default.gray("\u25CB " + (tier.note ?? "not found"));
14516
15487
  }
14517
- console.log(`${num3} ${import_chalk24.default.white(label)} ${statusStr}`);
15488
+ console.log(`${num3} ${import_chalk26.default.white(label)} ${statusStr}`);
14518
15489
  }
14519
15490
  console.log("");
14520
- console.log(import_chalk24.default.bold("Policy Evaluation:"));
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 = import_chalk24.default.green(" \u2705");
14525
- else if (step.outcome === "review") icon = import_chalk24.default.red(" \u{1F534}");
14526
- else if (step.outcome === "skip") icon = import_chalk24.default.gray(" \u2500 ");
14527
- else icon = import_chalk24.default.gray(" \u25CB ");
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 ? import_chalk24.default.white.bold(name) : import_chalk24.default.white(name);
14530
- const detail = isFinal ? import_chalk24.default.white(step.detail) : import_chalk24.default.gray(step.detail);
14531
- const arrow = isFinal ? import_chalk24.default.yellow(" \u2190 STOP") : "";
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(import_chalk24.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk24.default.gray(" \u2014 no approval needed"));
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
- import_chalk24.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk24.default.gray(" \u2014 human approval required")
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(import_chalk24.default.gray(` Reason: ${result.blockedByLabel}`));
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(import_chalk24.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
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 = import_path38.default.join(import_os31.default.homedir(), ".node9", "hud-debug");
15561
+ const flagFile = import_path42.default.join(import_os35.default.homedir(), ".node9", "hud-debug");
14590
15562
  if (state === "on") {
14591
- import_fs35.default.mkdirSync(import_path38.default.dirname(flagFile), { recursive: true });
14592
- import_fs35.default.writeFileSync(flagFile, "");
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 (import_fs35.default.existsSync(flagFile)) import_fs35.default.unlinkSync(flagFile);
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
- import_chalk24.default.red(`
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(import_chalk24.default.yellow(`
15591
+ console.log(import_chalk26.default.yellow(`
14620
15592
  \u23F8 Node9 paused until ${expiresAt}`));
14621
- console.log(import_chalk24.default.gray(` All tool calls will be allowed without review.`));
14622
- console.log(import_chalk24.default.gray(` Run "node9 resume" to re-enable early.
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(import_chalk24.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
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(import_chalk24.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
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
- import_chalk24.default.yellow(`
15617
+ import_chalk26.default.yellow(`
14646
15618
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
14647
15619
  );
14648
- console.error(import_chalk24.default.white(`
15620
+ console.error(import_chalk26.default.white(`
14649
15621
  "${target}" uses its own hook system. Use:`));
14650
15622
  console.error(
14651
- import_chalk24.default.green(` node9 addto ${target} `) + import_chalk24.default.gray("# one-time setup")
15623
+ import_chalk26.default.green(` node9 addto ${target} `) + import_chalk26.default.gray("# one-time setup")
14652
15624
  );
14653
- console.error(import_chalk24.default.green(` ${target} `) + import_chalk24.default.gray("# run normally"));
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(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
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
- import_chalk24.default.red(`
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(import_chalk24.default.green("\n\u2705 Approved \u2014 running command...\n"));
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 = import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log");
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
- import_fs35.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
15682
+ import_fs39.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
14710
15683
  `);
14711
15684
  }
14712
15685
  process.exit(0);