@node9/proxy 1.9.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -57,6 +57,11 @@ __export(audit_exports, {
57
57
  import fs from "fs";
58
58
  import path from "path";
59
59
  import os from "os";
60
+ function isTestCall(toolName, args) {
61
+ if (toolName !== "Bash" && toolName !== "bash") return false;
62
+ const cmd = args?.command;
63
+ return typeof cmd === "string" && TEST_COMMAND_RE.test(cmd);
64
+ }
60
65
  function redactSecrets(text) {
61
66
  if (!text) return text;
62
67
  let redacted = text;
@@ -92,12 +97,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
92
97
  }
93
98
  function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
94
99
  const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
100
+ const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
95
101
  appendToLog(LOCAL_AUDIT_LOG, {
96
102
  ts: (/* @__PURE__ */ new Date()).toISOString(),
97
103
  tool: toolName,
98
104
  ...argsField,
99
105
  decision,
100
106
  checkedBy,
107
+ ...testRun,
101
108
  agent: meta?.agent,
102
109
  mcpServer: meta?.mcpServer,
103
110
  hostname: os.hostname()
@@ -110,13 +117,14 @@ function appendConfigAudit(entry) {
110
117
  hostname: os.hostname()
111
118
  });
112
119
  }
113
- var LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
120
+ var LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, TEST_COMMAND_RE;
114
121
  var init_audit = __esm({
115
122
  "src/audit/index.ts"() {
116
123
  "use strict";
117
124
  init_hasher();
118
125
  LOCAL_AUDIT_LOG = path.join(os.homedir(), ".node9", "audit.log");
119
126
  HOOK_DEBUG_LOG = path.join(os.homedir(), ".node9", "hook-debug.log");
127
+ TEST_COMMAND_RE = /(?:^|\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;
120
128
  }
121
129
  });
122
130
 
@@ -139,8 +147,8 @@ function sanitizeConfig(raw) {
139
147
  }
140
148
  }
141
149
  const lines = result.error.issues.map((issue) => {
142
- const path32 = issue.path.length > 0 ? issue.path.join(".") : "root";
143
- return ` \u2022 ${path32}: ${issue.message}`;
150
+ const path34 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
+ return ` \u2022 ${path34}: ${issue.message}`;
144
152
  });
145
153
  return {
146
154
  sanitized,
@@ -239,6 +247,11 @@ var init_config_schema = __esm({
239
247
  dlp: z.object({
240
248
  enabled: z.boolean().optional(),
241
249
  scanIgnoredTools: z.boolean().optional()
250
+ }).optional(),
251
+ loopDetection: z.object({
252
+ enabled: z.boolean().optional(),
253
+ threshold: z.number().min(2).optional(),
254
+ windowSeconds: z.number().min(10).optional()
242
255
  }).optional()
243
256
  }).optional(),
244
257
  environments: z.record(z.object({ requireApproval: z.boolean().optional() })).optional()
@@ -536,7 +549,8 @@ function getConfig(cwd) {
536
549
  onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
537
550
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
538
551
  },
539
- dlp: { ...DEFAULT_CONFIG.policy.dlp }
552
+ dlp: { ...DEFAULT_CONFIG.policy.dlp },
553
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
540
554
  };
541
555
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
542
556
  const applyLayer = (source) => {
@@ -575,6 +589,13 @@ function getConfig(cwd) {
575
589
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
576
590
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
577
591
  }
592
+ if (p.loopDetection) {
593
+ const ld = p.loopDetection;
594
+ if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
595
+ if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
596
+ if (ld.windowSeconds !== void 0)
597
+ mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
598
+ }
578
599
  const envs = source.environments || {};
579
600
  for (const [envName, envConfig] of Object.entries(envs)) {
580
601
  if (envConfig && typeof envConfig === "object") {
@@ -878,7 +899,8 @@ var init_config = __esm({
878
899
  description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
879
900
  }
880
901
  ],
881
- dlp: { enabled: true, scanIgnoredTools: true }
902
+ dlp: { enabled: true, scanIgnoredTools: true },
903
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
882
904
  },
883
905
  environments: {}
884
906
  };
@@ -1687,9 +1709,9 @@ function matchesPattern(text, patterns) {
1687
1709
  const withoutDotSlash = text.replace(/^\.\//, "");
1688
1710
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1689
1711
  }
1690
- function getNestedValue(obj, path32) {
1712
+ function getNestedValue(obj, path34) {
1691
1713
  if (!obj || typeof obj !== "object") return null;
1692
- return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
1714
+ return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
1693
1715
  }
1694
1716
  function shouldSnapshot(toolName, args, config) {
1695
1717
  if (!config.settings.enableUndo) return false;
@@ -3056,6 +3078,58 @@ var init_cloud = __esm({
3056
3078
  }
3057
3079
  });
3058
3080
 
3081
+ // src/loop-detector.ts
3082
+ import fs11 from "fs";
3083
+ import path14 from "path";
3084
+ import os10 from "os";
3085
+ import crypto2 from "crypto";
3086
+ function loopStateFile() {
3087
+ return path14.join(os10.homedir(), ".node9", "loop-state.json");
3088
+ }
3089
+ function computeArgsHash(args) {
3090
+ const str = JSON.stringify(args ?? "");
3091
+ return crypto2.createHash("sha256").update(str).digest("hex").slice(0, 16);
3092
+ }
3093
+ function readState() {
3094
+ try {
3095
+ if (!fs11.existsSync(loopStateFile())) return [];
3096
+ const raw = fs11.readFileSync(loopStateFile(), "utf-8");
3097
+ const parsed = JSON.parse(raw);
3098
+ if (!Array.isArray(parsed)) return [];
3099
+ return parsed;
3100
+ } catch {
3101
+ return [];
3102
+ }
3103
+ }
3104
+ function writeState(records) {
3105
+ const dir = path14.dirname(loopStateFile());
3106
+ if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
3107
+ const tmpPath = `${loopStateFile()}.${os10.hostname()}.${process.pid}.tmp`;
3108
+ fs11.writeFileSync(tmpPath, JSON.stringify(records));
3109
+ fs11.renameSync(tmpPath, loopStateFile());
3110
+ }
3111
+ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
3112
+ try {
3113
+ const hash = computeArgsHash(args);
3114
+ const now = Date.now();
3115
+ const cutoff = now - windowMs;
3116
+ const records = readState().filter((r) => r.ts >= cutoff);
3117
+ records.push({ t: tool, h: hash, ts: now });
3118
+ const count = records.filter((r) => r.t === tool && r.h === hash).length;
3119
+ writeState(records.slice(-MAX_RECORDS));
3120
+ return { looping: count >= threshold, count };
3121
+ } catch {
3122
+ return { looping: false, count: 0 };
3123
+ }
3124
+ }
3125
+ var MAX_RECORDS;
3126
+ var init_loop_detector = __esm({
3127
+ "src/loop-detector.ts"() {
3128
+ "use strict";
3129
+ MAX_RECORDS = 500;
3130
+ }
3131
+ });
3132
+
3059
3133
  // src/auth/orchestrator.ts
3060
3134
  import { randomUUID } from "crypto";
3061
3135
  function isWriteTool(toolName) {
@@ -3144,6 +3218,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3144
3218
  let explainableLabel = "Local Config";
3145
3219
  let policyMatchedField;
3146
3220
  let policyMatchedWord;
3221
+ let policyRuleDescription;
3147
3222
  let riskMetadata;
3148
3223
  let statefulRecoveryCommand;
3149
3224
  let localSmartRuleMatched = false;
@@ -3237,6 +3312,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3237
3312
  return { approved: true, checkedBy: "audit" };
3238
3313
  }
3239
3314
  if (!taintWarning && !isIgnoredTool(toolName)) {
3315
+ const ld = config.policy.loopDetection;
3316
+ if (ld.enabled) {
3317
+ const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
3318
+ if (loopResult.looping) {
3319
+ const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
3320
+ if (!isManual)
3321
+ appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
3322
+ return {
3323
+ approved: false,
3324
+ reason,
3325
+ blockedBy: "loop-detection",
3326
+ blockedByLabel: "\u{1F504} Loop Detected"
3327
+ };
3328
+ }
3329
+ }
3240
3330
  if (getActiveTrustSession(toolName)) {
3241
3331
  if (approvers.cloud && creds?.apiKey)
3242
3332
  await auditLocalAllow(toolName, args, "trust", creds, meta);
@@ -3292,6 +3382,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3292
3382
  policyMatchedField = policyResult.matchedField;
3293
3383
  policyMatchedWord = policyResult.matchedWord;
3294
3384
  if (policyResult.ruleName) localSmartRuleMatched = true;
3385
+ if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
3386
+ else if (policyResult.reason) policyRuleDescription = policyResult.reason;
3295
3387
  riskMetadata = computeRiskMetadata(
3296
3388
  args,
3297
3389
  policyResult.tier ?? 6,
@@ -3555,7 +3647,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
3555
3647
  hashAuditArgs
3556
3648
  );
3557
3649
  }
3558
- return finalResult;
3650
+ const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
3651
+ return enrichedResult;
3559
3652
  }
3560
3653
  var WRITE_TOOLS;
3561
3654
  var init_orchestrator = __esm({
@@ -3570,6 +3663,7 @@ var init_orchestrator = __esm({
3570
3663
  init_state();
3571
3664
  init_daemon();
3572
3665
  init_cloud();
3666
+ init_loop_detector();
3573
3667
  WRITE_TOOLS = /* @__PURE__ */ new Set([
3574
3668
  "write",
3575
3669
  "write_file",
@@ -5307,8 +5401,8 @@ var init_suggestion_tracker = __esm({
5307
5401
  });
5308
5402
 
5309
5403
  // src/daemon/taint-store.ts
5310
- import fs12 from "fs";
5311
- import path15 from "path";
5404
+ import fs13 from "fs";
5405
+ import path16 from "path";
5312
5406
  var DEFAULT_TTL_MS, TaintStore;
5313
5407
  var init_taint_store = __esm({
5314
5408
  "src/daemon/taint-store.ts"() {
@@ -5377,9 +5471,9 @@ var init_taint_store = __esm({
5377
5471
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5378
5472
  _resolve(filePath) {
5379
5473
  try {
5380
- return fs12.realpathSync.native(path15.resolve(filePath));
5474
+ return fs13.realpathSync.native(path16.resolve(filePath));
5381
5475
  } catch {
5382
- return path15.resolve(filePath);
5476
+ return path16.resolve(filePath);
5383
5477
  }
5384
5478
  }
5385
5479
  };
@@ -5496,15 +5590,15 @@ var init_session_history = __esm({
5496
5590
 
5497
5591
  // src/daemon/state.ts
5498
5592
  import net2 from "net";
5499
- import fs13 from "fs";
5500
- import path16 from "path";
5501
- import os11 from "os";
5593
+ import fs14 from "fs";
5594
+ import path17 from "path";
5595
+ import os12 from "os";
5502
5596
  import { spawn as spawn2 } from "child_process";
5503
5597
  import { randomUUID as randomUUID3 } from "crypto";
5504
5598
  function loadInsightCounts() {
5505
5599
  try {
5506
- if (!fs13.existsSync(INSIGHT_COUNTS_FILE)) return;
5507
- const data = JSON.parse(fs13.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5600
+ if (!fs14.existsSync(INSIGHT_COUNTS_FILE)) return;
5601
+ const data = JSON.parse(fs14.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5508
5602
  for (const [tool, count] of Object.entries(data)) {
5509
5603
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
5510
5604
  }
@@ -5543,23 +5637,23 @@ function markRejectionHandlerRegistered() {
5543
5637
  daemonRejectionHandlerRegistered = true;
5544
5638
  }
5545
5639
  function atomicWriteSync2(filePath, data, options) {
5546
- const dir = path16.dirname(filePath);
5547
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5640
+ const dir = path17.dirname(filePath);
5641
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
5548
5642
  const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
5549
5643
  try {
5550
- fs13.writeFileSync(tmpPath, data, options);
5644
+ fs14.writeFileSync(tmpPath, data, options);
5551
5645
  } catch (err2) {
5552
5646
  try {
5553
- fs13.unlinkSync(tmpPath);
5647
+ fs14.unlinkSync(tmpPath);
5554
5648
  } catch {
5555
5649
  }
5556
5650
  throw err2;
5557
5651
  }
5558
5652
  try {
5559
- fs13.renameSync(tmpPath, filePath);
5653
+ fs14.renameSync(tmpPath, filePath);
5560
5654
  } catch (err2) {
5561
5655
  try {
5562
- fs13.unlinkSync(tmpPath);
5656
+ fs14.unlinkSync(tmpPath);
5563
5657
  } catch {
5564
5658
  }
5565
5659
  throw err2;
@@ -5583,16 +5677,16 @@ function appendAuditLog(data) {
5583
5677
  decision: data.decision,
5584
5678
  source: "daemon"
5585
5679
  };
5586
- const dir = path16.dirname(AUDIT_LOG_FILE);
5587
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5588
- fs13.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5680
+ const dir = path17.dirname(AUDIT_LOG_FILE);
5681
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
5682
+ fs14.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5589
5683
  } catch {
5590
5684
  }
5591
5685
  }
5592
5686
  function getAuditHistory(limit = 20) {
5593
5687
  try {
5594
- if (!fs13.existsSync(AUDIT_LOG_FILE)) return [];
5595
- const lines = fs13.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5688
+ if (!fs14.existsSync(AUDIT_LOG_FILE)) return [];
5689
+ const lines = fs14.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5596
5690
  if (lines.length === 1 && lines[0] === "") return [];
5597
5691
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
5598
5692
  } catch {
@@ -5601,19 +5695,19 @@ function getAuditHistory(limit = 20) {
5601
5695
  }
5602
5696
  function getOrgName() {
5603
5697
  try {
5604
- if (fs13.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5698
+ if (fs14.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5605
5699
  } catch {
5606
5700
  }
5607
5701
  return null;
5608
5702
  }
5609
5703
  function hasStoredSlackKey() {
5610
- return fs13.existsSync(CREDENTIALS_FILE);
5704
+ return fs14.existsSync(CREDENTIALS_FILE);
5611
5705
  }
5612
5706
  function writeGlobalSetting(key, value) {
5613
5707
  let config = {};
5614
5708
  try {
5615
- if (fs13.existsSync(GLOBAL_CONFIG_FILE)) {
5616
- config = JSON.parse(fs13.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5709
+ if (fs14.existsSync(GLOBAL_CONFIG_FILE)) {
5710
+ config = JSON.parse(fs14.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5617
5711
  }
5618
5712
  } catch {
5619
5713
  }
@@ -5625,8 +5719,8 @@ function writeTrustEntry(toolName, durationMs) {
5625
5719
  try {
5626
5720
  let trust = { entries: [] };
5627
5721
  try {
5628
- if (fs13.existsSync(TRUST_FILE2))
5629
- trust = JSON.parse(fs13.readFileSync(TRUST_FILE2, "utf-8"));
5722
+ if (fs14.existsSync(TRUST_FILE2))
5723
+ trust = JSON.parse(fs14.readFileSync(TRUST_FILE2, "utf-8"));
5630
5724
  } catch {
5631
5725
  }
5632
5726
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -5637,8 +5731,8 @@ function writeTrustEntry(toolName, durationMs) {
5637
5731
  }
5638
5732
  function readPersistentDecisions() {
5639
5733
  try {
5640
- if (fs13.existsSync(DECISIONS_FILE)) {
5641
- return JSON.parse(fs13.readFileSync(DECISIONS_FILE, "utf-8"));
5734
+ if (fs14.existsSync(DECISIONS_FILE)) {
5735
+ return JSON.parse(fs14.readFileSync(DECISIONS_FILE, "utf-8"));
5642
5736
  }
5643
5737
  } catch {
5644
5738
  }
@@ -5675,7 +5769,7 @@ function estimateToolCost(tool, args) {
5675
5769
  const filePath = a.file_path ?? a.path;
5676
5770
  if (filePath) {
5677
5771
  try {
5678
- const bytes = fs13.statSync(filePath).size;
5772
+ const bytes = fs14.statSync(filePath).size;
5679
5773
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
5680
5774
  } catch {
5681
5775
  }
@@ -5733,7 +5827,7 @@ function abandonPending() {
5733
5827
  });
5734
5828
  if (autoStarted) {
5735
5829
  try {
5736
- fs13.unlinkSync(DAEMON_PID_FILE);
5830
+ fs14.unlinkSync(DAEMON_PID_FILE);
5737
5831
  } catch {
5738
5832
  }
5739
5833
  setTimeout(() => {
@@ -5744,7 +5838,7 @@ function abandonPending() {
5744
5838
  }
5745
5839
  function startActivitySocket() {
5746
5840
  try {
5747
- fs13.unlinkSync(ACTIVITY_SOCKET_PATH2);
5841
+ fs14.unlinkSync(ACTIVITY_SOCKET_PATH2);
5748
5842
  } catch {
5749
5843
  }
5750
5844
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -5825,7 +5919,7 @@ function startActivitySocket() {
5825
5919
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
5826
5920
  process.on("exit", () => {
5827
5921
  try {
5828
- fs13.unlinkSync(ACTIVITY_SOCKET_PATH2);
5922
+ fs14.unlinkSync(ACTIVITY_SOCKET_PATH2);
5829
5923
  } catch {
5830
5924
  }
5831
5925
  });
@@ -5839,14 +5933,14 @@ var init_state2 = __esm({
5839
5933
  init_taint_store();
5840
5934
  init_session_counters();
5841
5935
  init_session_history();
5842
- homeDir = os11.homedir();
5843
- DAEMON_PID_FILE = path16.join(homeDir, ".node9", "daemon.pid");
5844
- DECISIONS_FILE = path16.join(homeDir, ".node9", "decisions.json");
5845
- AUDIT_LOG_FILE = path16.join(homeDir, ".node9", "audit.log");
5846
- TRUST_FILE2 = path16.join(homeDir, ".node9", "trust.json");
5847
- GLOBAL_CONFIG_FILE = path16.join(homeDir, ".node9", "config.json");
5848
- CREDENTIALS_FILE = path16.join(homeDir, ".node9", "credentials.json");
5849
- INSIGHT_COUNTS_FILE = path16.join(homeDir, ".node9", "insight-counts.json");
5936
+ homeDir = os12.homedir();
5937
+ DAEMON_PID_FILE = path17.join(homeDir, ".node9", "daemon.pid");
5938
+ DECISIONS_FILE = path17.join(homeDir, ".node9", "decisions.json");
5939
+ AUDIT_LOG_FILE = path17.join(homeDir, ".node9", "audit.log");
5940
+ TRUST_FILE2 = path17.join(homeDir, ".node9", "trust.json");
5941
+ GLOBAL_CONFIG_FILE = path17.join(homeDir, ".node9", "config.json");
5942
+ CREDENTIALS_FILE = path17.join(homeDir, ".node9", "credentials.json");
5943
+ INSIGHT_COUNTS_FILE = path17.join(homeDir, ".node9", "insight-counts.json");
5850
5944
  pending = /* @__PURE__ */ new Map();
5851
5945
  sseClients = /* @__PURE__ */ new Set();
5852
5946
  suggestionTracker = new SuggestionTracker(3);
@@ -5864,7 +5958,7 @@ var init_state2 = __esm({
5864
5958
  "2h": 2 * 60 * 6e4
5865
5959
  };
5866
5960
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5867
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path16.join(os11.tmpdir(), "node9-activity.sock");
5961
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path17.join(os12.tmpdir(), "node9-activity.sock");
5868
5962
  ACTIVITY_RING_SIZE = 100;
5869
5963
  activityRing = [];
5870
5964
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5886,14 +5980,14 @@ var init_state2 = __esm({
5886
5980
  });
5887
5981
 
5888
5982
  // src/config/patch.ts
5889
- import fs14 from "fs";
5890
- import path17 from "path";
5891
- import os12 from "os";
5983
+ import fs15 from "fs";
5984
+ import path18 from "path";
5985
+ import os13 from "os";
5892
5986
  function patchConfig(configPath, patch) {
5893
5987
  let config = {};
5894
5988
  try {
5895
- if (fs14.existsSync(configPath)) {
5896
- config = JSON.parse(fs14.readFileSync(configPath, "utf8"));
5989
+ if (fs15.existsSync(configPath)) {
5990
+ config = JSON.parse(fs15.readFileSync(configPath, "utf8"));
5897
5991
  }
5898
5992
  } catch {
5899
5993
  throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
@@ -5912,23 +6006,23 @@ function patchConfig(configPath, patch) {
5912
6006
  ignored.push(patch.toolName);
5913
6007
  }
5914
6008
  }
5915
- const dir = path17.dirname(configPath);
5916
- fs14.mkdirSync(dir, { recursive: true });
6009
+ const dir = path18.dirname(configPath);
6010
+ fs15.mkdirSync(dir, { recursive: true });
5917
6011
  const tmp = configPath + ".node9-tmp";
5918
6012
  try {
5919
- fs14.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
6013
+ fs15.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
5920
6014
  } catch (err2) {
5921
6015
  try {
5922
- fs14.unlinkSync(tmp);
6016
+ fs15.unlinkSync(tmp);
5923
6017
  } catch {
5924
6018
  }
5925
6019
  throw err2;
5926
6020
  }
5927
6021
  try {
5928
- fs14.renameSync(tmp, configPath);
6022
+ fs15.renameSync(tmp, configPath);
5929
6023
  } catch (err2) {
5930
6024
  try {
5931
- fs14.unlinkSync(tmp);
6025
+ fs15.unlinkSync(tmp);
5932
6026
  } catch {
5933
6027
  }
5934
6028
  throw err2;
@@ -5938,14 +6032,14 @@ var GLOBAL_CONFIG_PATH;
5938
6032
  var init_patch = __esm({
5939
6033
  "src/config/patch.ts"() {
5940
6034
  "use strict";
5941
- GLOBAL_CONFIG_PATH = path17.join(os12.homedir(), ".node9", "config.json");
6035
+ GLOBAL_CONFIG_PATH = path18.join(os13.homedir(), ".node9", "config.json");
5942
6036
  }
5943
6037
  });
5944
6038
 
5945
6039
  // src/daemon/server.ts
5946
6040
  import http from "http";
5947
- import fs15 from "fs";
5948
- import path18 from "path";
6041
+ import fs16 from "fs";
6042
+ import path19 from "path";
5949
6043
  import { randomUUID as randomUUID4 } from "crypto";
5950
6044
  import { spawnSync as spawnSync2 } from "child_process";
5951
6045
  import chalk2 from "chalk";
@@ -5965,7 +6059,7 @@ function startDaemon() {
5965
6059
  idleTimer = setTimeout(() => {
5966
6060
  if (autoStarted) {
5967
6061
  try {
5968
- fs15.unlinkSync(DAEMON_PID_FILE);
6062
+ fs16.unlinkSync(DAEMON_PID_FILE);
5969
6063
  } catch {
5970
6064
  }
5971
6065
  }
@@ -6128,7 +6222,7 @@ data: ${JSON.stringify(item.data)}
6128
6222
  status: "pending"
6129
6223
  });
6130
6224
  }
6131
- const projectCwd = typeof cwd === "string" && path18.isAbsolute(cwd) ? cwd : void 0;
6225
+ const projectCwd = typeof cwd === "string" && path19.isAbsolute(cwd) ? cwd : void 0;
6132
6226
  const projectConfig = getConfig(projectCwd);
6133
6227
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6134
6228
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6518,8 +6612,8 @@ data: ${JSON.stringify(item.data)}
6518
6612
  const body = await readBody(req);
6519
6613
  const data = body ? JSON.parse(body) : {};
6520
6614
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6521
- const node9Dir = path18.dirname(GLOBAL_CONFIG_PATH);
6522
- if (!path18.resolve(configPath).startsWith(node9Dir + path18.sep)) {
6615
+ const node9Dir = path19.dirname(GLOBAL_CONFIG_PATH);
6616
+ if (!path19.resolve(configPath).startsWith(node9Dir + path19.sep)) {
6523
6617
  res.writeHead(400, { "Content-Type": "application/json" });
6524
6618
  return res.end(
6525
6619
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6630,14 +6724,14 @@ data: ${JSON.stringify(item.data)}
6630
6724
  server.on("error", (e) => {
6631
6725
  if (e.code === "EADDRINUSE") {
6632
6726
  try {
6633
- if (fs15.existsSync(DAEMON_PID_FILE)) {
6634
- const { pid } = JSON.parse(fs15.readFileSync(DAEMON_PID_FILE, "utf-8"));
6727
+ if (fs16.existsSync(DAEMON_PID_FILE)) {
6728
+ const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6635
6729
  process.kill(pid, 0);
6636
6730
  return process.exit(0);
6637
6731
  }
6638
6732
  } catch {
6639
6733
  try {
6640
- fs15.unlinkSync(DAEMON_PID_FILE);
6734
+ fs16.unlinkSync(DAEMON_PID_FILE);
6641
6735
  } catch {
6642
6736
  }
6643
6737
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6709,28 +6803,28 @@ var init_server = __esm({
6709
6803
  });
6710
6804
 
6711
6805
  // src/daemon/index.ts
6712
- import fs16 from "fs";
6806
+ import fs17 from "fs";
6713
6807
  import chalk3 from "chalk";
6714
6808
  import { spawnSync as spawnSync3 } from "child_process";
6715
6809
  function stopDaemon() {
6716
- if (!fs16.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6810
+ if (!fs17.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6717
6811
  try {
6718
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6812
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6719
6813
  process.kill(pid, "SIGTERM");
6720
6814
  console.log(chalk3.green("\u2705 Stopped."));
6721
6815
  } catch {
6722
6816
  console.log(chalk3.gray("Cleaned up stale PID file."));
6723
6817
  } finally {
6724
6818
  try {
6725
- fs16.unlinkSync(DAEMON_PID_FILE);
6819
+ fs17.unlinkSync(DAEMON_PID_FILE);
6726
6820
  } catch {
6727
6821
  }
6728
6822
  }
6729
6823
  }
6730
6824
  function daemonStatus() {
6731
- if (fs16.existsSync(DAEMON_PID_FILE)) {
6825
+ if (fs17.existsSync(DAEMON_PID_FILE)) {
6732
6826
  try {
6733
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6827
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6734
6828
  process.kill(pid, 0);
6735
6829
  console.log(chalk3.green("Node9 daemon: running"));
6736
6830
  return;
@@ -6764,10 +6858,10 @@ __export(tail_exports, {
6764
6858
  startTail: () => startTail
6765
6859
  });
6766
6860
  import http2 from "http";
6767
- import chalk18 from "chalk";
6768
- import fs26 from "fs";
6769
- import os22 from "os";
6770
- import path29 from "path";
6861
+ import chalk19 from "chalk";
6862
+ import fs28 from "fs";
6863
+ import os24 from "os";
6864
+ import path31 from "path";
6771
6865
  import readline5 from "readline";
6772
6866
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
6773
6867
  function getIcon(tool) {
@@ -6777,44 +6871,64 @@ function getIcon(tool) {
6777
6871
  }
6778
6872
  return "\u{1F6E0}\uFE0F";
6779
6873
  }
6874
+ function visibleLength(s) {
6875
+ return s.replace(/\x1B\[[0-9;]*m/g, "").length;
6876
+ }
6877
+ function wrappedLineCount(text) {
6878
+ const cols = process.stdout.columns;
6879
+ if (!cols) return 1;
6880
+ const len = visibleLength(text);
6881
+ return Math.max(1, Math.ceil(len / cols));
6882
+ }
6780
6883
  function formatBase(activity) {
6781
6884
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6782
6885
  const icon = getIcon(activity.tool);
6783
6886
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6784
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os22.homedir(), "~");
6887
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os24.homedir(), "~");
6785
6888
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6786
- return `${chalk18.gray(time)} ${icon} ${chalk18.white.bold(toolName)} ${chalk18.dim(argsPreview)}`;
6889
+ return `${chalk19.gray(time)} ${icon} ${chalk19.white.bold(toolName)} ${chalk19.dim(argsPreview)}`;
6787
6890
  }
6788
6891
  function renderResult(activity, result) {
6789
6892
  const base = formatBase(activity);
6790
6893
  let status;
6791
6894
  if (result.status === "allow") {
6792
- status = chalk18.green("\u2713 ALLOW");
6895
+ status = chalk19.green("\u2713 ALLOW");
6793
6896
  } else if (result.status === "dlp") {
6794
- status = chalk18.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6897
+ status = chalk19.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6795
6898
  } else {
6796
- status = chalk18.red("\u2717 BLOCK");
6899
+ status = chalk19.red("\u2717 BLOCK");
6797
6900
  }
6798
6901
  const cost = result.costEstimate ?? activity.costEstimate;
6799
- const costSuffix = cost == null ? "" : chalk18.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
6902
+ const costSuffix = cost == null ? "" : chalk19.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
6800
6903
  if (process.stdout.isTTY) {
6801
- readline5.clearLine(process.stdout, 0);
6802
- readline5.cursorTo(process.stdout, 0);
6904
+ if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
6905
+ readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
6906
+ readline5.cursorTo(process.stdout, 0);
6907
+ process.stdout.write(ERASE_DOWN);
6908
+ } else {
6909
+ readline5.clearLine(process.stdout, 0);
6910
+ readline5.cursorTo(process.stdout, 0);
6911
+ }
6912
+ pendingShownForId = null;
6913
+ pendingWrappedLines = 0;
6803
6914
  }
6804
6915
  console.log(`${base} ${status}${costSuffix}`);
6805
6916
  }
6806
6917
  function renderPending(activity) {
6807
6918
  if (!process.stdout.isTTY) return;
6808
- process.stdout.write(`${formatBase(activity)} ${chalk18.yellow("\u25CF \u2026")}\r`);
6919
+ const line = `${formatBase(activity)} ${chalk19.yellow("\u25CF \u2026")}`;
6920
+ pendingShownForId = activity.id;
6921
+ pendingWrappedLines = wrappedLineCount(line);
6922
+ process.stdout.write(`${line}\r`);
6809
6923
  }
6810
6924
  async function ensureDaemon() {
6811
6925
  let pidPort = null;
6812
- if (fs26.existsSync(PID_FILE)) {
6926
+ if (fs28.existsSync(PID_FILE)) {
6813
6927
  try {
6814
- const { port } = JSON.parse(fs26.readFileSync(PID_FILE, "utf-8"));
6928
+ const { port } = JSON.parse(fs28.readFileSync(PID_FILE, "utf-8"));
6815
6929
  pidPort = port;
6816
6930
  } catch {
6817
- console.error(chalk18.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6931
+ console.error(chalk19.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6818
6932
  }
6819
6933
  }
6820
6934
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6825,7 +6939,7 @@ async function ensureDaemon() {
6825
6939
  if (res.ok) return checkPort;
6826
6940
  } catch {
6827
6941
  }
6828
- console.log(chalk18.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6942
+ console.log(chalk19.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6829
6943
  const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
6830
6944
  detached: true,
6831
6945
  stdio: "ignore",
@@ -6842,7 +6956,7 @@ async function ensureDaemon() {
6842
6956
  } catch {
6843
6957
  }
6844
6958
  }
6845
- console.error(chalk18.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6959
+ console.error(chalk19.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6846
6960
  process.exit(1);
6847
6961
  }
6848
6962
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6931,9 +7045,9 @@ function buildRecoveryCardLines(req) {
6931
7045
  ];
6932
7046
  }
6933
7047
  function readApproversFromDisk() {
6934
- const configPath = path29.join(os22.homedir(), ".node9", "config.json");
7048
+ const configPath = path31.join(os24.homedir(), ".node9", "config.json");
6935
7049
  try {
6936
- const raw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
7050
+ const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
6937
7051
  const settings = raw.settings ?? {};
6938
7052
  return settings.approvers ?? {};
6939
7053
  } catch {
@@ -6944,20 +7058,20 @@ function approverStatusLine() {
6944
7058
  const a = readApproversFromDisk();
6945
7059
  const fmt = (label, key) => {
6946
7060
  const on = a[key] !== false;
6947
- return `[${key[0]}]${label.slice(1)} ${on ? chalk18.green("\u2713") : chalk18.dim("\u2717")}`;
7061
+ return `[${key[0]}]${label.slice(1)} ${on ? chalk19.green("\u2713") : chalk19.dim("\u2717")}`;
6948
7062
  };
6949
7063
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
6950
7064
  }
6951
7065
  function toggleApprover(channel) {
6952
- const configPath = path29.join(os22.homedir(), ".node9", "config.json");
7066
+ const configPath = path31.join(os24.homedir(), ".node9", "config.json");
6953
7067
  try {
6954
- const raw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
7068
+ const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
6955
7069
  const settings = raw.settings ?? {};
6956
7070
  const approvers = settings.approvers ?? {};
6957
7071
  approvers[channel] = approvers[channel] === false;
6958
7072
  settings.approvers = approvers;
6959
7073
  raw.settings = settings;
6960
- fs26.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7074
+ fs28.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
6961
7075
  } catch (err2) {
6962
7076
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
6963
7077
  `);
@@ -6989,7 +7103,7 @@ async function startTail(options = {}) {
6989
7103
  req2.end();
6990
7104
  });
6991
7105
  if (result.ok) {
6992
- console.log(chalk18.green("\u2713 Flight Recorder buffer cleared."));
7106
+ console.log(chalk19.green("\u2713 Flight Recorder buffer cleared."));
6993
7107
  } else if (result.code === "ECONNREFUSED") {
6994
7108
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
6995
7109
  } else if (result.code === "ETIMEDOUT") {
@@ -7033,7 +7147,7 @@ async function startTail(options = {}) {
7033
7147
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7034
7148
  if (channel) {
7035
7149
  toggleApprover(channel);
7036
- console.log(chalk18.dim(` Approvers: ${approverStatusLine()}`));
7150
+ console.log(chalk19.dim(` Approvers: ${approverStatusLine()}`));
7037
7151
  }
7038
7152
  };
7039
7153
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7099,7 +7213,7 @@ async function startTail(options = {}) {
7099
7213
  localAllowCounts.get(req2.toolName) ?? 0
7100
7214
  )
7101
7215
  );
7102
- const decisionStamp = action === "always-allow" ? chalk18.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk18.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk18.green("\u2713 ALLOWED") : action === "redirect" ? chalk18.yellow("\u21A9 REDIRECT AI") : chalk18.red("\u2717 DENIED");
7216
+ const decisionStamp = action === "always-allow" ? chalk19.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk19.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk19.green("\u2713 ALLOWED") : action === "redirect" ? chalk19.yellow("\u21A9 REDIRECT AI") : chalk19.red("\u2717 DENIED");
7103
7217
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7104
7218
  for (const line of stampedLines) process.stdout.write(line + "\n");
7105
7219
  process.stdout.write(SHOW_CURSOR);
@@ -7127,8 +7241,8 @@ async function startTail(options = {}) {
7127
7241
  }
7128
7242
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7129
7243
  try {
7130
- fs26.appendFileSync(
7131
- path29.join(os22.homedir(), ".node9", "hook-debug.log"),
7244
+ fs28.appendFileSync(
7245
+ path31.join(os24.homedir(), ".node9", "hook-debug.log"),
7132
7246
  `[tail] POST /decision failed: ${String(err2)}
7133
7247
  `
7134
7248
  );
@@ -7150,7 +7264,7 @@ async function startTail(options = {}) {
7150
7264
  );
7151
7265
  const stampedLines = buildCardLines(req2, priorCount);
7152
7266
  if (externalDecision) {
7153
- const source = externalDecision === "allow" ? chalk18.green("\u2713 ALLOWED") : chalk18.red("\u2717 DENIED");
7267
+ const source = externalDecision === "allow" ? chalk19.green("\u2713 ALLOWED") : chalk19.red("\u2717 DENIED");
7154
7268
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7155
7269
  }
7156
7270
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7209,16 +7323,16 @@ async function startTail(options = {}) {
7209
7323
  }
7210
7324
  } catch {
7211
7325
  }
7212
- console.log(chalk18.cyan.bold(`
7213
- \u{1F6F0}\uFE0F Node9 tail `) + chalk18.dim(`\u2192 ${dashboardUrl}`));
7326
+ console.log(chalk19.cyan.bold(`
7327
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk19.dim(`\u2192 ${dashboardUrl}`));
7214
7328
  if (canApprove) {
7215
- console.log(chalk18.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7216
- console.log(chalk18.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7329
+ console.log(chalk19.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7330
+ console.log(chalk19.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7217
7331
  }
7218
7332
  if (options.history) {
7219
- console.log(chalk18.dim("Showing history + live events.\n"));
7333
+ console.log(chalk19.dim("Showing history + live events.\n"));
7220
7334
  } else {
7221
- console.log(chalk18.dim("Showing live events only. Use --history to include past.\n"));
7335
+ console.log(chalk19.dim("Showing live events only. Use --history to include past.\n"));
7222
7336
  }
7223
7337
  process.on("SIGINT", () => {
7224
7338
  exitIdleMode();
@@ -7228,13 +7342,13 @@ async function startTail(options = {}) {
7228
7342
  readline5.clearLine(process.stdout, 0);
7229
7343
  readline5.cursorTo(process.stdout, 0);
7230
7344
  }
7231
- console.log(chalk18.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7345
+ console.log(chalk19.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7232
7346
  process.exit(0);
7233
7347
  });
7234
7348
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7235
7349
  const req = http2.get(sseUrl, (res) => {
7236
7350
  if (res.statusCode !== 200) {
7237
- console.error(chalk18.red(`Failed to connect: HTTP ${res.statusCode}`));
7351
+ console.error(chalk19.red(`Failed to connect: HTTP ${res.statusCode}`));
7238
7352
  process.exit(1);
7239
7353
  }
7240
7354
  if (canApprove) enterIdleMode();
@@ -7265,7 +7379,7 @@ async function startTail(options = {}) {
7265
7379
  readline5.clearLine(process.stdout, 0);
7266
7380
  readline5.cursorTo(process.stdout, 0);
7267
7381
  }
7268
- console.log(chalk18.red("\n\u274C Daemon disconnected."));
7382
+ console.log(chalk19.red("\n\u274C Daemon disconnected."));
7269
7383
  process.exit(1);
7270
7384
  });
7271
7385
  });
@@ -7357,9 +7471,9 @@ async function startTail(options = {}) {
7357
7471
  const hash = data.hash ?? "";
7358
7472
  const summary = data.argsSummary ?? data.tool;
7359
7473
  const fileCount = data.fileCount ?? 0;
7360
- const files = fileCount > 0 ? chalk18.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7474
+ const files = fileCount > 0 ? chalk19.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7361
7475
  process.stdout.write(
7362
- `${chalk18.dim(time)} ${chalk18.cyan("\u{1F4F8} snapshot")} ${chalk18.dim(hash)} ${summary}${files}
7476
+ `${chalk19.dim(time)} ${chalk19.cyan("\u{1F4F8} snapshot")} ${chalk19.dim(hash)} ${summary}${files}
7363
7477
  `
7364
7478
  );
7365
7479
  return;
@@ -7376,19 +7490,19 @@ async function startTail(options = {}) {
7376
7490
  }
7377
7491
  req.on("error", (err2) => {
7378
7492
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7379
- console.error(chalk18.red(`
7493
+ console.error(chalk19.red(`
7380
7494
  \u274C ${msg}`));
7381
7495
  process.exit(1);
7382
7496
  });
7383
7497
  }
7384
- var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7498
+ var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7385
7499
  var init_tail = __esm({
7386
7500
  "src/tui/tail.ts"() {
7387
7501
  "use strict";
7388
7502
  init_daemon2();
7389
7503
  init_daemon();
7390
7504
  init_core();
7391
- PID_FILE = path29.join(os22.homedir(), ".node9", "daemon.pid");
7505
+ PID_FILE = path31.join(os24.homedir(), ".node9", "daemon.pid");
7392
7506
  ICONS = {
7393
7507
  bash: "\u{1F4BB}",
7394
7508
  shell: "\u{1F4BB}",
@@ -7416,6 +7530,8 @@ var init_tail = __esm({
7416
7530
  HIDE_CURSOR = "\x1B[?25l";
7417
7531
  SHOW_CURSOR = "\x1B[?25h";
7418
7532
  ERASE_DOWN = "\x1B[J";
7533
+ pendingShownForId = null;
7534
+ pendingWrappedLines = 0;
7419
7535
  DIVIDER = "\u2500".repeat(60);
7420
7536
  }
7421
7537
  });
@@ -7427,9 +7543,9 @@ __export(hud_exports, {
7427
7543
  main: () => main,
7428
7544
  renderEnvironmentLine: () => renderEnvironmentLine
7429
7545
  });
7430
- import fs27 from "fs";
7431
- import path30 from "path";
7432
- import os23 from "os";
7546
+ import fs29 from "fs";
7547
+ import path32 from "path";
7548
+ import os25 from "os";
7433
7549
  import http3 from "http";
7434
7550
  async function readStdin() {
7435
7551
  const chunks = [];
@@ -7488,10 +7604,10 @@ function bold(s) {
7488
7604
  function color(c, s) {
7489
7605
  return `${c}${s}${RESET3}`;
7490
7606
  }
7491
- function progressBar(pct, warnAt = 70, critAt = 85) {
7492
- const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
7607
+ function progressBar(pct2, warnAt = 70, critAt = 85) {
7608
+ const filled = Math.round(Math.min(pct2, 100) / 100 * BAR_WIDTH);
7493
7609
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7494
- const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7610
+ const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
7495
7611
  return `${c}${bar}${RESET3}`;
7496
7612
  }
7497
7613
  function formatTimeLeft(resetsAt) {
@@ -7505,9 +7621,9 @@ function formatTimeLeft(resetsAt) {
7505
7621
  return ` (${m}m left)`;
7506
7622
  }
7507
7623
  function safeReadJson(filePath) {
7508
- if (!fs27.existsSync(filePath)) return null;
7624
+ if (!fs29.existsSync(filePath)) return null;
7509
7625
  try {
7510
- return JSON.parse(fs27.readFileSync(filePath, "utf-8"));
7626
+ return JSON.parse(fs29.readFileSync(filePath, "utf-8"));
7511
7627
  } catch {
7512
7628
  return null;
7513
7629
  }
@@ -7528,12 +7644,12 @@ function countHooksInFile(filePath) {
7528
7644
  return Object.keys(cfg.hooks).length;
7529
7645
  }
7530
7646
  function countRulesInDir(rulesDir) {
7531
- if (!fs27.existsSync(rulesDir)) return 0;
7647
+ if (!fs29.existsSync(rulesDir)) return 0;
7532
7648
  let count = 0;
7533
7649
  try {
7534
- for (const entry of fs27.readdirSync(rulesDir, { withFileTypes: true })) {
7650
+ for (const entry of fs29.readdirSync(rulesDir, { withFileTypes: true })) {
7535
7651
  if (entry.isDirectory()) {
7536
- count += countRulesInDir(path30.join(rulesDir, entry.name));
7652
+ count += countRulesInDir(path32.join(rulesDir, entry.name));
7537
7653
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7538
7654
  count++;
7539
7655
  }
@@ -7544,46 +7660,46 @@ function countRulesInDir(rulesDir) {
7544
7660
  }
7545
7661
  function isSamePath(a, b) {
7546
7662
  try {
7547
- return path30.resolve(a) === path30.resolve(b);
7663
+ return path32.resolve(a) === path32.resolve(b);
7548
7664
  } catch {
7549
7665
  return false;
7550
7666
  }
7551
7667
  }
7552
7668
  function countConfigs(cwd) {
7553
- const homeDir2 = os23.homedir();
7554
- const claudeDir = path30.join(homeDir2, ".claude");
7669
+ const homeDir2 = os25.homedir();
7670
+ const claudeDir = path32.join(homeDir2, ".claude");
7555
7671
  let claudeMdCount = 0;
7556
7672
  let rulesCount = 0;
7557
7673
  let hooksCount = 0;
7558
7674
  const userMcpServers = /* @__PURE__ */ new Set();
7559
7675
  const projectMcpServers = /* @__PURE__ */ new Set();
7560
- if (fs27.existsSync(path30.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7561
- rulesCount += countRulesInDir(path30.join(claudeDir, "rules"));
7562
- const userSettings = path30.join(claudeDir, "settings.json");
7676
+ if (fs29.existsSync(path32.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7677
+ rulesCount += countRulesInDir(path32.join(claudeDir, "rules"));
7678
+ const userSettings = path32.join(claudeDir, "settings.json");
7563
7679
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7564
7680
  hooksCount += countHooksInFile(userSettings);
7565
- const userClaudeJson = path30.join(homeDir2, ".claude.json");
7681
+ const userClaudeJson = path32.join(homeDir2, ".claude.json");
7566
7682
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7567
7683
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7568
7684
  userMcpServers.delete(name);
7569
7685
  }
7570
7686
  if (cwd) {
7571
- if (fs27.existsSync(path30.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7572
- if (fs27.existsSync(path30.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7573
- const projectClaudeDir = path30.join(cwd, ".claude");
7687
+ if (fs29.existsSync(path32.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7688
+ if (fs29.existsSync(path32.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7689
+ const projectClaudeDir = path32.join(cwd, ".claude");
7574
7690
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7575
7691
  if (!overlapsUserScope) {
7576
- if (fs27.existsSync(path30.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7577
- rulesCount += countRulesInDir(path30.join(projectClaudeDir, "rules"));
7578
- const projSettings = path30.join(projectClaudeDir, "settings.json");
7692
+ if (fs29.existsSync(path32.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7693
+ rulesCount += countRulesInDir(path32.join(projectClaudeDir, "rules"));
7694
+ const projSettings = path32.join(projectClaudeDir, "settings.json");
7579
7695
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7580
7696
  hooksCount += countHooksInFile(projSettings);
7581
7697
  }
7582
- if (fs27.existsSync(path30.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7583
- const localSettings = path30.join(projectClaudeDir, "settings.local.json");
7698
+ if (fs29.existsSync(path32.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7699
+ const localSettings = path32.join(projectClaudeDir, "settings.local.json");
7584
7700
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7585
7701
  hooksCount += countHooksInFile(localSettings);
7586
- const mcpJsonServers = getMcpServerNames(path30.join(cwd, ".mcp.json"));
7702
+ const mcpJsonServers = getMcpServerNames(path32.join(cwd, ".mcp.json"));
7587
7703
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7588
7704
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7589
7705
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7616,12 +7732,12 @@ function readActiveShieldsHud() {
7616
7732
  return shieldsCache.value;
7617
7733
  }
7618
7734
  try {
7619
- const shieldsPath = path30.join(os23.homedir(), ".node9", "shields.json");
7620
- if (!fs27.existsSync(shieldsPath)) {
7735
+ const shieldsPath = path32.join(os25.homedir(), ".node9", "shields.json");
7736
+ if (!fs29.existsSync(shieldsPath)) {
7621
7737
  shieldsCache = { value: [], ts: now };
7622
7738
  return [];
7623
7739
  }
7624
- const parsed = JSON.parse(fs27.readFileSync(shieldsPath, "utf-8"));
7740
+ const parsed = JSON.parse(fs29.readFileSync(shieldsPath, "utf-8"));
7625
7741
  if (!Array.isArray(parsed.active)) {
7626
7742
  shieldsCache = { value: [], ts: now };
7627
7743
  return [];
@@ -7707,15 +7823,15 @@ function renderContextLine(stdin) {
7707
7823
  }
7708
7824
  const rl = stdin.rate_limits;
7709
7825
  if (rl?.five_hour?.used_percentage !== void 0) {
7710
- const pct = Math.round(rl.five_hour.used_percentage);
7711
- const bar = progressBar(pct, 60, 80);
7826
+ const pct2 = Math.round(rl.five_hour.used_percentage);
7827
+ const bar = progressBar(pct2, 60, 80);
7712
7828
  const left = formatTimeLeft(rl.five_hour.resets_at);
7713
- parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
7829
+ parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
7714
7830
  }
7715
7831
  if (rl?.seven_day?.used_percentage !== void 0) {
7716
- const pct = Math.round(rl.seven_day.used_percentage);
7717
- const bar = progressBar(pct, 60, 80);
7718
- parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
7832
+ const pct2 = Math.round(rl.seven_day.used_percentage);
7833
+ const bar = progressBar(pct2, 60, 80);
7834
+ parts.push(`${dim("\u2502")} 7d ${bar} ${pct2}%`);
7719
7835
  }
7720
7836
  if (parts.length === 0) return null;
7721
7837
  return parts.join(" ");
@@ -7723,17 +7839,17 @@ function renderContextLine(stdin) {
7723
7839
  async function main() {
7724
7840
  try {
7725
7841
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7726
- if (fs27.existsSync(path30.join(os23.homedir(), ".node9", "hud-debug"))) {
7842
+ if (fs29.existsSync(path32.join(os25.homedir(), ".node9", "hud-debug"))) {
7727
7843
  try {
7728
- const logPath = path30.join(os23.homedir(), ".node9", "hud-debug.log");
7844
+ const logPath = path32.join(os25.homedir(), ".node9", "hud-debug.log");
7729
7845
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7730
7846
  let size = 0;
7731
7847
  try {
7732
- size = fs27.statSync(logPath).size;
7848
+ size = fs29.statSync(logPath).size;
7733
7849
  } catch {
7734
7850
  }
7735
7851
  if (size < MAX_LOG_SIZE) {
7736
- fs27.appendFileSync(
7852
+ fs29.appendFileSync(
7737
7853
  logPath,
7738
7854
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7739
7855
  );
@@ -7754,11 +7870,11 @@ async function main() {
7754
7870
  try {
7755
7871
  const cwd = stdin.cwd ?? process.cwd();
7756
7872
  for (const configPath of [
7757
- path30.join(cwd, "node9.config.json"),
7758
- path30.join(os23.homedir(), ".node9", "config.json")
7873
+ path32.join(cwd, "node9.config.json"),
7874
+ path32.join(os25.homedir(), ".node9", "config.json")
7759
7875
  ]) {
7760
- if (!fs27.existsSync(configPath)) continue;
7761
- const cfg = JSON.parse(fs27.readFileSync(configPath, "utf-8"));
7876
+ if (!fs29.existsSync(configPath)) continue;
7877
+ const cfg = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
7762
7878
  const hud = cfg.settings?.hud;
7763
7879
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7764
7880
  }
@@ -7804,9 +7920,9 @@ init_core();
7804
7920
  import { Command } from "commander";
7805
7921
 
7806
7922
  // src/setup.ts
7807
- import fs11 from "fs";
7808
- import path14 from "path";
7809
- import os10 from "os";
7923
+ import fs12 from "fs";
7924
+ import path15 from "path";
7925
+ import os11 from "os";
7810
7926
  import chalk from "chalk";
7811
7927
  import { confirm } from "@inquirer/prompts";
7812
7928
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
@@ -7834,26 +7950,26 @@ function fullPathCommand(subcommand) {
7834
7950
  }
7835
7951
  function readJson(filePath) {
7836
7952
  try {
7837
- if (fs11.existsSync(filePath)) {
7838
- return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
7953
+ if (fs12.existsSync(filePath)) {
7954
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
7839
7955
  }
7840
7956
  } catch {
7841
7957
  }
7842
7958
  return null;
7843
7959
  }
7844
7960
  function writeJson(filePath, data) {
7845
- const dir = path14.dirname(filePath);
7846
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
7847
- fs11.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7961
+ const dir = path15.dirname(filePath);
7962
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
7963
+ fs12.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7848
7964
  }
7849
7965
  function isNode9Hook(cmd) {
7850
7966
  if (!cmd) return false;
7851
7967
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
7852
7968
  }
7853
7969
  function teardownClaude() {
7854
- const homeDir2 = os10.homedir();
7855
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7856
- const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
7970
+ const homeDir2 = os11.homedir();
7971
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
7972
+ const mcpPath = path15.join(homeDir2, ".claude", ".mcp.json");
7857
7973
  let changed = false;
7858
7974
  const settings = readJson(hooksPath);
7859
7975
  if (settings?.hooks) {
@@ -7901,8 +8017,8 @@ function teardownClaude() {
7901
8017
  }
7902
8018
  }
7903
8019
  function teardownGemini() {
7904
- const homeDir2 = os10.homedir();
7905
- const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
8020
+ const homeDir2 = os11.homedir();
8021
+ const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
7906
8022
  const settings = readJson(settingsPath);
7907
8023
  if (!settings) {
7908
8024
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7945,8 +8061,8 @@ function teardownGemini() {
7945
8061
  }
7946
8062
  }
7947
8063
  function teardownCursor() {
7948
- const homeDir2 = os10.homedir();
7949
- const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
8064
+ const homeDir2 = os11.homedir();
8065
+ const mcpPath = path15.join(homeDir2, ".cursor", "mcp.json");
7950
8066
  const mcpConfig = readJson(mcpPath);
7951
8067
  if (!mcpConfig?.mcpServers) {
7952
8068
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -7977,9 +8093,9 @@ function teardownCursor() {
7977
8093
  }
7978
8094
  }
7979
8095
  async function setupClaude() {
7980
- const homeDir2 = os10.homedir();
7981
- const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
7982
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8096
+ const homeDir2 = os11.homedir();
8097
+ const mcpPath = path15.join(homeDir2, ".claude", ".mcp.json");
8098
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
7983
8099
  const claudeConfig = readJson(mcpPath) ?? {};
7984
8100
  const settings = readJson(hooksPath) ?? {};
7985
8101
  const servers = claudeConfig.mcpServers ?? {};
@@ -8076,8 +8192,8 @@ async function setupClaude() {
8076
8192
  }
8077
8193
  }
8078
8194
  async function setupGemini() {
8079
- const homeDir2 = os10.homedir();
8080
- const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
8195
+ const homeDir2 = os11.homedir();
8196
+ const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
8081
8197
  const settings = readJson(settingsPath) ?? {};
8082
8198
  const servers = settings.mcpServers ?? {};
8083
8199
  let hooksChanged = false;
@@ -8172,10 +8288,10 @@ async function setupGemini() {
8172
8288
  printDaemonTip();
8173
8289
  }
8174
8290
  }
8175
- function detectAgents(homeDir2 = os10.homedir()) {
8291
+ function detectAgents(homeDir2 = os11.homedir()) {
8176
8292
  const exists = (p) => {
8177
8293
  try {
8178
- return fs11.existsSync(p);
8294
+ return fs12.existsSync(p);
8179
8295
  } catch (err2) {
8180
8296
  const code = err2.code;
8181
8297
  if (code !== "ENOENT") {
@@ -8186,15 +8302,15 @@ function detectAgents(homeDir2 = os10.homedir()) {
8186
8302
  }
8187
8303
  };
8188
8304
  return {
8189
- claude: exists(path14.join(homeDir2, ".claude")) || exists(path14.join(homeDir2, ".claude.json")),
8190
- gemini: exists(path14.join(homeDir2, ".gemini")),
8191
- cursor: exists(path14.join(homeDir2, ".cursor")),
8192
- codex: exists(path14.join(homeDir2, ".codex"))
8305
+ claude: exists(path15.join(homeDir2, ".claude")) || exists(path15.join(homeDir2, ".claude.json")),
8306
+ gemini: exists(path15.join(homeDir2, ".gemini")),
8307
+ cursor: exists(path15.join(homeDir2, ".cursor")),
8308
+ codex: exists(path15.join(homeDir2, ".codex"))
8193
8309
  };
8194
8310
  }
8195
8311
  async function setupCursor() {
8196
- const homeDir2 = os10.homedir();
8197
- const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
8312
+ const homeDir2 = os11.homedir();
8313
+ const mcpPath = path15.join(homeDir2, ".cursor", "mcp.json");
8198
8314
  const mcpConfig = readJson(mcpPath) ?? {};
8199
8315
  const servers = mcpConfig.mcpServers ?? {};
8200
8316
  let anythingChanged = false;
@@ -8260,21 +8376,21 @@ async function setupCursor() {
8260
8376
  }
8261
8377
  function readToml(filePath) {
8262
8378
  try {
8263
- if (fs11.existsSync(filePath)) {
8264
- return parseToml(fs11.readFileSync(filePath, "utf-8"));
8379
+ if (fs12.existsSync(filePath)) {
8380
+ return parseToml(fs12.readFileSync(filePath, "utf-8"));
8265
8381
  }
8266
8382
  } catch {
8267
8383
  }
8268
8384
  return null;
8269
8385
  }
8270
8386
  function writeToml(filePath, data) {
8271
- const dir = path14.dirname(filePath);
8272
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
8273
- fs11.writeFileSync(filePath, stringifyToml(data));
8387
+ const dir = path15.dirname(filePath);
8388
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
8389
+ fs12.writeFileSync(filePath, stringifyToml(data));
8274
8390
  }
8275
8391
  async function setupCodex() {
8276
- const homeDir2 = os10.homedir();
8277
- const configPath = path14.join(homeDir2, ".codex", "config.toml");
8392
+ const homeDir2 = os11.homedir();
8393
+ const configPath = path15.join(homeDir2, ".codex", "config.toml");
8278
8394
  const config = readToml(configPath) ?? {};
8279
8395
  const servers = config.mcp_servers ?? {};
8280
8396
  let anythingChanged = false;
@@ -8339,8 +8455,8 @@ async function setupCodex() {
8339
8455
  }
8340
8456
  }
8341
8457
  function setupHud() {
8342
- const homeDir2 = os10.homedir();
8343
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8458
+ const homeDir2 = os11.homedir();
8459
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8344
8460
  const settings = readJson(hooksPath) ?? {};
8345
8461
  const hudCommand = fullPathCommand("hud");
8346
8462
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8366,8 +8482,8 @@ function setupHud() {
8366
8482
  console.log(chalk.gray(" Restart Claude Code to activate."));
8367
8483
  }
8368
8484
  function teardownHud() {
8369
- const homeDir2 = os10.homedir();
8370
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8485
+ const homeDir2 = os11.homedir();
8486
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8371
8487
  const settings = readJson(hooksPath);
8372
8488
  if (!settings) {
8373
8489
  console.log(chalk.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8387,10 +8503,10 @@ function teardownHud() {
8387
8503
 
8388
8504
  // src/cli.ts
8389
8505
  init_daemon2();
8390
- import chalk19 from "chalk";
8391
- import fs28 from "fs";
8392
- import path31 from "path";
8393
- import os24 from "os";
8506
+ import chalk20 from "chalk";
8507
+ import fs30 from "fs";
8508
+ import path33 from "path";
8509
+ import os26 from "os";
8394
8510
  import { confirm as confirm2 } from "@inquirer/prompts";
8395
8511
 
8396
8512
  // src/utils/duration.ts
@@ -8619,19 +8735,19 @@ init_daemon();
8619
8735
  init_config();
8620
8736
  init_policy();
8621
8737
  import chalk5 from "chalk";
8622
- import fs18 from "fs";
8738
+ import fs19 from "fs";
8623
8739
  import { spawn as spawn6 } from "child_process";
8624
- import path20 from "path";
8625
- import os14 from "os";
8740
+ import path21 from "path";
8741
+ import os15 from "os";
8626
8742
 
8627
8743
  // src/undo.ts
8628
8744
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
8629
- import crypto2 from "crypto";
8630
- import fs17 from "fs";
8745
+ import crypto3 from "crypto";
8746
+ import fs18 from "fs";
8631
8747
  import net3 from "net";
8632
- import path19 from "path";
8633
- import os13 from "os";
8634
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path19.join(os13.tmpdir(), "node9-activity.sock");
8748
+ import path20 from "path";
8749
+ import os14 from "os";
8750
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path20.join(os14.tmpdir(), "node9-activity.sock");
8635
8751
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8636
8752
  try {
8637
8753
  const payload = JSON.stringify({
@@ -8651,22 +8767,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8651
8767
  } catch {
8652
8768
  }
8653
8769
  }
8654
- var SNAPSHOT_STACK_PATH = path19.join(os13.homedir(), ".node9", "snapshots.json");
8655
- var UNDO_LATEST_PATH = path19.join(os13.homedir(), ".node9", "undo_latest.txt");
8770
+ var SNAPSHOT_STACK_PATH = path20.join(os14.homedir(), ".node9", "snapshots.json");
8771
+ var UNDO_LATEST_PATH = path20.join(os14.homedir(), ".node9", "undo_latest.txt");
8656
8772
  var MAX_SNAPSHOTS = 10;
8657
8773
  var GIT_TIMEOUT = 15e3;
8658
8774
  function readStack() {
8659
8775
  try {
8660
- if (fs17.existsSync(SNAPSHOT_STACK_PATH))
8661
- return JSON.parse(fs17.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8776
+ if (fs18.existsSync(SNAPSHOT_STACK_PATH))
8777
+ return JSON.parse(fs18.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8662
8778
  } catch {
8663
8779
  }
8664
8780
  return [];
8665
8781
  }
8666
8782
  function writeStack(stack) {
8667
- const dir = path19.dirname(SNAPSHOT_STACK_PATH);
8668
- if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
8669
- fs17.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8783
+ const dir = path20.dirname(SNAPSHOT_STACK_PATH);
8784
+ if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
8785
+ fs18.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8670
8786
  }
8671
8787
  function extractFilePath(args) {
8672
8788
  if (!args || typeof args !== "object") return null;
@@ -8686,12 +8802,12 @@ function buildArgsSummary(tool, args) {
8686
8802
  return "";
8687
8803
  }
8688
8804
  function findProjectRoot(filePath) {
8689
- let dir = path19.dirname(filePath);
8805
+ let dir = path20.dirname(filePath);
8690
8806
  while (true) {
8691
- if (fs17.existsSync(path19.join(dir, ".git")) || fs17.existsSync(path19.join(dir, "package.json"))) {
8807
+ if (fs18.existsSync(path20.join(dir, ".git")) || fs18.existsSync(path20.join(dir, "package.json"))) {
8692
8808
  return dir;
8693
8809
  }
8694
- const parent = path19.dirname(dir);
8810
+ const parent = path20.dirname(dir);
8695
8811
  if (parent === dir) return process.cwd();
8696
8812
  dir = parent;
8697
8813
  }
@@ -8699,7 +8815,7 @@ function findProjectRoot(filePath) {
8699
8815
  function normalizeCwdForHash(cwd) {
8700
8816
  let normalized;
8701
8817
  try {
8702
- normalized = fs17.realpathSync(cwd);
8818
+ normalized = fs18.realpathSync(cwd);
8703
8819
  } catch {
8704
8820
  normalized = cwd;
8705
8821
  }
@@ -8708,17 +8824,17 @@ function normalizeCwdForHash(cwd) {
8708
8824
  return normalized;
8709
8825
  }
8710
8826
  function getShadowRepoDir(cwd) {
8711
- const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8712
- return path19.join(os13.homedir(), ".node9", "snapshots", hash);
8827
+ const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8828
+ return path20.join(os14.homedir(), ".node9", "snapshots", hash);
8713
8829
  }
8714
8830
  function cleanOrphanedIndexFiles(shadowDir) {
8715
8831
  try {
8716
8832
  const cutoff = Date.now() - 6e4;
8717
- for (const f of fs17.readdirSync(shadowDir)) {
8833
+ for (const f of fs18.readdirSync(shadowDir)) {
8718
8834
  if (f.startsWith("index_")) {
8719
- const fp = path19.join(shadowDir, f);
8835
+ const fp = path20.join(shadowDir, f);
8720
8836
  try {
8721
- if (fs17.statSync(fp).mtimeMs < cutoff) fs17.unlinkSync(fp);
8837
+ if (fs18.statSync(fp).mtimeMs < cutoff) fs18.unlinkSync(fp);
8722
8838
  } catch {
8723
8839
  }
8724
8840
  }
@@ -8730,7 +8846,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8730
8846
  const hardcoded = [".git", ".node9"];
8731
8847
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8732
8848
  try {
8733
- fs17.writeFileSync(path19.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8849
+ fs18.writeFileSync(path20.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8734
8850
  } catch {
8735
8851
  }
8736
8852
  }
@@ -8743,25 +8859,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8743
8859
  timeout: 3e3
8744
8860
  });
8745
8861
  if (check.status === 0) {
8746
- const ptPath = path19.join(shadowDir, "project-path.txt");
8862
+ const ptPath = path20.join(shadowDir, "project-path.txt");
8747
8863
  try {
8748
- const stored = fs17.readFileSync(ptPath, "utf8").trim();
8864
+ const stored = fs18.readFileSync(ptPath, "utf8").trim();
8749
8865
  if (stored === normalizedCwd) return true;
8750
8866
  if (process.env.NODE9_DEBUG === "1")
8751
8867
  console.error(
8752
8868
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8753
8869
  );
8754
- fs17.rmSync(shadowDir, { recursive: true, force: true });
8870
+ fs18.rmSync(shadowDir, { recursive: true, force: true });
8755
8871
  } catch {
8756
8872
  try {
8757
- fs17.writeFileSync(ptPath, normalizedCwd, "utf8");
8873
+ fs18.writeFileSync(ptPath, normalizedCwd, "utf8");
8758
8874
  } catch {
8759
8875
  }
8760
8876
  return true;
8761
8877
  }
8762
8878
  }
8763
8879
  try {
8764
- fs17.mkdirSync(shadowDir, { recursive: true });
8880
+ fs18.mkdirSync(shadowDir, { recursive: true });
8765
8881
  } catch {
8766
8882
  }
8767
8883
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8770,7 +8886,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8770
8886
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8771
8887
  return false;
8772
8888
  }
8773
- const configFile = path19.join(shadowDir, "config");
8889
+ const configFile = path20.join(shadowDir, "config");
8774
8890
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8775
8891
  timeout: 3e3
8776
8892
  });
@@ -8778,7 +8894,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8778
8894
  timeout: 3e3
8779
8895
  });
8780
8896
  try {
8781
- fs17.writeFileSync(path19.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8897
+ fs18.writeFileSync(path20.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8782
8898
  } catch {
8783
8899
  }
8784
8900
  return true;
@@ -8798,12 +8914,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8798
8914
  let indexFile = null;
8799
8915
  try {
8800
8916
  const rawFilePath = extractFilePath(args);
8801
- const absFilePath = rawFilePath && path19.isAbsolute(rawFilePath) ? rawFilePath : null;
8917
+ const absFilePath = rawFilePath && path20.isAbsolute(rawFilePath) ? rawFilePath : null;
8802
8918
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8803
8919
  const shadowDir = getShadowRepoDir(cwd);
8804
8920
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8805
8921
  writeShadowExcludes(shadowDir, ignorePaths);
8806
- indexFile = path19.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8922
+ indexFile = path20.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8807
8923
  const shadowEnv = {
8808
8924
  ...process.env,
8809
8925
  GIT_DIR: shadowDir,
@@ -8875,7 +8991,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8875
8991
  writeStack(stack);
8876
8992
  const entry = stack[stack.length - 1];
8877
8993
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8878
- fs17.writeFileSync(UNDO_LATEST_PATH, commitHash);
8994
+ fs18.writeFileSync(UNDO_LATEST_PATH, commitHash);
8879
8995
  if (shouldGc) {
8880
8996
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8881
8997
  }
@@ -8886,7 +9002,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8886
9002
  } finally {
8887
9003
  if (indexFile) {
8888
9004
  try {
8889
- fs17.unlinkSync(indexFile);
9005
+ fs18.unlinkSync(indexFile);
8890
9006
  } catch {
8891
9007
  }
8892
9008
  }
@@ -8962,9 +9078,9 @@ function applyUndo(hash, cwd) {
8962
9078
  timeout: GIT_TIMEOUT
8963
9079
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8964
9080
  for (const file of [...tracked, ...untracked]) {
8965
- const fullPath = path19.join(dir, file);
8966
- if (!snapshotFiles.has(file) && fs17.existsSync(fullPath)) {
8967
- fs17.unlinkSync(fullPath);
9081
+ const fullPath = path20.join(dir, file);
9082
+ if (!snapshotFiles.has(file) && fs18.existsSync(fullPath)) {
9083
+ fs18.unlinkSync(fullPath);
8968
9084
  }
8969
9085
  }
8970
9086
  return true;
@@ -8988,9 +9104,9 @@ function registerCheckCommand(program2) {
8988
9104
  } catch (err2) {
8989
9105
  const tempConfig = getConfig();
8990
9106
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
8991
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9107
+ const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
8992
9108
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
8993
- fs18.appendFileSync(
9109
+ fs19.appendFileSync(
8994
9110
  logPath,
8995
9111
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
8996
9112
  RAW: ${raw}
@@ -9003,10 +9119,10 @@ RAW: ${raw}
9003
9119
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9004
9120
  try {
9005
9121
  const scriptPath = process.argv[1];
9006
- if (typeof scriptPath !== "string" || !path20.isAbsolute(scriptPath))
9122
+ if (typeof scriptPath !== "string" || !path21.isAbsolute(scriptPath))
9007
9123
  throw new Error("node9: argv[1] is not an absolute path");
9008
- const resolvedScript = fs18.realpathSync(scriptPath);
9009
- const expectedCli = fs18.realpathSync(path20.resolve(__dirname, "../../cli.js"));
9124
+ const resolvedScript = fs19.realpathSync(scriptPath);
9125
+ const expectedCli = fs19.realpathSync(path21.resolve(__dirname, "../../cli.js"));
9010
9126
  if (resolvedScript !== expectedCli)
9011
9127
  throw new Error(
9012
9128
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9032,10 +9148,10 @@ RAW: ${raw}
9032
9148
  }
9033
9149
  }
9034
9150
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9035
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9036
- if (!fs18.existsSync(path20.dirname(logPath)))
9037
- fs18.mkdirSync(path20.dirname(logPath), { recursive: true });
9038
- fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9151
+ const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9152
+ if (!fs19.existsSync(path21.dirname(logPath)))
9153
+ fs19.mkdirSync(path21.dirname(logPath), { recursive: true });
9154
+ fs19.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9039
9155
  `);
9040
9156
  }
9041
9157
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9048,8 +9164,8 @@ RAW: ${raw}
9048
9164
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9049
9165
  let ttyFd = null;
9050
9166
  try {
9051
- ttyFd = fs18.openSync("/dev/tty", "w");
9052
- const writeTty = (line) => fs18.writeSync(ttyFd, line + "\n");
9167
+ ttyFd = fs19.openSync("/dev/tty", "w");
9168
+ const writeTty = (line) => fs19.writeSync(ttyFd, line + "\n");
9053
9169
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9054
9170
  writeTty(chalk5.bgRed.white.bold(`
9055
9171
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9068,7 +9184,7 @@ RAW: ${raw}
9068
9184
  } finally {
9069
9185
  if (ttyFd !== null)
9070
9186
  try {
9071
- fs18.closeSync(ttyFd);
9187
+ fs19.closeSync(ttyFd);
9072
9188
  } catch {
9073
9189
  }
9074
9190
  }
@@ -9100,7 +9216,7 @@ RAW: ${raw}
9100
9216
  if (shouldSnapshot(toolName, toolInput, config)) {
9101
9217
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9102
9218
  }
9103
- const safeCwdForAuth = typeof payload.cwd === "string" && path20.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9219
+ const safeCwdForAuth = typeof payload.cwd === "string" && path21.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9104
9220
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9105
9221
  cwd: safeCwdForAuth
9106
9222
  });
@@ -9112,12 +9228,12 @@ RAW: ${raw}
9112
9228
  }
9113
9229
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9114
9230
  try {
9115
- const tty = fs18.openSync("/dev/tty", "w");
9116
- fs18.writeSync(
9231
+ const tty = fs19.openSync("/dev/tty", "w");
9232
+ fs19.writeSync(
9117
9233
  tty,
9118
9234
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9119
9235
  );
9120
- fs18.closeSync(tty);
9236
+ fs19.closeSync(tty);
9121
9237
  } catch {
9122
9238
  }
9123
9239
  const daemonReady = await autoStartDaemonAndWait();
@@ -9144,9 +9260,9 @@ RAW: ${raw}
9144
9260
  });
9145
9261
  } catch (err2) {
9146
9262
  if (process.env.NODE9_DEBUG === "1") {
9147
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9263
+ const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9148
9264
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9149
- fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9265
+ fs19.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9150
9266
  `);
9151
9267
  }
9152
9268
  process.exit(0);
@@ -9183,9 +9299,9 @@ RAW: ${raw}
9183
9299
  init_audit();
9184
9300
  init_config();
9185
9301
  init_policy();
9186
- import fs19 from "fs";
9187
- import path21 from "path";
9188
- import os15 from "os";
9302
+ import fs20 from "fs";
9303
+ import path22 from "path";
9304
+ import os16 from "os";
9189
9305
  init_daemon();
9190
9306
 
9191
9307
  // src/utils/cp-mv-parser.ts
@@ -9226,9 +9342,9 @@ function containsShellMetachar(token) {
9226
9342
  }
9227
9343
 
9228
9344
  // src/cli/commands/log.ts
9229
- var TEST_COMMAND_RE = /(?:^|\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;
9345
+ var TEST_COMMAND_RE2 = /(?:^|\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;
9230
9346
  function detectTestResult(command, output) {
9231
- if (!TEST_COMMAND_RE.test(command)) return null;
9347
+ if (!TEST_COMMAND_RE2.test(command)) return null;
9232
9348
  const out = output.toLowerCase();
9233
9349
  if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
9234
9350
  out
@@ -9258,10 +9374,10 @@ function registerLogCommand(program2) {
9258
9374
  decision: "allowed",
9259
9375
  source: "post-hook"
9260
9376
  };
9261
- const logPath = path21.join(os15.homedir(), ".node9", "audit.log");
9262
- if (!fs19.existsSync(path21.dirname(logPath)))
9263
- fs19.mkdirSync(path21.dirname(logPath), { recursive: true });
9264
- fs19.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9377
+ const logPath = path22.join(os16.homedir(), ".node9", "audit.log");
9378
+ if (!fs20.existsSync(path22.dirname(logPath)))
9379
+ fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
9380
+ fs20.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9265
9381
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9266
9382
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9267
9383
  if (command) {
@@ -9277,16 +9393,24 @@ function registerLogCommand(program2) {
9277
9393
  if (bashCommand && output) {
9278
9394
  const testResult = detectTestResult(bashCommand, output);
9279
9395
  if (testResult) {
9280
- await notifyActivitySocket({
9281
- id: "test-result",
9282
- ts: Date.now(),
9396
+ appendToLog(LOCAL_AUDIT_LOG, {
9397
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9283
9398
  tool,
9284
- status: testResult === "pass" ? "test_pass" : "test_fail"
9399
+ testResult,
9400
+ source: "test-result"
9285
9401
  });
9402
+ if (isDaemonRunning()) {
9403
+ await notifyActivitySocket({
9404
+ id: "test-result",
9405
+ ts: Date.now(),
9406
+ tool,
9407
+ status: testResult === "pass" ? "test_pass" : "test_fail"
9408
+ });
9409
+ }
9286
9410
  }
9287
9411
  }
9288
9412
  }
9289
- const safeCwd = typeof payload.cwd === "string" && path21.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9413
+ const safeCwd = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9290
9414
  const config = getConfig(safeCwd);
9291
9415
  if (shouldSnapshot(tool, {}, config)) {
9292
9416
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9295,9 +9419,9 @@ function registerLogCommand(program2) {
9295
9419
  const msg = err2 instanceof Error ? err2.message : String(err2);
9296
9420
  process.stderr.write(`[Node9] audit log error: ${msg}
9297
9421
  `);
9298
- const debugPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9422
+ const debugPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9299
9423
  try {
9300
- fs19.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9424
+ fs20.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9301
9425
  `);
9302
9426
  } catch {
9303
9427
  }
@@ -9698,13 +9822,13 @@ function registerConfigShowCommand(program2) {
9698
9822
  // src/cli/commands/doctor.ts
9699
9823
  init_daemon();
9700
9824
  import chalk7 from "chalk";
9701
- import fs20 from "fs";
9702
- import path22 from "path";
9703
- import os16 from "os";
9825
+ import fs21 from "fs";
9826
+ import path23 from "path";
9827
+ import os17 from "os";
9704
9828
  import { execSync as execSync2 } from "child_process";
9705
9829
  function registerDoctorCommand(program2, version2) {
9706
9830
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9707
- const homeDir2 = os16.homedir();
9831
+ const homeDir2 = os17.homedir();
9708
9832
  let failures = 0;
9709
9833
  function pass(msg) {
9710
9834
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -9753,10 +9877,10 @@ function registerDoctorCommand(program2, version2) {
9753
9877
  );
9754
9878
  }
9755
9879
  section("Configuration");
9756
- const globalConfigPath = path22.join(homeDir2, ".node9", "config.json");
9757
- if (fs20.existsSync(globalConfigPath)) {
9880
+ const globalConfigPath = path23.join(homeDir2, ".node9", "config.json");
9881
+ if (fs21.existsSync(globalConfigPath)) {
9758
9882
  try {
9759
- JSON.parse(fs20.readFileSync(globalConfigPath, "utf-8"));
9883
+ JSON.parse(fs21.readFileSync(globalConfigPath, "utf-8"));
9760
9884
  pass("~/.node9/config.json found and valid");
9761
9885
  } catch {
9762
9886
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9764,10 +9888,10 @@ function registerDoctorCommand(program2, version2) {
9764
9888
  } else {
9765
9889
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9766
9890
  }
9767
- const projectConfigPath = path22.join(process.cwd(), "node9.config.json");
9768
- if (fs20.existsSync(projectConfigPath)) {
9891
+ const projectConfigPath = path23.join(process.cwd(), "node9.config.json");
9892
+ if (fs21.existsSync(projectConfigPath)) {
9769
9893
  try {
9770
- JSON.parse(fs20.readFileSync(projectConfigPath, "utf-8"));
9894
+ JSON.parse(fs21.readFileSync(projectConfigPath, "utf-8"));
9771
9895
  pass("node9.config.json found and valid (project)");
9772
9896
  } catch {
9773
9897
  fail(
@@ -9776,8 +9900,8 @@ function registerDoctorCommand(program2, version2) {
9776
9900
  );
9777
9901
  }
9778
9902
  }
9779
- const credsPath = path22.join(homeDir2, ".node9", "credentials.json");
9780
- if (fs20.existsSync(credsPath)) {
9903
+ const credsPath = path23.join(homeDir2, ".node9", "credentials.json");
9904
+ if (fs21.existsSync(credsPath)) {
9781
9905
  pass("Cloud credentials found (~/.node9/credentials.json)");
9782
9906
  } else {
9783
9907
  warn(
@@ -9786,10 +9910,10 @@ function registerDoctorCommand(program2, version2) {
9786
9910
  );
9787
9911
  }
9788
9912
  section("Agent Hooks");
9789
- const claudeSettingsPath = path22.join(homeDir2, ".claude", "settings.json");
9790
- if (fs20.existsSync(claudeSettingsPath)) {
9913
+ const claudeSettingsPath = path23.join(homeDir2, ".claude", "settings.json");
9914
+ if (fs21.existsSync(claudeSettingsPath)) {
9791
9915
  try {
9792
- const cs = JSON.parse(fs20.readFileSync(claudeSettingsPath, "utf-8"));
9916
+ const cs = JSON.parse(fs21.readFileSync(claudeSettingsPath, "utf-8"));
9793
9917
  const hasHook = cs.hooks?.PreToolUse?.some(
9794
9918
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9795
9919
  );
@@ -9805,10 +9929,10 @@ function registerDoctorCommand(program2, version2) {
9805
9929
  } else {
9806
9930
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9807
9931
  }
9808
- const geminiSettingsPath = path22.join(homeDir2, ".gemini", "settings.json");
9809
- if (fs20.existsSync(geminiSettingsPath)) {
9932
+ const geminiSettingsPath = path23.join(homeDir2, ".gemini", "settings.json");
9933
+ if (fs21.existsSync(geminiSettingsPath)) {
9810
9934
  try {
9811
- const gs = JSON.parse(fs20.readFileSync(geminiSettingsPath, "utf-8"));
9935
+ const gs = JSON.parse(fs21.readFileSync(geminiSettingsPath, "utf-8"));
9812
9936
  const hasHook = gs.hooks?.BeforeTool?.some(
9813
9937
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9814
9938
  );
@@ -9824,10 +9948,10 @@ function registerDoctorCommand(program2, version2) {
9824
9948
  } else {
9825
9949
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9826
9950
  }
9827
- const cursorHooksPath = path22.join(homeDir2, ".cursor", "hooks.json");
9828
- if (fs20.existsSync(cursorHooksPath)) {
9951
+ const cursorHooksPath = path23.join(homeDir2, ".cursor", "hooks.json");
9952
+ if (fs21.existsSync(cursorHooksPath)) {
9829
9953
  try {
9830
- const cur = JSON.parse(fs20.readFileSync(cursorHooksPath, "utf-8"));
9954
+ const cur = JSON.parse(fs21.readFileSync(cursorHooksPath, "utf-8"));
9831
9955
  const hasHook = cur.hooks?.preToolUse?.some(
9832
9956
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9833
9957
  );
@@ -9865,9 +9989,9 @@ function registerDoctorCommand(program2, version2) {
9865
9989
 
9866
9990
  // src/cli/commands/audit.ts
9867
9991
  import chalk8 from "chalk";
9868
- import fs21 from "fs";
9869
- import path23 from "path";
9870
- import os17 from "os";
9992
+ import fs22 from "fs";
9993
+ import path24 from "path";
9994
+ import os18 from "os";
9871
9995
  function formatRelativeTime(timestamp) {
9872
9996
  const diff = Date.now() - new Date(timestamp).getTime();
9873
9997
  const sec = Math.floor(diff / 1e3);
@@ -9880,14 +10004,14 @@ function formatRelativeTime(timestamp) {
9880
10004
  }
9881
10005
  function registerAuditCommand(program2) {
9882
10006
  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) => {
9883
- const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9884
- if (!fs21.existsSync(logPath)) {
10007
+ const logPath = path24.join(os18.homedir(), ".node9", "audit.log");
10008
+ if (!fs22.existsSync(logPath)) {
9885
10009
  console.log(
9886
10010
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
9887
10011
  );
9888
10012
  return;
9889
10013
  }
9890
- const raw = fs21.readFileSync(logPath, "utf-8");
10014
+ const raw = fs22.readFileSync(logPath, "utf-8");
9891
10015
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
9892
10016
  let entries = lines.flatMap((line) => {
9893
10017
  try {
@@ -9939,10 +10063,398 @@ function registerAuditCommand(program2) {
9939
10063
  });
9940
10064
  }
9941
10065
 
10066
+ // src/cli/commands/report.ts
10067
+ import chalk9 from "chalk";
10068
+ import fs23 from "fs";
10069
+ import path25 from "path";
10070
+ import os19 from "os";
10071
+ 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;
10072
+ function buildTestTimestamps(allEntries) {
10073
+ const testTs = /* @__PURE__ */ new Set();
10074
+ for (const e of allEntries) {
10075
+ if (e.source !== "post-hook") continue;
10076
+ if (e.tool !== "Bash" && e.tool !== "bash") continue;
10077
+ const cmd = e.args?.command;
10078
+ if (typeof cmd === "string" && TEST_COMMAND_RE3.test(cmd)) {
10079
+ testTs.add(new Date(e.ts).getTime());
10080
+ }
10081
+ }
10082
+ return testTs;
10083
+ }
10084
+ function isTestEntry(entry, testTs) {
10085
+ if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
10086
+ if (entry.testRun === true) return true;
10087
+ const cmd = entry.args?.command;
10088
+ if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
10089
+ const t = new Date(entry.ts).getTime();
10090
+ for (const ts of testTs) {
10091
+ if (Math.abs(ts - t) <= 3e3) return true;
10092
+ }
10093
+ return false;
10094
+ }
10095
+ function getDateRange(period) {
10096
+ const now = /* @__PURE__ */ new Date();
10097
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
10098
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
10099
+ switch (period) {
10100
+ case "today":
10101
+ return { start: todayStart, end };
10102
+ case "7d": {
10103
+ const s = new Date(todayStart);
10104
+ s.setDate(s.getDate() - 6);
10105
+ return { start: s, end };
10106
+ }
10107
+ case "30d": {
10108
+ const s = new Date(todayStart);
10109
+ s.setDate(s.getDate() - 29);
10110
+ return { start: s, end };
10111
+ }
10112
+ case "month":
10113
+ return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
10114
+ }
10115
+ }
10116
+ function parseAuditLog(logPath) {
10117
+ if (!fs23.existsSync(logPath)) return [];
10118
+ const raw = fs23.readFileSync(logPath, "utf-8");
10119
+ return raw.split("\n").flatMap((line) => {
10120
+ if (!line.trim()) return [];
10121
+ try {
10122
+ return [JSON.parse(line)];
10123
+ } catch {
10124
+ return [];
10125
+ }
10126
+ });
10127
+ }
10128
+ function isAllow(decision) {
10129
+ return decision.startsWith("allow");
10130
+ }
10131
+ function isDlp(checkedBy) {
10132
+ return !!checkedBy?.includes("dlp");
10133
+ }
10134
+ function barStr(value, max, width) {
10135
+ if (max === 0 || width <= 0) return "\u2591".repeat(width);
10136
+ const filled = Math.max(1, Math.round(value / max * width));
10137
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
10138
+ }
10139
+ function colorBar(value, max, width) {
10140
+ const s = barStr(value, max, width);
10141
+ const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
10142
+ return chalk9.cyan(s.slice(0, filled)) + chalk9.dim(s.slice(filled));
10143
+ }
10144
+ function pct(num2, total) {
10145
+ if (total === 0) return "\u2013";
10146
+ return Math.round(num2 / total * 100) + "%";
10147
+ }
10148
+ function fmtDate(d) {
10149
+ const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
10150
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
10151
+ }
10152
+ function num(n) {
10153
+ return n.toLocaleString();
10154
+ }
10155
+ function fmtCost(usd) {
10156
+ if (usd < 1e-3) return "< $0.001";
10157
+ if (usd < 1) return "$" + usd.toFixed(4);
10158
+ return "$" + usd.toFixed(2);
10159
+ }
10160
+ var CLAUDE_PRICING = {
10161
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10162
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10163
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
10164
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10165
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10166
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10167
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10168
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10169
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
10170
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
10171
+ };
10172
+ function claudeModelPrice(model) {
10173
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10174
+ for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
10175
+ if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
10176
+ }
10177
+ return null;
10178
+ }
10179
+ function loadClaudeCost(start, end) {
10180
+ const projectsDir = path25.join(os19.homedir(), ".claude", "projects");
10181
+ if (!fs23.existsSync(projectsDir)) return { total: 0, byDay: /* @__PURE__ */ new Map() };
10182
+ let dirs;
10183
+ try {
10184
+ dirs = fs23.readdirSync(projectsDir);
10185
+ } catch {
10186
+ return { total: 0, byDay: /* @__PURE__ */ new Map() };
10187
+ }
10188
+ let total = 0;
10189
+ const byDay = /* @__PURE__ */ new Map();
10190
+ for (const proj of dirs) {
10191
+ const projPath = path25.join(projectsDir, proj);
10192
+ let files;
10193
+ try {
10194
+ const stat = fs23.statSync(projPath);
10195
+ if (!stat.isDirectory()) continue;
10196
+ files = fs23.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10197
+ } catch {
10198
+ continue;
10199
+ }
10200
+ for (const file of files) {
10201
+ try {
10202
+ const raw = fs23.readFileSync(path25.join(projPath, file), "utf-8");
10203
+ for (const line of raw.split("\n")) {
10204
+ if (!line.trim()) continue;
10205
+ let entry;
10206
+ try {
10207
+ entry = JSON.parse(line);
10208
+ } catch {
10209
+ continue;
10210
+ }
10211
+ if (entry.type !== "assistant") continue;
10212
+ if (!entry.timestamp) continue;
10213
+ const ts = new Date(entry.timestamp);
10214
+ if (ts < start || ts > end) continue;
10215
+ const usage = entry.message?.usage;
10216
+ const model = entry.message?.model;
10217
+ if (!usage || !model) continue;
10218
+ const p = claudeModelPrice(model);
10219
+ if (!p) continue;
10220
+ const cost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
10221
+ total += cost;
10222
+ const dateKey = entry.timestamp.slice(0, 10);
10223
+ byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10224
+ }
10225
+ } catch {
10226
+ continue;
10227
+ }
10228
+ }
10229
+ }
10230
+ return { total, byDay };
10231
+ }
10232
+ function registerReportCommand(program2) {
10233
+ 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) => {
10234
+ const period = ["today", "7d", "30d", "month"].includes(
10235
+ options.period
10236
+ ) ? options.period : "7d";
10237
+ const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
10238
+ const allEntries = parseAuditLog(logPath);
10239
+ if (allEntries.length === 0) {
10240
+ console.log(
10241
+ chalk9.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
10242
+ );
10243
+ return;
10244
+ }
10245
+ const { start, end } = getDateRange(period);
10246
+ const { total: costUSD, byDay: costByDay } = loadClaudeCost(start, end);
10247
+ const periodMs = end.getTime() - start.getTime();
10248
+ const priorEnd = new Date(start.getTime() - 1);
10249
+ const priorStart = new Date(start.getTime() - periodMs);
10250
+ const priorEntries = allEntries.filter((e) => {
10251
+ if (e.source === "post-hook") return false;
10252
+ const ts = new Date(e.ts);
10253
+ return ts >= priorStart && ts <= priorEnd;
10254
+ });
10255
+ const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
10256
+ const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
10257
+ const excludeTests = options.tests === false;
10258
+ const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
10259
+ let filteredTestCount = 0;
10260
+ const entries = allEntries.filter((e) => {
10261
+ if (e.source === "post-hook") return false;
10262
+ const ts = new Date(e.ts);
10263
+ if (ts < start || ts > end) return false;
10264
+ if (excludeTests && isTestEntry(e, testTs)) {
10265
+ filteredTestCount++;
10266
+ return false;
10267
+ }
10268
+ return true;
10269
+ });
10270
+ if (entries.length === 0) {
10271
+ console.log(chalk9.yellow(`
10272
+ No activity for period "${period}".
10273
+ `));
10274
+ return;
10275
+ }
10276
+ let allowed = 0;
10277
+ let blocked = 0;
10278
+ let dlpHits = 0;
10279
+ let loopHits = 0;
10280
+ let testPasses = 0;
10281
+ let testFails = 0;
10282
+ const toolMap = /* @__PURE__ */ new Map();
10283
+ const blockMap = /* @__PURE__ */ new Map();
10284
+ const agentMap = /* @__PURE__ */ new Map();
10285
+ const mcpMap = /* @__PURE__ */ new Map();
10286
+ const dailyMap = /* @__PURE__ */ new Map();
10287
+ const hourMap = /* @__PURE__ */ new Map();
10288
+ for (const e of entries) {
10289
+ const allow = isAllow(e.decision);
10290
+ const dateKey = e.ts.slice(0, 10);
10291
+ if (allow) allowed++;
10292
+ else blocked++;
10293
+ if (isDlp(e.checkedBy)) dlpHits++;
10294
+ if (e.checkedBy === "loop-detected") loopHits++;
10295
+ const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
10296
+ t.calls++;
10297
+ if (!allow) t.blocked++;
10298
+ toolMap.set(e.tool, t);
10299
+ if (!allow && e.checkedBy) {
10300
+ blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
10301
+ }
10302
+ if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
10303
+ if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
10304
+ const hour = new Date(e.ts).getHours();
10305
+ hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
10306
+ const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
10307
+ d.calls++;
10308
+ if (!allow) d.blocked++;
10309
+ dailyMap.set(dateKey, d);
10310
+ }
10311
+ for (const e of allEntries) {
10312
+ if (e.source !== "test-result") continue;
10313
+ const ts = new Date(e.ts);
10314
+ if (ts < start || ts > end) continue;
10315
+ if (e.testResult === "pass") testPasses++;
10316
+ else if (e.testResult === "fail") testFails++;
10317
+ }
10318
+ const total = entries.length;
10319
+ const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
10320
+ const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
10321
+ const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
10322
+ const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
10323
+ const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
10324
+ const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
10325
+ const W = Math.min(process.stdout.columns || 80, 100);
10326
+ const INNER = W - 4;
10327
+ const COL = Math.floor(INNER / 2) - 1;
10328
+ const LABEL = 24;
10329
+ const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
10330
+ const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num(v.calls).length), 1);
10331
+ const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num(v).length), 1);
10332
+ const line = chalk9.dim("\u2500".repeat(W - 2));
10333
+ const periodLabel = {
10334
+ today: "Today",
10335
+ "7d": "Last 7 Days",
10336
+ "30d": "Last 30 Days",
10337
+ month: "This Month"
10338
+ };
10339
+ console.log("");
10340
+ console.log(
10341
+ " " + chalk9.bold.cyan("\u{1F6E1} node9 Report") + chalk9.dim(" \xB7 ") + chalk9.white(periodLabel[period]) + chalk9.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + chalk9.dim(` ${num(total)} events`) + (excludeTests ? chalk9.dim(` \u2013tests (\u2013${filteredTestCount})`) : "")
10342
+ );
10343
+ console.log(" " + line);
10344
+ console.log("");
10345
+ const blockLabel = blocked > 0 ? chalk9.red(`\u{1F6D1} ${num(blocked)} blocked`) : chalk9.dim("\u{1F6D1} 0 blocked");
10346
+ const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
10347
+ const loopLabel = loopHits > 0 ? chalk9.yellow(`\u{1F504} ${loopHits} loops`) : chalk9.dim("\u{1F504} 0 loops");
10348
+ const costLabel = costUSD > 0 ? chalk9.magenta(`\u{1F4B0} ${fmtCost(costUSD)}`) : chalk9.dim("\u{1F4B0} \u2013");
10349
+ const currentRate = total > 0 ? blocked / total : 0;
10350
+ const trendLabel = (() => {
10351
+ if (priorBlockRate === null) return chalk9.dim(`${pct(blocked, total)} block rate`);
10352
+ const delta = Math.round((currentRate - priorBlockRate) * 100);
10353
+ const arrow = delta > 0 ? chalk9.red(`\u25B2${delta}%`) : delta < 0 ? chalk9.green(`\u25BC${Math.abs(delta)}%`) : chalk9.dim("\u2013");
10354
+ return chalk9.dim(`${pct(blocked, total)} block rate `) + arrow + chalk9.dim(" vs prior");
10355
+ })();
10356
+ const reads = toolMap.get("Read")?.calls ?? 0;
10357
+ const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
10358
+ const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
10359
+ const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
10360
+ console.log(
10361
+ " " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel + " " + costLabel
10362
+ );
10363
+ console.log(" " + ratioLabel + " " + testLabel);
10364
+ console.log("");
10365
+ const toolHeaderRaw = "Top Tools";
10366
+ const blockHeaderRaw = "Top Blocks";
10367
+ console.log(
10368
+ " " + chalk9.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + chalk9.bold(blockHeaderRaw)
10369
+ );
10370
+ console.log(" " + chalk9.dim("\u2500".repeat(COL)) + " " + chalk9.dim("\u2500".repeat(COL)));
10371
+ const rows = Math.max(topTools.length, topBlocks.length, 1);
10372
+ for (let i = 0; i < rows; i++) {
10373
+ let leftStyled = " ".repeat(COL);
10374
+ if (i < topTools.length) {
10375
+ const [tool, { calls }] = topTools[i];
10376
+ const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
10377
+ const countStr = num(calls).padStart(TOOL_COUNT_W);
10378
+ const b = colorBar(calls, maxTool, BAR);
10379
+ const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
10380
+ const pad = Math.max(0, COL - rawLen);
10381
+ leftStyled = chalk9.white(label.padEnd(LABEL)) + b + " " + chalk9.white(countStr) + " ".repeat(pad);
10382
+ }
10383
+ let rightStyled = "";
10384
+ if (i < topBlocks.length) {
10385
+ const [reason, count] = topBlocks[i];
10386
+ const label = reason.length > LABEL - 1 ? reason.slice(0, LABEL - 2) + "\u2026" : reason;
10387
+ const countStr = num(count).padStart(BLOCK_COUNT_W);
10388
+ const b = colorBar(count, maxBlock, BAR);
10389
+ rightStyled = chalk9.white(label.padEnd(LABEL)) + b + " " + chalk9.red(countStr);
10390
+ }
10391
+ console.log(" " + leftStyled + " " + rightStyled);
10392
+ }
10393
+ if (topBlocks.length === 0) {
10394
+ console.log(" " + " ".repeat(COL) + " " + chalk9.dim("nothing blocked \u2713"));
10395
+ }
10396
+ if (agentMap.size > 1) {
10397
+ console.log("");
10398
+ console.log(" " + chalk9.bold("Agents"));
10399
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10400
+ const maxAgent = Math.max(...agentMap.values(), 1);
10401
+ for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
10402
+ const label = agent.slice(0, LABEL - 1);
10403
+ const b = colorBar(count, maxAgent, BAR);
10404
+ console.log(" " + chalk9.white(label.padEnd(LABEL)) + b + " " + chalk9.white(num(count)));
10405
+ }
10406
+ }
10407
+ if (mcpMap.size > 0) {
10408
+ console.log("");
10409
+ console.log(" " + chalk9.bold("MCP Servers"));
10410
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10411
+ const maxMcp = Math.max(...mcpMap.values(), 1);
10412
+ for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
10413
+ const label = server.slice(0, LABEL - 1).padEnd(LABEL);
10414
+ const b = colorBar(count, maxMcp, BAR);
10415
+ console.log(" " + chalk9.white(label) + b + " " + chalk9.white(num(count)));
10416
+ }
10417
+ }
10418
+ if (hourMap.size > 0) {
10419
+ const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
10420
+ const maxHour = Math.max(...hourMap.values(), 1);
10421
+ const bar = Array.from({ length: 24 }, (_, h) => {
10422
+ const v = hourMap.get(h) ?? 0;
10423
+ return BLOCKS[Math.round(v / maxHour * 8)];
10424
+ }).join("");
10425
+ console.log("");
10426
+ console.log(" " + chalk9.bold("Hour of Day") + chalk9.dim(" (local, 0h \u2013 23h)"));
10427
+ console.log(" " + chalk9.cyan(bar));
10428
+ console.log(" " + chalk9.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
10429
+ }
10430
+ if (dailyList.length > 1) {
10431
+ console.log("");
10432
+ console.log(" " + chalk9.bold("Daily Activity"));
10433
+ console.log(" " + chalk9.dim("\u2500".repeat(W - 2)));
10434
+ const DAY_BAR = Math.max(8, Math.min(30, W - 36));
10435
+ for (const [dateKey, { calls, blocked: db }] of dailyList) {
10436
+ const label = fmtDate(dateKey).padEnd(10);
10437
+ const b = colorBar(calls, maxDaily, DAY_BAR);
10438
+ const dayCost = costByDay.get(dateKey);
10439
+ const costNote = dayCost ? chalk9.magenta(` ${fmtCost(dayCost)}`) : "";
10440
+ const blockNote = db > 0 ? chalk9.red(` ${db} blocked`) : "";
10441
+ console.log(
10442
+ " " + chalk9.dim(label) + " " + b + " " + chalk9.white(num(calls)) + blockNote + costNote
10443
+ );
10444
+ }
10445
+ }
10446
+ console.log("");
10447
+ console.log(
10448
+ " " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
10449
+ );
10450
+ console.log("");
10451
+ });
10452
+ }
10453
+
9942
10454
  // src/cli/commands/daemon-cmd.ts
9943
10455
  init_daemon2();
9944
10456
  init_daemon();
9945
- import chalk9 from "chalk";
10457
+ import chalk10 from "chalk";
9946
10458
  import { spawn as spawn7 } from "child_process";
9947
10459
  function registerDaemonCommand(program2) {
9948
10460
  program2.command("daemon").description("Run the local approval server").argument("[action]", "start | stop | status (default: start)").option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
@@ -9955,7 +10467,7 @@ function registerDaemonCommand(program2) {
9955
10467
  if (cmd === "status") return daemonStatus();
9956
10468
  if (cmd !== "start" && action !== void 0) {
9957
10469
  console.error(
9958
- chalk9.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10470
+ chalk10.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
9959
10471
  );
9960
10472
  process.exit(1);
9961
10473
  }
@@ -9963,7 +10475,7 @@ function registerDaemonCommand(program2) {
9963
10475
  process.env.NODE9_WATCH_MODE = "1";
9964
10476
  setTimeout(() => {
9965
10477
  openBrowserLocal();
9966
- console.log(chalk9.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10478
+ console.log(chalk10.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9967
10479
  }, 600);
9968
10480
  startDaemon();
9969
10481
  return;
@@ -9971,7 +10483,7 @@ function registerDaemonCommand(program2) {
9971
10483
  if (options.openui) {
9972
10484
  if (isDaemonRunning()) {
9973
10485
  openBrowserLocal();
9974
- console.log(chalk9.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10486
+ console.log(chalk10.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9975
10487
  process.exit(0);
9976
10488
  }
9977
10489
  const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
@@ -9984,7 +10496,7 @@ function registerDaemonCommand(program2) {
9984
10496
  if (isDaemonRunning()) break;
9985
10497
  }
9986
10498
  openBrowserLocal();
9987
- console.log(chalk9.green(`
10499
+ console.log(chalk10.green(`
9988
10500
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
9989
10501
  process.exit(0);
9990
10502
  }
@@ -9994,7 +10506,7 @@ function registerDaemonCommand(program2) {
9994
10506
  stdio: "ignore"
9995
10507
  });
9996
10508
  child.unref();
9997
- console.log(chalk9.green(`
10509
+ console.log(chalk10.green(`
9998
10510
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
9999
10511
  process.exit(0);
10000
10512
  }
@@ -10006,13 +10518,13 @@ function registerDaemonCommand(program2) {
10006
10518
  // src/cli/commands/status.ts
10007
10519
  init_core();
10008
10520
  init_daemon();
10009
- import chalk10 from "chalk";
10010
- import fs22 from "fs";
10011
- import path24 from "path";
10012
- import os18 from "os";
10521
+ import chalk11 from "chalk";
10522
+ import fs24 from "fs";
10523
+ import path26 from "path";
10524
+ import os20 from "os";
10013
10525
  function readJson2(filePath) {
10014
10526
  try {
10015
- if (fs22.existsSync(filePath)) return JSON.parse(fs22.readFileSync(filePath, "utf-8"));
10527
+ if (fs24.existsSync(filePath)) return JSON.parse(fs24.readFileSync(filePath, "utf-8"));
10016
10528
  } catch {
10017
10529
  }
10018
10530
  return null;
@@ -10026,21 +10538,21 @@ function wrappedMcpServers(servers) {
10026
10538
  return Object.entries(servers).filter(([, s]) => s.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
10027
10539
  }
10028
10540
  function printAgentSection(label, hookPairs, wrapped) {
10029
- console.log(chalk10.bold(` ${label}`));
10541
+ console.log(chalk11.bold(` ${label}`));
10030
10542
  for (const { name, present } of hookPairs) {
10031
10543
  if (present) {
10032
- console.log(chalk10.green(` \u2713 ${name}`));
10544
+ console.log(chalk11.green(` \u2713 ${name}`));
10033
10545
  } else {
10034
- console.log(chalk10.red(` \u2717 ${name}`) + chalk10.gray(" (not wired)"));
10546
+ console.log(chalk11.red(` \u2717 ${name}`) + chalk11.gray(" (not wired)"));
10035
10547
  }
10036
10548
  }
10037
10549
  if (wrapped.length > 0) {
10038
- console.log(chalk10.cyan(` MCP proxied:`));
10550
+ console.log(chalk11.cyan(` MCP proxied:`));
10039
10551
  for (const entry of wrapped) {
10040
- console.log(chalk10.gray(` \u2022 ${entry}`));
10552
+ console.log(chalk11.gray(` \u2022 ${entry}`));
10041
10553
  }
10042
10554
  } else {
10043
- console.log(chalk10.gray(` MCP proxied: none`));
10555
+ console.log(chalk11.gray(` MCP proxied: none`));
10044
10556
  }
10045
10557
  }
10046
10558
  function registerStatusCommand(program2) {
@@ -10051,58 +10563,58 @@ function registerStatusCommand(program2) {
10051
10563
  const settings = mergedConfig.settings;
10052
10564
  console.log("");
10053
10565
  if (creds && settings.approvers.cloud) {
10054
- console.log(chalk10.green(" \u25CF Agent mode") + chalk10.gray(" \u2014 cloud team policy enforced"));
10566
+ console.log(chalk11.green(" \u25CF Agent mode") + chalk11.gray(" \u2014 cloud team policy enforced"));
10055
10567
  } else if (creds && !settings.approvers.cloud) {
10056
10568
  console.log(
10057
- chalk10.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk10.gray(" \u2014 all decisions stay on this machine")
10569
+ chalk11.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk11.gray(" \u2014 all decisions stay on this machine")
10058
10570
  );
10059
10571
  } else {
10060
10572
  console.log(
10061
- chalk10.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk10.gray(" \u2014 no API key (Local rules only)")
10573
+ chalk11.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk11.gray(" \u2014 no API key (Local rules only)")
10062
10574
  );
10063
10575
  }
10064
10576
  console.log("");
10065
10577
  if (daemonRunning) {
10066
10578
  console.log(
10067
- chalk10.green(" \u25CF Daemon running") + chalk10.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10579
+ chalk11.green(" \u25CF Daemon running") + chalk11.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10068
10580
  );
10069
10581
  } else {
10070
- console.log(chalk10.gray(" \u25CB Daemon stopped"));
10582
+ console.log(chalk11.gray(" \u25CB Daemon stopped"));
10071
10583
  }
10072
10584
  if (settings.enableUndo) {
10073
10585
  console.log(
10074
- chalk10.magenta(" \u25CF Undo Engine") + chalk10.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10586
+ chalk11.magenta(" \u25CF Undo Engine") + chalk11.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10075
10587
  );
10076
10588
  }
10077
10589
  console.log("");
10078
- const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
10590
+ const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
10079
10591
  console.log(` Mode: ${modeLabel}`);
10080
- const projectConfig = path24.join(process.cwd(), "node9.config.json");
10081
- const globalConfig = path24.join(os18.homedir(), ".node9", "config.json");
10592
+ const projectConfig = path26.join(process.cwd(), "node9.config.json");
10593
+ const globalConfig = path26.join(os20.homedir(), ".node9", "config.json");
10082
10594
  console.log(
10083
- ` Local: ${fs22.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
10595
+ ` Local: ${fs24.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
10084
10596
  );
10085
10597
  console.log(
10086
- ` Global: ${fs22.existsSync(globalConfig) ? chalk10.green("Active (~/.node9/config.json)") : chalk10.gray("Not present")}`
10598
+ ` Global: ${fs24.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
10087
10599
  );
10088
10600
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10089
10601
  console.log(
10090
- ` Sandbox: ${chalk10.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10602
+ ` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10091
10603
  );
10092
10604
  }
10093
- const homeDir2 = os18.homedir();
10605
+ const homeDir2 = os20.homedir();
10094
10606
  const claudeSettings = readJson2(
10095
- path24.join(homeDir2, ".claude", "settings.json")
10607
+ path26.join(homeDir2, ".claude", "settings.json")
10096
10608
  );
10097
- const claudeConfig = readJson2(path24.join(homeDir2, ".claude.json"));
10609
+ const claudeConfig = readJson2(path26.join(homeDir2, ".claude.json"));
10098
10610
  const geminiSettings = readJson2(
10099
- path24.join(homeDir2, ".gemini", "settings.json")
10611
+ path26.join(homeDir2, ".gemini", "settings.json")
10100
10612
  );
10101
- const cursorConfig = readJson2(path24.join(homeDir2, ".cursor", "mcp.json"));
10613
+ const cursorConfig = readJson2(path26.join(homeDir2, ".cursor", "mcp.json"));
10102
10614
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10103
10615
  if (agentFound) {
10104
10616
  console.log("");
10105
- console.log(chalk10.bold(" Agent Wiring:"));
10617
+ console.log(chalk11.bold(" Agent Wiring:"));
10106
10618
  console.log("");
10107
10619
  if (claudeSettings || claudeConfig) {
10108
10620
  const preHook = claudeSettings?.hooks?.PreToolUse?.some(
@@ -10148,7 +10660,7 @@ function registerStatusCommand(program2) {
10148
10660
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
10149
10661
  console.log("");
10150
10662
  console.log(
10151
- chalk10.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk10.gray(" \u2014 all tool calls allowed")
10663
+ chalk11.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk11.gray(" \u2014 all tool calls allowed")
10152
10664
  );
10153
10665
  }
10154
10666
  console.log("");
@@ -10157,10 +10669,10 @@ function registerStatusCommand(program2) {
10157
10669
 
10158
10670
  // src/cli/commands/init.ts
10159
10671
  init_core();
10160
- import chalk11 from "chalk";
10161
- import fs23 from "fs";
10162
- import path25 from "path";
10163
- import os19 from "os";
10672
+ import chalk12 from "chalk";
10673
+ import fs25 from "fs";
10674
+ import path27 from "path";
10675
+ import os21 from "os";
10164
10676
  import https2 from "https";
10165
10677
  init_shields();
10166
10678
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
@@ -10195,7 +10707,7 @@ function fireTelemetryPing(agents) {
10195
10707
  }
10196
10708
  function registerInitCommand(program2) {
10197
10709
  program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
10198
- console.log(chalk11.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10710
+ console.log(chalk12.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10199
10711
  let chosenMode = options.mode.toLowerCase();
10200
10712
  if (!["standard", "strict", "audit"].includes(chosenMode)) {
10201
10713
  chosenMode = DEFAULT_CONFIG.settings.mode;
@@ -10214,37 +10726,37 @@ function registerInitCommand(program2) {
10214
10726
  const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
10215
10727
  if (hasNewShields) writeActiveShields(merged);
10216
10728
  } catch (err2) {
10217
- console.log(chalk11.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10729
+ console.log(chalk12.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10218
10730
  }
10219
10731
  }
10220
10732
  console.log("");
10221
10733
  }
10222
- const configPath = path25.join(os19.homedir(), ".node9", "config.json");
10223
- if (fs23.existsSync(configPath) && !options.force) {
10734
+ const configPath = path27.join(os21.homedir(), ".node9", "config.json");
10735
+ if (fs25.existsSync(configPath) && !options.force) {
10224
10736
  try {
10225
- const existing = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
10737
+ const existing = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
10226
10738
  const settings = existing.settings ?? {};
10227
10739
  if (settings.mode !== chosenMode) {
10228
10740
  settings.mode = chosenMode;
10229
10741
  existing.settings = settings;
10230
- fs23.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10231
- console.log(chalk11.green(`\u2705 Mode updated: ${chosenMode}`));
10742
+ fs25.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10743
+ console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
10232
10744
  } else {
10233
- console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10745
+ console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10234
10746
  }
10235
10747
  } catch {
10236
- console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10748
+ console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10237
10749
  }
10238
10750
  } else {
10239
10751
  const configToSave = {
10240
10752
  ...DEFAULT_CONFIG,
10241
10753
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10242
10754
  };
10243
- const dir = path25.dirname(configPath);
10244
- if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
10245
- fs23.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10246
- console.log(chalk11.green(`\u2705 Config created: ${configPath}`));
10247
- console.log(chalk11.gray(` Mode: ${chosenMode}`));
10755
+ const dir = path27.dirname(configPath);
10756
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
10757
+ fs25.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10758
+ console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
10759
+ console.log(chalk12.gray(` Mode: ${chosenMode}`));
10248
10760
  }
10249
10761
  if (options.skipSetup) return;
10250
10762
  console.log("");
@@ -10254,18 +10766,18 @@ function registerInitCommand(program2) {
10254
10766
  );
10255
10767
  if (found.length === 0) {
10256
10768
  console.log(
10257
- chalk11.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
10769
+ chalk12.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
10258
10770
  );
10259
- console.log(chalk11.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
10771
+ console.log(chalk12.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
10260
10772
  return;
10261
10773
  }
10262
- console.log(chalk11.bold("Detected agents:"));
10774
+ console.log(chalk12.bold("Detected agents:"));
10263
10775
  for (const agent of found) {
10264
- console.log(chalk11.green(` \u2713 ${agent}`));
10776
+ console.log(chalk12.green(` \u2713 ${agent}`));
10265
10777
  }
10266
10778
  console.log("");
10267
10779
  for (const agent of found) {
10268
- console.log(chalk11.bold(`Wiring ${agent}...`));
10780
+ console.log(chalk12.bold(`Wiring ${agent}...`));
10269
10781
  if (agent === "claude") await setupClaude();
10270
10782
  else if (agent === "gemini") await setupGemini();
10271
10783
  else if (agent === "cursor") await setupCursor();
@@ -10282,26 +10794,26 @@ function registerInitCommand(program2) {
10282
10794
  console.log("");
10283
10795
  }
10284
10796
  const agentList = found.join(", ");
10285
- console.log(chalk11.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
10797
+ console.log(chalk12.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
10286
10798
  console.log("");
10287
- console.log(chalk11.white(" Watch live: ") + chalk11.cyan("node9 tail"));
10288
- console.log(chalk11.white(" Local UI: ") + chalk11.cyan("node9 daemon --openui"));
10799
+ console.log(chalk12.white(" Watch live: ") + chalk12.cyan("node9 tail"));
10800
+ console.log(chalk12.white(" Local UI: ") + chalk12.cyan("node9 daemon --openui"));
10289
10801
  console.log("");
10290
- console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10802
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10291
10803
  console.log(
10292
- chalk11.white(" Team dashboard + full audit trail \u2192 ") + chalk11.cyan.bold("https://node9.ai")
10804
+ chalk12.white(" Team dashboard + full audit trail \u2192 ") + chalk12.cyan.bold("https://node9.ai")
10293
10805
  );
10294
- console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10806
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10295
10807
  });
10296
10808
  }
10297
10809
 
10298
10810
  // src/cli/commands/undo.ts
10299
- import path26 from "path";
10300
- import chalk13 from "chalk";
10811
+ import path28 from "path";
10812
+ import chalk14 from "chalk";
10301
10813
 
10302
10814
  // src/tui/undo-navigator.ts
10303
10815
  import readline2 from "readline";
10304
- import chalk12 from "chalk";
10816
+ import chalk13 from "chalk";
10305
10817
  var RESET = "\x1B[0m";
10306
10818
  var BOLD = "\x1B[1m";
10307
10819
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
@@ -10319,15 +10831,15 @@ function renderDiff(raw) {
10319
10831
  );
10320
10832
  for (const line of lines) {
10321
10833
  if (line.startsWith("+++") || line.startsWith("---")) {
10322
- process.stdout.write(chalk12.bold(line) + "\n");
10834
+ process.stdout.write(chalk13.bold(line) + "\n");
10323
10835
  } else if (line.startsWith("+")) {
10324
- process.stdout.write(chalk12.green(line) + "\n");
10836
+ process.stdout.write(chalk13.green(line) + "\n");
10325
10837
  } else if (line.startsWith("-")) {
10326
- process.stdout.write(chalk12.red(line) + "\n");
10838
+ process.stdout.write(chalk13.red(line) + "\n");
10327
10839
  } else if (line.startsWith("@@")) {
10328
- process.stdout.write(chalk12.cyan(line) + "\n");
10840
+ process.stdout.write(chalk13.cyan(line) + "\n");
10329
10841
  } else {
10330
- process.stdout.write(chalk12.gray(line) + "\n");
10842
+ process.stdout.write(chalk13.gray(line) + "\n");
10331
10843
  }
10332
10844
  }
10333
10845
  }
@@ -10346,23 +10858,23 @@ function render(entries, idx) {
10346
10858
  const step = idx + 1;
10347
10859
  process.stdout.write(CLEAR_SCREEN);
10348
10860
  process.stdout.write(
10349
- chalk12.magenta.bold(`\u23EA Node9 Undo`) + chalk12.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? chalk12.gray(
10861
+ chalk13.magenta.bold(`\u23EA Node9 Undo`) + chalk13.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? chalk13.gray(
10350
10862
  ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
10351
10863
  ) : "") + "\n\n"
10352
10864
  );
10353
10865
  process.stdout.write(
10354
- ` ${BOLD}Tool:${RESET} ${chalk12.cyan(entry.tool)}` + (entry.argsSummary ? chalk12.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
10866
+ ` ${BOLD}Tool:${RESET} ${chalk13.cyan(entry.tool)}` + (entry.argsSummary ? chalk13.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
10355
10867
  );
10356
- process.stdout.write(` ${BOLD}When:${RESET} ${chalk12.gray(formatAge(entry.timestamp))}
10868
+ process.stdout.write(` ${BOLD}When:${RESET} ${chalk13.gray(formatAge(entry.timestamp))}
10357
10869
  `);
10358
- process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk12.gray(entry.cwd)}
10870
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk13.gray(entry.cwd)}
10359
10871
  `);
10360
10872
  if (entry.files && entry.files.length > 0) {
10361
- process.stdout.write(` ${BOLD}Files:${RESET} ${chalk12.gray(entry.files.join(", "))}
10873
+ process.stdout.write(` ${BOLD}Files:${RESET} ${chalk13.gray(entry.files.join(", "))}
10362
10874
  `);
10363
10875
  }
10364
10876
  if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
10365
- process.stdout.write(chalk12.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
10877
+ process.stdout.write(chalk13.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
10366
10878
  }
10367
10879
  process.stdout.write("\n");
10368
10880
  const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
@@ -10370,12 +10882,12 @@ function render(entries, idx) {
10370
10882
  renderDiff(diff);
10371
10883
  } else {
10372
10884
  process.stdout.write(
10373
- chalk12.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
10885
+ chalk13.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
10374
10886
  );
10375
10887
  }
10376
10888
  process.stdout.write("\n");
10377
10889
  process.stdout.write(
10378
- chalk12.gray(" ") + (idx < total - 1 ? chalk12.white("[\u2190] older") : chalk12.gray("[\u2190] older")) + chalk12.gray(" ") + (idx > 0 ? chalk12.white("[\u2192] newer") : chalk12.gray("[\u2192] newer")) + chalk12.gray(" ") + chalk12.green("[\u21B5] restore here") + chalk12.gray(" ") + chalk12.yellow("[s] session start") + chalk12.gray(" ") + chalk12.gray("[q] quit") + "\n"
10890
+ chalk13.gray(" ") + (idx < total - 1 ? chalk13.white("[\u2190] older") : chalk13.gray("[\u2190] older")) + chalk13.gray(" ") + (idx > 0 ? chalk13.white("[\u2192] newer") : chalk13.gray("[\u2192] newer")) + chalk13.gray(" ") + chalk13.green("[\u21B5] restore here") + chalk13.gray(" ") + chalk13.yellow("[s] session start") + chalk13.gray(" ") + chalk13.gray("[q] quit") + "\n"
10379
10891
  );
10380
10892
  }
10381
10893
  async function runUndoNavigator(entries) {
@@ -10429,19 +10941,19 @@ async function runUndoNavigator(entries) {
10429
10941
  cleanup();
10430
10942
  process.stdout.write(CLEAR_SCREEN);
10431
10943
  const entry = display[idx];
10432
- process.stdout.write(chalk12.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
10944
+ process.stdout.write(chalk13.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
10433
10945
  if (applyUndo(entry.hash, entry.cwd)) {
10434
- process.stdout.write(chalk12.green("\u2705 Reverted successfully.\n\n"));
10946
+ process.stdout.write(chalk13.green("\u2705 Reverted successfully.\n\n"));
10435
10947
  resolve({ restored: true });
10436
10948
  } else {
10437
- process.stdout.write(chalk12.red("\u274C Undo failed.\n\n"));
10949
+ process.stdout.write(chalk13.red("\u274C Undo failed.\n\n"));
10438
10950
  resolve({ restored: false });
10439
10951
  }
10440
10952
  } else if (name === "q" || key?.ctrl && name === "c") {
10441
10953
  done = true;
10442
10954
  cleanup();
10443
10955
  process.stdout.write(CLEAR_SCREEN);
10444
- process.stdout.write(chalk12.gray("\nCancelled.\n\n"));
10956
+ process.stdout.write(chalk13.gray("\nCancelled.\n\n"));
10445
10957
  resolve({ restored: false });
10446
10958
  }
10447
10959
  };
@@ -10455,7 +10967,7 @@ function findMatchingCwd(startDir, history) {
10455
10967
  let dir = startDir;
10456
10968
  while (true) {
10457
10969
  if (cwds.has(dir)) return dir;
10458
- const parent = path26.dirname(dir);
10970
+ const parent = path28.dirname(dir);
10459
10971
  if (parent === dir) return null;
10460
10972
  dir = parent;
10461
10973
  }
@@ -10477,39 +10989,39 @@ function registerUndoCommand(program2) {
10477
10989
  if (history.length === 0) {
10478
10990
  if (!options.all && allHistory.length > 0) {
10479
10991
  console.log(
10480
- chalk13.yellow(
10992
+ chalk14.yellow(
10481
10993
  `
10482
10994
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
10483
- Run ${chalk13.cyan("node9 undo --all")} to see snapshots from all projects.
10995
+ Run ${chalk14.cyan("node9 undo --all")} to see snapshots from all projects.
10484
10996
  `
10485
10997
  )
10486
10998
  );
10487
10999
  } else {
10488
- console.log(chalk13.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
11000
+ console.log(chalk14.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
10489
11001
  }
10490
11002
  return;
10491
11003
  }
10492
11004
  if (options.list) {
10493
- console.log(chalk13.magenta.bold("\n\u23EA Snapshot History\n"));
11005
+ console.log(chalk14.magenta.bold("\n\u23EA Snapshot History\n"));
10494
11006
  console.log(
10495
- chalk13.gray(
11007
+ chalk14.gray(
10496
11008
  ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
10497
11009
  )
10498
11010
  );
10499
- console.log(chalk13.gray(" " + "\u2500".repeat(80)));
11011
+ console.log(chalk14.gray(" " + "\u2500".repeat(80)));
10500
11012
  const display = [...history].reverse();
10501
11013
  let prevTs = null;
10502
11014
  for (let i = 0; i < display.length; i++) {
10503
11015
  const e = display[i];
10504
11016
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
10505
- if (isGap) console.log(chalk13.gray(" \u2500\u2500 earlier \u2500\u2500"));
11017
+ if (isGap) console.log(chalk14.gray(" \u2500\u2500 earlier \u2500\u2500"));
10506
11018
  const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
10507
11019
  const tool = e.tool.slice(0, 8).padEnd(8);
10508
11020
  const when = formatAge2(e.timestamp).padEnd(10);
10509
11021
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
10510
11022
  console.log(
10511
- chalk13.white(
10512
- ` ${String(i + 1).padEnd(3)} ${label} ${chalk13.cyan(tool)} ${chalk13.gray(when)} ${chalk13.gray(dir)}`
11023
+ chalk14.white(
11024
+ ` ${String(i + 1).padEnd(3)} ${label} ${chalk14.cyan(tool)} ${chalk14.gray(when)} ${chalk14.gray(dir)}`
10513
11025
  )
10514
11026
  );
10515
11027
  prevTs = e.timestamp;
@@ -10522,7 +11034,7 @@ function registerUndoCommand(program2) {
10522
11034
  const idx = history.length - steps;
10523
11035
  if (idx < 0) {
10524
11036
  console.log(
10525
- chalk13.yellow(
11037
+ chalk14.yellow(
10526
11038
  `
10527
11039
  \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10528
11040
  `
@@ -10533,47 +11045,47 @@ function registerUndoCommand(program2) {
10533
11045
  const snapshot = history[idx];
10534
11046
  const ageStr = formatAge2(snapshot.timestamp);
10535
11047
  console.log(
10536
- chalk13.magenta.bold(`
11048
+ chalk14.magenta.bold(`
10537
11049
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
10538
11050
  );
10539
11051
  console.log(
10540
- chalk13.white(
10541
- ` Tool: ${chalk13.cyan(snapshot.tool)}${snapshot.argsSummary ? chalk13.gray(" \u2192 " + snapshot.argsSummary) : ""}`
11052
+ chalk14.white(
11053
+ ` Tool: ${chalk14.cyan(snapshot.tool)}${snapshot.argsSummary ? chalk14.gray(" \u2192 " + snapshot.argsSummary) : ""}`
10542
11054
  )
10543
11055
  );
10544
- console.log(chalk13.white(` When: ${chalk13.gray(ageStr)}`));
10545
- console.log(chalk13.white(` Dir: ${chalk13.gray(snapshot.cwd)}`));
11056
+ console.log(chalk14.white(` When: ${chalk14.gray(ageStr)}`));
11057
+ console.log(chalk14.white(` Dir: ${chalk14.gray(snapshot.cwd)}`));
10546
11058
  if (steps > 1)
10547
11059
  console.log(
10548
- chalk13.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
11060
+ chalk14.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10549
11061
  );
10550
11062
  console.log("");
10551
11063
  const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10552
11064
  if (diff) {
10553
11065
  const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10554
11066
  for (const line of lines) {
10555
- if (line.startsWith("+++") || line.startsWith("---")) console.log(chalk13.bold(line));
10556
- else if (line.startsWith("+")) console.log(chalk13.green(line));
10557
- else if (line.startsWith("-")) console.log(chalk13.red(line));
10558
- else if (line.startsWith("@@")) console.log(chalk13.cyan(line));
10559
- else console.log(chalk13.gray(line));
11067
+ if (line.startsWith("+++") || line.startsWith("---")) console.log(chalk14.bold(line));
11068
+ else if (line.startsWith("+")) console.log(chalk14.green(line));
11069
+ else if (line.startsWith("-")) console.log(chalk14.red(line));
11070
+ else if (line.startsWith("@@")) console.log(chalk14.cyan(line));
11071
+ else console.log(chalk14.gray(line));
10560
11072
  }
10561
11073
  console.log("");
10562
11074
  } else {
10563
11075
  console.log(
10564
- chalk13.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
11076
+ chalk14.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
10565
11077
  );
10566
11078
  }
10567
11079
  const { confirm: confirm3 } = await import("@inquirer/prompts");
10568
11080
  const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10569
11081
  if (proceed) {
10570
11082
  if (applyUndo(snapshot.hash, snapshot.cwd)) {
10571
- console.log(chalk13.green("\n\u2705 Reverted successfully.\n"));
11083
+ console.log(chalk14.green("\n\u2705 Reverted successfully.\n"));
10572
11084
  } else {
10573
- console.error(chalk13.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
11085
+ console.error(chalk14.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10574
11086
  }
10575
11087
  } else {
10576
- console.log(chalk13.gray("\nCancelled.\n"));
11088
+ console.log(chalk14.gray("\nCancelled.\n"));
10577
11089
  }
10578
11090
  return;
10579
11091
  }
@@ -10583,7 +11095,7 @@ function registerUndoCommand(program2) {
10583
11095
 
10584
11096
  // src/cli/commands/watch.ts
10585
11097
  init_daemon();
10586
- import chalk14 from "chalk";
11098
+ import chalk15 from "chalk";
10587
11099
  import { spawn as spawn8, spawnSync as spawnSync5 } from "child_process";
10588
11100
  function registerWatchCommand(program2) {
10589
11101
  program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
@@ -10599,7 +11111,7 @@ function registerWatchCommand(program2) {
10599
11111
  throw new Error("not running");
10600
11112
  }
10601
11113
  } catch {
10602
- console.error(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
11114
+ console.error(chalk15.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10603
11115
  const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
10604
11116
  detached: true,
10605
11117
  stdio: "ignore",
@@ -10621,12 +11133,12 @@ function registerWatchCommand(program2) {
10621
11133
  }
10622
11134
  }
10623
11135
  if (!ready) {
10624
- console.error(chalk14.red("\u274C Daemon failed to start. Try: node9 daemon start"));
11136
+ console.error(chalk15.red("\u274C Daemon failed to start. Try: node9 daemon start"));
10625
11137
  process.exit(1);
10626
11138
  }
10627
11139
  }
10628
11140
  console.error(
10629
- chalk14.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk14.dim(` \u2192 localhost:${port}`) + chalk14.dim(
11141
+ chalk15.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk15.dim(` \u2192 localhost:${port}`) + chalk15.dim(
10630
11142
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
10631
11143
  )
10632
11144
  );
@@ -10635,7 +11147,7 @@ function registerWatchCommand(program2) {
10635
11147
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
10636
11148
  });
10637
11149
  if (result.error) {
10638
- console.error(chalk14.red(`\u274C Failed to run command: ${result.error.message}`));
11150
+ console.error(chalk15.red(`\u274C Failed to run command: ${result.error.message}`));
10639
11151
  process.exit(1);
10640
11152
  }
10641
11153
  process.exit(result.status ?? 0);
@@ -10645,18 +11157,18 @@ function registerWatchCommand(program2) {
10645
11157
  // src/mcp-gateway/index.ts
10646
11158
  init_orchestrator();
10647
11159
  import readline3 from "readline";
10648
- import chalk15 from "chalk";
11160
+ import chalk16 from "chalk";
10649
11161
  import { spawn as spawn9 } from "child_process";
10650
11162
  import { execa as execa2 } from "execa";
10651
11163
  init_provenance();
10652
11164
 
10653
11165
  // src/mcp-pin.ts
10654
- import fs24 from "fs";
10655
- import path27 from "path";
10656
- import os20 from "os";
10657
- import crypto3 from "crypto";
11166
+ import fs26 from "fs";
11167
+ import path29 from "path";
11168
+ import os22 from "os";
11169
+ import crypto4 from "crypto";
10658
11170
  function getPinsFilePath() {
10659
- return path27.join(os20.homedir(), ".node9", "mcp-pins.json");
11171
+ return path29.join(os22.homedir(), ".node9", "mcp-pins.json");
10660
11172
  }
10661
11173
  function hashToolDefinitions(tools) {
10662
11174
  const sorted = [...tools].sort((a, b) => {
@@ -10665,15 +11177,15 @@ function hashToolDefinitions(tools) {
10665
11177
  return nameA.localeCompare(nameB);
10666
11178
  });
10667
11179
  const canonical = JSON.stringify(sorted);
10668
- return crypto3.createHash("sha256").update(canonical).digest("hex");
11180
+ return crypto4.createHash("sha256").update(canonical).digest("hex");
10669
11181
  }
10670
11182
  function getServerKey(upstreamCommand) {
10671
- return crypto3.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
11183
+ return crypto4.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
10672
11184
  }
10673
11185
  function readMcpPinsSafe() {
10674
11186
  const filePath = getPinsFilePath();
10675
11187
  try {
10676
- const raw = fs24.readFileSync(filePath, "utf-8");
11188
+ const raw = fs26.readFileSync(filePath, "utf-8");
10677
11189
  if (!raw.trim()) {
10678
11190
  return { ok: false, reason: "corrupt", detail: "empty file" };
10679
11191
  }
@@ -10697,10 +11209,10 @@ function readMcpPins() {
10697
11209
  }
10698
11210
  function writeMcpPins(data) {
10699
11211
  const filePath = getPinsFilePath();
10700
- fs24.mkdirSync(path27.dirname(filePath), { recursive: true });
10701
- const tmp = `${filePath}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
10702
- fs24.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10703
- fs24.renameSync(tmp, filePath);
11212
+ fs26.mkdirSync(path29.dirname(filePath), { recursive: true });
11213
+ const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
11214
+ fs26.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11215
+ fs26.renameSync(tmp, filePath);
10704
11216
  }
10705
11217
  function checkPin(serverKey, currentHash) {
10706
11218
  const result = readMcpPinsSafe();
@@ -10792,13 +11304,13 @@ async function runMcpGateway(upstreamCommand) {
10792
11304
  const prov = checkProvenance(executable);
10793
11305
  if (prov.trustLevel === "suspect") {
10794
11306
  console.error(
10795
- chalk15.red(
11307
+ chalk16.red(
10796
11308
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
10797
11309
  )
10798
11310
  );
10799
- console.error(chalk15.red(" Verify this binary is trusted before proceeding."));
11311
+ console.error(chalk16.red(" Verify this binary is trusted before proceeding."));
10800
11312
  }
10801
- console.error(chalk15.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
11313
+ console.error(chalk16.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10802
11314
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
10803
11315
  "NODE_OPTIONS",
10804
11316
  "NODE_PATH",
@@ -10901,10 +11413,10 @@ async function runMcpGateway(upstreamCommand) {
10901
11413
  mcpServer
10902
11414
  });
10903
11415
  if (!result.approved) {
10904
- console.error(chalk15.red(`
11416
+ console.error(chalk16.red(`
10905
11417
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
10906
- console.error(chalk15.gray(` Tool: ${toolName}`));
10907
- console.error(chalk15.gray(` Reason: ${result.reason ?? "Security Policy"}
11418
+ console.error(chalk16.gray(` Tool: ${toolName}`));
11419
+ console.error(chalk16.gray(` Reason: ${result.reason ?? "Security Policy"}
10908
11420
  `));
10909
11421
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
10910
11422
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -10983,7 +11495,7 @@ async function runMcpGateway(upstreamCommand) {
10983
11495
  updatePin(serverKey, upstreamCommand, currentHash, toolNames);
10984
11496
  pinState = "validated";
10985
11497
  console.error(
10986
- chalk15.green(
11498
+ chalk16.green(
10987
11499
  `\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
10988
11500
  )
10989
11501
  );
@@ -10996,11 +11508,11 @@ async function runMcpGateway(upstreamCommand) {
10996
11508
  } else if (pinStatus === "corrupt") {
10997
11509
  pinState = "quarantined";
10998
11510
  console.error(
10999
- chalk15.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11511
+ chalk16.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11000
11512
  );
11001
- console.error(chalk15.red(" Tool calls are blocked until the pin file is repaired."));
11513
+ console.error(chalk16.red(" Tool calls are blocked until the pin file is repaired."));
11002
11514
  console.error(
11003
- chalk15.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11515
+ chalk16.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11004
11516
  `)
11005
11517
  );
11006
11518
  const errorResponse = {
@@ -11017,13 +11529,13 @@ async function runMcpGateway(upstreamCommand) {
11017
11529
  } else {
11018
11530
  pinState = "quarantined";
11019
11531
  console.error(
11020
- chalk15.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11532
+ chalk16.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11021
11533
  );
11022
11534
  console.error(
11023
- chalk15.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11535
+ chalk16.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11024
11536
  );
11025
- console.error(chalk15.red(" Session quarantined \u2014 all tool calls blocked."));
11026
- console.error(chalk15.yellow(` Run: node9 mcp pin update ${serverKey}
11537
+ console.error(chalk16.red(" Session quarantined \u2014 all tool calls blocked."));
11538
+ console.error(chalk16.yellow(` Run: node9 mcp pin update ${serverKey}
11027
11539
  `));
11028
11540
  const errorResponse = {
11029
11541
  jsonrpc: "2.0",
@@ -11072,9 +11584,9 @@ function registerMcpGatewayCommand(program2) {
11072
11584
 
11073
11585
  // src/mcp-server/index.ts
11074
11586
  import readline4 from "readline";
11075
- import fs25 from "fs";
11076
- import os21 from "os";
11077
- import path28 from "path";
11587
+ import fs27 from "fs";
11588
+ import os23 from "os";
11589
+ import path30 from "path";
11078
11590
  init_core();
11079
11591
  init_daemon();
11080
11592
  init_shields();
@@ -11249,13 +11761,13 @@ function handleStatus() {
11249
11761
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11250
11762
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11251
11763
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11252
- const projectConfig = path28.join(process.cwd(), "node9.config.json");
11253
- const globalConfig = path28.join(os21.homedir(), ".node9", "config.json");
11764
+ const projectConfig = path30.join(process.cwd(), "node9.config.json");
11765
+ const globalConfig = path30.join(os23.homedir(), ".node9", "config.json");
11254
11766
  lines.push(
11255
- `Project config (node9.config.json): ${fs25.existsSync(projectConfig) ? "present" : "not found"}`
11767
+ `Project config (node9.config.json): ${fs27.existsSync(projectConfig) ? "present" : "not found"}`
11256
11768
  );
11257
11769
  lines.push(
11258
- `Global config (~/.node9/config.json): ${fs25.existsSync(globalConfig) ? "present" : "not found"}`
11770
+ `Global config (~/.node9/config.json): ${fs27.existsSync(globalConfig) ? "present" : "not found"}`
11259
11771
  );
11260
11772
  return lines.join("\n");
11261
11773
  }
@@ -11329,21 +11841,21 @@ function handleShieldDisable(args) {
11329
11841
  writeActiveShields(active.filter((s) => s !== name));
11330
11842
  return `Shield "${name}" disabled.`;
11331
11843
  }
11332
- var GLOBAL_CONFIG_PATH2 = path28.join(os21.homedir(), ".node9", "config.json");
11844
+ var GLOBAL_CONFIG_PATH2 = path30.join(os23.homedir(), ".node9", "config.json");
11333
11845
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11334
11846
  function readGlobalConfigRaw() {
11335
11847
  try {
11336
- if (fs25.existsSync(GLOBAL_CONFIG_PATH2)) {
11337
- return JSON.parse(fs25.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11848
+ if (fs27.existsSync(GLOBAL_CONFIG_PATH2)) {
11849
+ return JSON.parse(fs27.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11338
11850
  }
11339
11851
  } catch {
11340
11852
  }
11341
11853
  return {};
11342
11854
  }
11343
11855
  function writeGlobalConfigRaw(data) {
11344
- const dir = path28.dirname(GLOBAL_CONFIG_PATH2);
11345
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
11346
- fs25.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11856
+ const dir = path30.dirname(GLOBAL_CONFIG_PATH2);
11857
+ if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
11858
+ fs27.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11347
11859
  }
11348
11860
  function handleApproverList() {
11349
11861
  const config = getConfig();
@@ -11386,9 +11898,9 @@ function handleApproverSet(args) {
11386
11898
  }
11387
11899
  function handleAuditGet(args) {
11388
11900
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11389
- const auditPath = path28.join(os21.homedir(), ".node9", "audit.log");
11390
- if (!fs25.existsSync(auditPath)) return "No audit log found.";
11391
- const lines = fs25.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11901
+ const auditPath = path30.join(os23.homedir(), ".node9", "audit.log");
11902
+ if (!fs27.existsSync(auditPath)) return "No audit log found.";
11903
+ const lines = fs27.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11392
11904
  const recent = lines.slice(-limit);
11393
11905
  const entries = recent.map((line) => {
11394
11906
  try {
@@ -11576,7 +12088,7 @@ function registerMcpServerCommand(program2) {
11576
12088
 
11577
12089
  // src/cli/commands/trust.ts
11578
12090
  init_trusted_hosts();
11579
- import chalk16 from "chalk";
12091
+ import chalk17 from "chalk";
11580
12092
  function isValidHost(host) {
11581
12093
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
11582
12094
  }
@@ -11586,51 +12098,51 @@ function registerTrustCommand(program2) {
11586
12098
  const normalized = normalizeHost(host.trim());
11587
12099
  if (!isValidHost(normalized)) {
11588
12100
  console.error(
11589
- chalk16.red(`
12101
+ chalk17.red(`
11590
12102
  \u274C Invalid host: "${host}"
11591
- `) + chalk16.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
12103
+ `) + chalk17.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
11592
12104
  );
11593
12105
  process.exit(1);
11594
12106
  }
11595
12107
  addTrustedHost(normalized);
11596
- console.log(chalk16.green(`
12108
+ console.log(chalk17.green(`
11597
12109
  \u2705 ${normalized} added to trusted hosts.`));
11598
12110
  console.log(
11599
- chalk16.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
12111
+ chalk17.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
11600
12112
  );
11601
12113
  });
11602
12114
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
11603
12115
  const normalized = normalizeHost(host.trim());
11604
12116
  const removed = removeTrustedHost(normalized);
11605
12117
  if (!removed) {
11606
- console.error(chalk16.yellow(`
12118
+ console.error(chalk17.yellow(`
11607
12119
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
11608
12120
  `));
11609
12121
  process.exit(1);
11610
12122
  }
11611
- console.log(chalk16.green(`
12123
+ console.log(chalk17.green(`
11612
12124
  \u2705 ${normalized} removed from trusted hosts.
11613
12125
  `));
11614
12126
  });
11615
12127
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
11616
12128
  const hosts = readTrustedHosts();
11617
12129
  if (hosts.length === 0) {
11618
- console.log(chalk16.gray("\n No trusted hosts configured.\n"));
11619
- console.log(` Add one: ${chalk16.cyan("node9 trust add api.mycompany.com")}
12130
+ console.log(chalk17.gray("\n No trusted hosts configured.\n"));
12131
+ console.log(` Add one: ${chalk17.cyan("node9 trust add api.mycompany.com")}
11620
12132
  `);
11621
12133
  return;
11622
12134
  }
11623
- console.log(chalk16.bold("\n\u{1F513} Trusted Hosts\n"));
12135
+ console.log(chalk17.bold("\n\u{1F513} Trusted Hosts\n"));
11624
12136
  for (const entry of hosts) {
11625
12137
  const date = new Date(entry.addedAt).toLocaleDateString();
11626
- console.log(` ${chalk16.cyan(entry.host.padEnd(40))} ${chalk16.gray(`added ${date}`)}`);
12138
+ console.log(` ${chalk17.cyan(entry.host.padEnd(40))} ${chalk17.gray(`added ${date}`)}`);
11627
12139
  }
11628
12140
  console.log("");
11629
12141
  });
11630
12142
  }
11631
12143
 
11632
12144
  // src/cli/commands/mcp-pin.ts
11633
- import chalk17 from "chalk";
12145
+ import chalk18 from "chalk";
11634
12146
  function registerMcpPinCommand(program2) {
11635
12147
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
11636
12148
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -11638,31 +12150,31 @@ function registerMcpPinCommand(program2) {
11638
12150
  const result = readMcpPinsSafe();
11639
12151
  if (!result.ok) {
11640
12152
  if (result.reason === "missing") {
11641
- console.log(chalk17.gray("\nNo MCP servers are pinned yet."));
12153
+ console.log(chalk18.gray("\nNo MCP servers are pinned yet."));
11642
12154
  console.log(
11643
- chalk17.gray("Pins are created automatically when the MCP gateway first connects.\n")
12155
+ chalk18.gray("Pins are created automatically when the MCP gateway first connects.\n")
11644
12156
  );
11645
12157
  return;
11646
12158
  }
11647
- console.error(chalk17.red(`
12159
+ console.error(chalk18.red(`
11648
12160
  \u274C Pin file is corrupt: ${result.detail}`));
11649
- console.error(chalk17.yellow(" Run: node9 mcp pin reset\n"));
12161
+ console.error(chalk18.yellow(" Run: node9 mcp pin reset\n"));
11650
12162
  process.exit(1);
11651
12163
  }
11652
12164
  const entries = Object.entries(result.pins.servers);
11653
12165
  if (entries.length === 0) {
11654
- console.log(chalk17.gray("\nNo MCP servers are pinned yet."));
12166
+ console.log(chalk18.gray("\nNo MCP servers are pinned yet."));
11655
12167
  console.log(
11656
- chalk17.gray("Pins are created automatically when the MCP gateway first connects.\n")
12168
+ chalk18.gray("Pins are created automatically when the MCP gateway first connects.\n")
11657
12169
  );
11658
12170
  return;
11659
12171
  }
11660
- console.log(chalk17.bold("\n\u{1F512} Pinned MCP Servers\n"));
12172
+ console.log(chalk18.bold("\n\u{1F512} Pinned MCP Servers\n"));
11661
12173
  for (const [key, entry] of entries) {
11662
- console.log(` ${chalk17.cyan(key)} ${chalk17.gray(entry.label)}`);
11663
- console.log(` Tools (${entry.toolCount}): ${chalk17.white(entry.toolNames.join(", "))}`);
11664
- console.log(` Hash: ${chalk17.gray(entry.toolsHash.slice(0, 16))}...`);
11665
- console.log(` Pinned: ${chalk17.gray(entry.pinnedAt)}`);
12174
+ console.log(` ${chalk18.cyan(key)} ${chalk18.gray(entry.label)}`);
12175
+ console.log(` Tools (${entry.toolCount}): ${chalk18.white(entry.toolNames.join(", "))}`);
12176
+ console.log(` Hash: ${chalk18.gray(entry.toolsHash.slice(0, 16))}...`);
12177
+ console.log(` Pinned: ${chalk18.gray(entry.pinnedAt)}`);
11666
12178
  console.log("");
11667
12179
  }
11668
12180
  });
@@ -11673,55 +12185,55 @@ function registerMcpPinCommand(program2) {
11673
12185
  try {
11674
12186
  pins = readMcpPins();
11675
12187
  } catch {
11676
- console.error(chalk17.red("\n\u274C Pin file is corrupt."));
11677
- console.error(chalk17.yellow(" Run: node9 mcp pin reset\n"));
12188
+ console.error(chalk18.red("\n\u274C Pin file is corrupt."));
12189
+ console.error(chalk18.yellow(" Run: node9 mcp pin reset\n"));
11678
12190
  process.exit(1);
11679
12191
  }
11680
12192
  if (!pins.servers[serverKey]) {
11681
- console.error(chalk17.red(`
12193
+ console.error(chalk18.red(`
11682
12194
  \u274C No pin found for server key "${serverKey}"
11683
12195
  `));
11684
- console.error(`Run ${chalk17.cyan("node9 mcp pin list")} to see pinned servers.
12196
+ console.error(`Run ${chalk18.cyan("node9 mcp pin list")} to see pinned servers.
11685
12197
  `);
11686
12198
  process.exit(1);
11687
12199
  }
11688
12200
  const label = pins.servers[serverKey].label;
11689
12201
  removePin(serverKey);
11690
- console.log(chalk17.green(`
11691
- \u{1F513} Pin removed for ${chalk17.cyan(serverKey)}`));
11692
- console.log(chalk17.gray(` Server: ${label}`));
11693
- console.log(chalk17.gray(" Next connection will re-pin with current tool definitions.\n"));
12202
+ console.log(chalk18.green(`
12203
+ \u{1F513} Pin removed for ${chalk18.cyan(serverKey)}`));
12204
+ console.log(chalk18.gray(` Server: ${label}`));
12205
+ console.log(chalk18.gray(" Next connection will re-pin with current tool definitions.\n"));
11694
12206
  });
11695
12207
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
11696
12208
  const result = readMcpPinsSafe();
11697
12209
  if (!result.ok && result.reason === "missing") {
11698
- console.log(chalk17.gray("\nNo pins to clear.\n"));
12210
+ console.log(chalk18.gray("\nNo pins to clear.\n"));
11699
12211
  return;
11700
12212
  }
11701
12213
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
11702
12214
  clearAllPins();
11703
- console.log(chalk17.green(`
12215
+ console.log(chalk18.green(`
11704
12216
  \u{1F513} Cleared ${count} MCP pin(s).`));
11705
- console.log(chalk17.gray(" Next connection to each server will re-pin.\n"));
12217
+ console.log(chalk18.gray(" Next connection to each server will re-pin.\n"));
11706
12218
  });
11707
12219
  }
11708
12220
 
11709
12221
  // src/cli.ts
11710
12222
  var { version } = JSON.parse(
11711
- fs28.readFileSync(path31.join(__dirname, "../package.json"), "utf-8")
12223
+ fs30.readFileSync(path33.join(__dirname, "../package.json"), "utf-8")
11712
12224
  );
11713
12225
  var program = new Command();
11714
12226
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
11715
12227
  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) => {
11716
12228
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
11717
- const credPath = path31.join(os24.homedir(), ".node9", "credentials.json");
11718
- if (!fs28.existsSync(path31.dirname(credPath)))
11719
- fs28.mkdirSync(path31.dirname(credPath), { recursive: true });
12229
+ const credPath = path33.join(os26.homedir(), ".node9", "credentials.json");
12230
+ if (!fs30.existsSync(path33.dirname(credPath)))
12231
+ fs30.mkdirSync(path33.dirname(credPath), { recursive: true });
11720
12232
  const profileName = options.profile || "default";
11721
12233
  let existingCreds = {};
11722
12234
  try {
11723
- if (fs28.existsSync(credPath)) {
11724
- const raw = JSON.parse(fs28.readFileSync(credPath, "utf-8"));
12235
+ if (fs30.existsSync(credPath)) {
12236
+ const raw = JSON.parse(fs30.readFileSync(credPath, "utf-8"));
11725
12237
  if (raw.apiKey) {
11726
12238
  existingCreds = {
11727
12239
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -11733,13 +12245,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11733
12245
  } catch {
11734
12246
  }
11735
12247
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
11736
- fs28.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12248
+ fs30.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
11737
12249
  if (profileName === "default") {
11738
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
12250
+ const configPath = path33.join(os26.homedir(), ".node9", "config.json");
11739
12251
  let config = {};
11740
12252
  try {
11741
- if (fs28.existsSync(configPath))
11742
- config = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
12253
+ if (fs30.existsSync(configPath))
12254
+ config = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
11743
12255
  } catch {
11744
12256
  }
11745
12257
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -11754,19 +12266,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11754
12266
  approvers.cloud = false;
11755
12267
  }
11756
12268
  s.approvers = approvers;
11757
- if (!fs28.existsSync(path31.dirname(configPath)))
11758
- fs28.mkdirSync(path31.dirname(configPath), { recursive: true });
11759
- fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12269
+ if (!fs30.existsSync(path33.dirname(configPath)))
12270
+ fs30.mkdirSync(path33.dirname(configPath), { recursive: true });
12271
+ fs30.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
11760
12272
  }
11761
12273
  if (options.profile && profileName !== "default") {
11762
- console.log(chalk19.green(`\u2705 Profile "${profileName}" saved`));
11763
- console.log(chalk19.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
12274
+ console.log(chalk20.green(`\u2705 Profile "${profileName}" saved`));
12275
+ console.log(chalk20.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
11764
12276
  } else if (options.local) {
11765
- console.log(chalk19.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
11766
- console.log(chalk19.gray(` All decisions stay on this machine.`));
12277
+ console.log(chalk20.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
12278
+ console.log(chalk20.gray(` All decisions stay on this machine.`));
11767
12279
  } else {
11768
- console.log(chalk19.green(`\u2705 Logged in \u2014 agent mode`));
11769
- console.log(chalk19.gray(` Team policy enforced for all calls via Node9 cloud.`));
12280
+ console.log(chalk20.green(`\u2705 Logged in \u2014 agent mode`));
12281
+ console.log(chalk20.gray(` Team policy enforced for all calls via Node9 cloud.`));
11770
12282
  }
11771
12283
  });
11772
12284
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
@@ -11774,19 +12286,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
11774
12286
  if (target === "claude") return await setupClaude();
11775
12287
  if (target === "cursor") return await setupCursor();
11776
12288
  if (target === "hud") return setupHud();
11777
- console.error(chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12289
+ console.error(chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11778
12290
  process.exit(1);
11779
12291
  });
11780
12292
  program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
11781
12293
  if (!target) {
11782
- console.log(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
11783
- console.log(" Usage: " + chalk19.white("node9 setup <target>") + "\n");
12294
+ console.log(chalk20.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
12295
+ console.log(" Usage: " + chalk20.white("node9 setup <target>") + "\n");
11784
12296
  console.log(" Targets:");
11785
- console.log(" " + chalk19.green("claude") + " \u2014 Claude Code (hook mode)");
11786
- console.log(" " + chalk19.green("gemini") + " \u2014 Gemini CLI (hook mode)");
11787
- console.log(" " + chalk19.green("cursor") + " \u2014 Cursor (hook mode)");
12297
+ console.log(" " + chalk20.green("claude") + " \u2014 Claude Code (hook mode)");
12298
+ console.log(" " + chalk20.green("gemini") + " \u2014 Gemini CLI (hook mode)");
12299
+ console.log(" " + chalk20.green("cursor") + " \u2014 Cursor (hook mode)");
11788
12300
  process.stdout.write(
11789
- " " + chalk19.green("hud") + " \u2014 Claude Code security statusline\n"
12301
+ " " + chalk20.green("hud") + " \u2014 Claude Code security statusline\n"
11790
12302
  );
11791
12303
  console.log("");
11792
12304
  return;
@@ -11796,7 +12308,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
11796
12308
  if (t === "claude") return await setupClaude();
11797
12309
  if (t === "cursor") return await setupCursor();
11798
12310
  if (t === "hud") return setupHud();
11799
- console.error(chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12311
+ console.error(chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11800
12312
  process.exit(1);
11801
12313
  });
11802
12314
  program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
@@ -11807,31 +12319,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
11807
12319
  else if (target === "hud") fn = teardownHud;
11808
12320
  else {
11809
12321
  console.error(
11810
- chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
12322
+ chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
11811
12323
  );
11812
12324
  process.exit(1);
11813
12325
  }
11814
- console.log(chalk19.cyan(`
12326
+ console.log(chalk20.cyan(`
11815
12327
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
11816
12328
  `));
11817
12329
  try {
11818
12330
  fn();
11819
12331
  } catch (err2) {
11820
- console.error(chalk19.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
12332
+ console.error(chalk20.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
11821
12333
  process.exit(1);
11822
12334
  }
11823
- console.log(chalk19.gray("\n Restart the agent for changes to take effect."));
12335
+ console.log(chalk20.gray("\n Restart the agent for changes to take effect."));
11824
12336
  });
11825
12337
  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) => {
11826
- console.log(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
11827
- console.log(chalk19.bold("Stopping daemon..."));
12338
+ console.log(chalk20.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
12339
+ console.log(chalk20.bold("Stopping daemon..."));
11828
12340
  try {
11829
12341
  stopDaemon();
11830
- console.log(chalk19.green(" \u2705 Daemon stopped"));
12342
+ console.log(chalk20.green(" \u2705 Daemon stopped"));
11831
12343
  } catch {
11832
- console.log(chalk19.blue(" \u2139\uFE0F Daemon was not running"));
12344
+ console.log(chalk20.blue(" \u2139\uFE0F Daemon was not running"));
11833
12345
  }
11834
- console.log(chalk19.bold("\nRemoving hooks..."));
12346
+ console.log(chalk20.bold("\nRemoving hooks..."));
11835
12347
  let teardownFailed = false;
11836
12348
  for (const [label, fn] of [
11837
12349
  ["Claude", teardownClaude],
@@ -11843,45 +12355,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
11843
12355
  } catch (err2) {
11844
12356
  teardownFailed = true;
11845
12357
  console.error(
11846
- chalk19.red(
12358
+ chalk20.red(
11847
12359
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
11848
12360
  )
11849
12361
  );
11850
12362
  }
11851
12363
  }
11852
12364
  if (options.purge) {
11853
- const node9Dir = path31.join(os24.homedir(), ".node9");
11854
- if (fs28.existsSync(node9Dir)) {
12365
+ const node9Dir = path33.join(os26.homedir(), ".node9");
12366
+ if (fs30.existsSync(node9Dir)) {
11855
12367
  const confirmed = await confirm2({
11856
12368
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
11857
12369
  default: false
11858
12370
  });
11859
12371
  if (confirmed) {
11860
- fs28.rmSync(node9Dir, { recursive: true });
11861
- if (fs28.existsSync(node9Dir)) {
12372
+ fs30.rmSync(node9Dir, { recursive: true });
12373
+ if (fs30.existsSync(node9Dir)) {
11862
12374
  console.error(
11863
- chalk19.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12375
+ chalk20.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
11864
12376
  );
11865
12377
  } else {
11866
- console.log(chalk19.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
12378
+ console.log(chalk20.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
11867
12379
  }
11868
12380
  } else {
11869
- console.log(chalk19.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12381
+ console.log(chalk20.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
11870
12382
  }
11871
12383
  } else {
11872
- console.log(chalk19.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
12384
+ console.log(chalk20.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
11873
12385
  }
11874
12386
  } else {
11875
12387
  console.log(
11876
- chalk19.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
12388
+ chalk20.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
11877
12389
  );
11878
12390
  }
11879
12391
  if (teardownFailed) {
11880
- console.error(chalk19.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
12392
+ console.error(chalk20.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
11881
12393
  process.exit(1);
11882
12394
  }
11883
- console.log(chalk19.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
11884
- console.log(chalk19.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
12395
+ console.log(chalk20.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
12396
+ console.log(chalk20.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
11885
12397
  });
11886
12398
  registerDoctorCommand(program, version);
11887
12399
  program.command("explain").description(
@@ -11894,7 +12406,7 @@ program.command("explain").description(
11894
12406
  try {
11895
12407
  args = JSON.parse(trimmed);
11896
12408
  } catch {
11897
- console.error(chalk19.red(`
12409
+ console.error(chalk20.red(`
11898
12410
  \u274C Invalid JSON: ${trimmed}
11899
12411
  `));
11900
12412
  process.exit(1);
@@ -11905,60 +12417,61 @@ program.command("explain").description(
11905
12417
  }
11906
12418
  const result = await explainPolicy(tool, args);
11907
12419
  console.log("");
11908
- console.log(chalk19.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12420
+ console.log(chalk20.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
11909
12421
  console.log("");
11910
- console.log(` ${chalk19.bold("Tool:")} ${chalk19.white(result.tool)}`);
12422
+ console.log(` ${chalk20.bold("Tool:")} ${chalk20.white(result.tool)}`);
11911
12423
  if (argsRaw) {
11912
12424
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
11913
- console.log(` ${chalk19.bold("Input:")} ${chalk19.gray(preview)}`);
12425
+ console.log(` ${chalk20.bold("Input:")} ${chalk20.gray(preview)}`);
11914
12426
  }
11915
12427
  console.log("");
11916
- console.log(chalk19.bold("Config Sources (Waterfall):"));
12428
+ console.log(chalk20.bold("Config Sources (Waterfall):"));
11917
12429
  for (const tier of result.waterfall) {
11918
- const num = chalk19.gray(` ${tier.tier}.`);
12430
+ const num2 = chalk20.gray(` ${tier.tier}.`);
11919
12431
  const label = tier.label.padEnd(16);
11920
12432
  let statusStr;
11921
12433
  if (tier.tier === 1) {
11922
- statusStr = chalk19.gray(tier.note ?? "");
12434
+ statusStr = chalk20.gray(tier.note ?? "");
11923
12435
  } else if (tier.status === "active") {
11924
- const loc = tier.path ? chalk19.gray(tier.path) : "";
11925
- const note = tier.note ? chalk19.gray(`(${tier.note})`) : "";
11926
- statusStr = chalk19.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
12436
+ const loc = tier.path ? chalk20.gray(tier.path) : "";
12437
+ const note = tier.note ? chalk20.gray(`(${tier.note})`) : "";
12438
+ statusStr = chalk20.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
11927
12439
  } else {
11928
- statusStr = chalk19.gray("\u25CB " + (tier.note ?? "not found"));
12440
+ statusStr = chalk20.gray("\u25CB " + (tier.note ?? "not found"));
11929
12441
  }
11930
- console.log(`${num} ${chalk19.white(label)} ${statusStr}`);
12442
+ console.log(`${num2} ${chalk20.white(label)} ${statusStr}`);
11931
12443
  }
11932
12444
  console.log("");
11933
- console.log(chalk19.bold("Policy Evaluation:"));
12445
+ console.log(chalk20.bold("Policy Evaluation:"));
11934
12446
  for (const step of result.steps) {
11935
12447
  const isFinal = step.isFinal;
11936
12448
  let icon;
11937
- if (step.outcome === "allow") icon = chalk19.green(" \u2705");
11938
- else if (step.outcome === "review") icon = chalk19.red(" \u{1F534}");
11939
- else if (step.outcome === "skip") icon = chalk19.gray(" \u2500 ");
11940
- else icon = chalk19.gray(" \u25CB ");
12449
+ if (step.outcome === "allow") icon = chalk20.green(" \u2705");
12450
+ else if (step.outcome === "review") icon = chalk20.red(" \u{1F534}");
12451
+ else if (step.outcome === "skip") icon = chalk20.gray(" \u2500 ");
12452
+ else icon = chalk20.gray(" \u25CB ");
11941
12453
  const name = step.name.padEnd(18);
11942
- const nameStr = isFinal ? chalk19.white.bold(name) : chalk19.white(name);
11943
- const detail = isFinal ? chalk19.white(step.detail) : chalk19.gray(step.detail);
11944
- const arrow = isFinal ? chalk19.yellow(" \u2190 STOP") : "";
12454
+ const nameStr = isFinal ? chalk20.white.bold(name) : chalk20.white(name);
12455
+ const detail = isFinal ? chalk20.white(step.detail) : chalk20.gray(step.detail);
12456
+ const arrow = isFinal ? chalk20.yellow(" \u2190 STOP") : "";
11945
12457
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
11946
12458
  }
11947
12459
  console.log("");
11948
12460
  if (result.decision === "allow") {
11949
- console.log(chalk19.green.bold(" Decision: \u2705 ALLOW") + chalk19.gray(" \u2014 no approval needed"));
12461
+ console.log(chalk20.green.bold(" Decision: \u2705 ALLOW") + chalk20.gray(" \u2014 no approval needed"));
11950
12462
  } else {
11951
12463
  console.log(
11952
- chalk19.red.bold(" Decision: \u{1F534} REVIEW") + chalk19.gray(" \u2014 human approval required")
12464
+ chalk20.red.bold(" Decision: \u{1F534} REVIEW") + chalk20.gray(" \u2014 human approval required")
11953
12465
  );
11954
12466
  if (result.blockedByLabel) {
11955
- console.log(chalk19.gray(` Reason: ${result.blockedByLabel}`));
12467
+ console.log(chalk20.gray(` Reason: ${result.blockedByLabel}`));
11956
12468
  }
11957
12469
  }
11958
12470
  console.log("");
11959
12471
  });
11960
12472
  registerInitCommand(program);
11961
12473
  registerAuditCommand(program);
12474
+ registerReportCommand(program);
11962
12475
  registerStatusCommand(program);
11963
12476
  registerDaemonCommand(program);
11964
12477
  program.command("tail").description("Stream live agent activity to the terminal").option("--history", "Replay recent history then continue live", false).option("--clear", "Clear the history buffer and exit (does not stream)", false).action(async (options) => {
@@ -11966,7 +12479,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
11966
12479
  try {
11967
12480
  await startTail2(options);
11968
12481
  } catch (err2) {
11969
- console.error(chalk19.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
12482
+ console.error(chalk20.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
11970
12483
  process.exit(1);
11971
12484
  }
11972
12485
  });
@@ -11998,14 +12511,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
11998
12511
  Run "node9 addto claude" to register it as the statusLine.`
11999
12512
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12000
12513
  if (subcommand === "debug") {
12001
- const flagFile = path31.join(os24.homedir(), ".node9", "hud-debug");
12514
+ const flagFile = path33.join(os26.homedir(), ".node9", "hud-debug");
12002
12515
  if (state === "on") {
12003
- fs28.mkdirSync(path31.dirname(flagFile), { recursive: true });
12004
- fs28.writeFileSync(flagFile, "");
12516
+ fs30.mkdirSync(path33.dirname(flagFile), { recursive: true });
12517
+ fs30.writeFileSync(flagFile, "");
12005
12518
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12006
12519
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12007
12520
  } else if (state === "off") {
12008
- if (fs28.existsSync(flagFile)) fs28.unlinkSync(flagFile);
12521
+ if (fs30.existsSync(flagFile)) fs30.unlinkSync(flagFile);
12009
12522
  console.log("HUD debug logging disabled.");
12010
12523
  } else {
12011
12524
  console.error("Usage: node9 hud debug on|off");
@@ -12020,7 +12533,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12020
12533
  const ms = parseDuration(options.duration);
12021
12534
  if (ms === null) {
12022
12535
  console.error(
12023
- chalk19.red(`
12536
+ chalk20.red(`
12024
12537
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12025
12538
  `)
12026
12539
  );
@@ -12028,20 +12541,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12028
12541
  }
12029
12542
  pauseNode9(ms, options.duration);
12030
12543
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12031
- console.log(chalk19.yellow(`
12544
+ console.log(chalk20.yellow(`
12032
12545
  \u23F8 Node9 paused until ${expiresAt}`));
12033
- console.log(chalk19.gray(` All tool calls will be allowed without review.`));
12034
- console.log(chalk19.gray(` Run "node9 resume" to re-enable early.
12546
+ console.log(chalk20.gray(` All tool calls will be allowed without review.`));
12547
+ console.log(chalk20.gray(` Run "node9 resume" to re-enable early.
12035
12548
  `));
12036
12549
  });
12037
12550
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12038
12551
  const { paused } = checkPause();
12039
12552
  if (!paused) {
12040
- console.log(chalk19.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12553
+ console.log(chalk20.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12041
12554
  return;
12042
12555
  }
12043
12556
  resumeNode9();
12044
- console.log(chalk19.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12557
+ console.log(chalk20.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12045
12558
  });
12046
12559
  var HOOK_BASED_AGENTS = {
12047
12560
  claude: "claude",
@@ -12054,15 +12567,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12054
12567
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12055
12568
  const target = HOOK_BASED_AGENTS[firstArg2];
12056
12569
  console.error(
12057
- chalk19.yellow(`
12570
+ chalk20.yellow(`
12058
12571
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12059
12572
  );
12060
- console.error(chalk19.white(`
12573
+ console.error(chalk20.white(`
12061
12574
  "${target}" uses its own hook system. Use:`));
12062
12575
  console.error(
12063
- chalk19.green(` node9 addto ${target} `) + chalk19.gray("# one-time setup")
12576
+ chalk20.green(` node9 addto ${target} `) + chalk20.gray("# one-time setup")
12064
12577
  );
12065
- console.error(chalk19.green(` ${target} `) + chalk19.gray("# run normally"));
12578
+ console.error(chalk20.green(` ${target} `) + chalk20.gray("# run normally"));
12066
12579
  process.exit(1);
12067
12580
  }
12068
12581
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12079,7 +12592,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12079
12592
  }
12080
12593
  );
12081
12594
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12082
- console.error(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12595
+ console.error(chalk20.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12083
12596
  const daemonReady = await autoStartDaemonAndWait();
12084
12597
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12085
12598
  }
@@ -12092,12 +12605,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12092
12605
  }
12093
12606
  if (!result.approved) {
12094
12607
  console.error(
12095
- chalk19.red(`
12608
+ chalk20.red(`
12096
12609
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12097
12610
  );
12098
12611
  process.exit(1);
12099
12612
  }
12100
- console.error(chalk19.green("\n\u2705 Approved \u2014 running command...\n"));
12613
+ console.error(chalk20.green("\n\u2705 Approved \u2014 running command...\n"));
12101
12614
  await runProxy(fullCommand);
12102
12615
  } else {
12103
12616
  program.help();
@@ -12112,9 +12625,9 @@ if (process.argv[2] !== "daemon") {
12112
12625
  const isCheckHook = process.argv[2] === "check";
12113
12626
  if (isCheckHook) {
12114
12627
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12115
- const logPath = path31.join(os24.homedir(), ".node9", "hook-debug.log");
12628
+ const logPath = path33.join(os26.homedir(), ".node9", "hook-debug.log");
12116
12629
  const msg = reason instanceof Error ? reason.message : String(reason);
12117
- fs28.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12630
+ fs30.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12118
12631
  `);
12119
12632
  }
12120
12633
  process.exit(0);