@node9/proxy 1.9.2 → 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,
@@ -192,6 +200,7 @@ var init_config_schema = __esm({
192
200
  errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
193
201
  }),
194
202
  reason: z.string().optional(),
203
+ description: z.string().optional(),
195
204
  // Unknown predicate names are filtered out rather than failing the whole rule.
196
205
  // Failing the whole z.array() would cause sanitizeConfig to drop the entire
197
206
  // `policy` top-level key, silently disabling ALL smart rules in the config.
@@ -238,6 +247,11 @@ var init_config_schema = __esm({
238
247
  dlp: z.object({
239
248
  enabled: z.boolean().optional(),
240
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()
241
255
  }).optional()
242
256
  }).optional(),
243
257
  environments: z.record(z.object({ requireApproval: z.boolean().optional() })).optional()
@@ -535,7 +549,8 @@ function getConfig(cwd) {
535
549
  onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
536
550
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
537
551
  },
538
- dlp: { ...DEFAULT_CONFIG.policy.dlp }
552
+ dlp: { ...DEFAULT_CONFIG.policy.dlp },
553
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
539
554
  };
540
555
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
541
556
  const applyLayer = (source) => {
@@ -574,6 +589,13 @@ function getConfig(cwd) {
574
589
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
575
590
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
576
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
+ }
577
599
  const envs = source.environments || {};
578
600
  for (const [envName, envConfig] of Object.entries(envs)) {
579
601
  if (envConfig && typeof envConfig === "object") {
@@ -769,7 +791,8 @@ var init_config = __esm({
769
791
  }
770
792
  ],
771
793
  verdict: "block",
772
- reason: "Recursive delete of home directory is irreversible"
794
+ reason: "Recursive delete of home directory is irreversible",
795
+ description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
773
796
  },
774
797
  // ── SQL safety ────────────────────────────────────────────────────────
775
798
  {
@@ -781,7 +804,8 @@ var init_config = __esm({
781
804
  ],
782
805
  conditionMode: "all",
783
806
  verdict: "review",
784
- reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
807
+ reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
808
+ description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
785
809
  },
786
810
  {
787
811
  name: "review-drop-truncate-shell",
@@ -796,7 +820,8 @@ var init_config = __esm({
796
820
  ],
797
821
  conditionMode: "all",
798
822
  verdict: "review",
799
- reason: "SQL DDL destructive statement inside a shell command"
823
+ reason: "SQL DDL destructive statement inside a shell command",
824
+ description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
800
825
  },
801
826
  // ── Git safety ────────────────────────────────────────────────────────
802
827
  {
@@ -812,7 +837,8 @@ var init_config = __esm({
812
837
  ],
813
838
  conditionMode: "all",
814
839
  verdict: "block",
815
- reason: "Force push overwrites remote history and cannot be undone"
840
+ reason: "Force push overwrites remote history and cannot be undone",
841
+ description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
816
842
  },
817
843
  {
818
844
  name: "review-git-push",
@@ -827,7 +853,8 @@ var init_config = __esm({
827
853
  ],
828
854
  conditionMode: "all",
829
855
  verdict: "review",
830
- reason: "git push sends changes to a shared remote"
856
+ reason: "git push sends changes to a shared remote",
857
+ description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
831
858
  },
832
859
  {
833
860
  name: "review-git-destructive",
@@ -842,7 +869,8 @@ var init_config = __esm({
842
869
  ],
843
870
  conditionMode: "all",
844
871
  verdict: "review",
845
- reason: "Destructive git operation \u2014 discards history or working-tree changes"
872
+ reason: "Destructive git operation \u2014 discards history or working-tree changes",
873
+ description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
846
874
  },
847
875
  // ── Shell safety ──────────────────────────────────────────────────────
848
876
  {
@@ -851,7 +879,8 @@ var init_config = __esm({
851
879
  conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
852
880
  conditionMode: "all",
853
881
  verdict: "review",
854
- reason: "Command requires elevated privileges"
882
+ reason: "Command requires elevated privileges",
883
+ description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
855
884
  },
856
885
  {
857
886
  name: "review-curl-pipe-shell",
@@ -866,10 +895,12 @@ var init_config = __esm({
866
895
  ],
867
896
  conditionMode: "all",
868
897
  verdict: "block",
869
- reason: "Piping remote script into a shell is a supply-chain attack vector"
898
+ reason: "Piping remote script into a shell is a supply-chain attack vector",
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."
870
900
  }
871
901
  ],
872
- dlp: { enabled: true, scanIgnoredTools: true }
902
+ dlp: { enabled: true, scanIgnoredTools: true },
903
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
873
904
  },
874
905
  environments: {}
875
906
  };
@@ -899,7 +930,8 @@ var init_config = __esm({
899
930
  tool: "*",
900
931
  conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
901
932
  verdict: "review",
902
- reason: "rm can permanently delete files \u2014 confirm the target path"
933
+ reason: "rm can permanently delete files \u2014 confirm the target path",
934
+ description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
903
935
  },
904
936
  // ── SQL safety (Safe by Default) ──────────────────────────────────────────
905
937
  // These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
@@ -911,14 +943,16 @@ var init_config = __esm({
911
943
  tool: "*",
912
944
  conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
913
945
  verdict: "review",
914
- reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
946
+ reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
947
+ description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
915
948
  },
916
949
  {
917
950
  name: "review-truncate-sql",
918
951
  tool: "*",
919
952
  conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
920
953
  verdict: "review",
921
- reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
954
+ reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
955
+ description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
922
956
  },
923
957
  {
924
958
  name: "review-drop-column-sql",
@@ -927,7 +961,8 @@ var init_config = __esm({
927
961
  { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
928
962
  ],
929
963
  verdict: "review",
930
- reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
964
+ reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
965
+ description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
931
966
  }
932
967
  ];
933
968
  cachedConfig = null;
@@ -1674,9 +1709,9 @@ function matchesPattern(text, patterns) {
1674
1709
  const withoutDotSlash = text.replace(/^\.\//, "");
1675
1710
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1676
1711
  }
1677
- function getNestedValue(obj, path32) {
1712
+ function getNestedValue(obj, path34) {
1678
1713
  if (!obj || typeof obj !== "object") return null;
1679
- return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
1714
+ return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
1680
1715
  }
1681
1716
  function shouldSnapshot(toolName, args, config) {
1682
1717
  if (!config.settings.enableUndo) return false;
@@ -1827,6 +1862,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1827
1862
  reason: matchedRule.reason,
1828
1863
  tier: 2,
1829
1864
  ruleName: matchedRule.name ?? matchedRule.tool,
1865
+ ...(matchedRule.description ?? matchedRule.reason) && {
1866
+ ruleDescription: matchedRule.description ?? matchedRule.reason
1867
+ },
1830
1868
  ...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
1831
1869
  dependsOnStatePredicates: matchedRule.dependsOnState
1832
1870
  },
@@ -3040,6 +3078,58 @@ var init_cloud = __esm({
3040
3078
  }
3041
3079
  });
3042
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
+
3043
3133
  // src/auth/orchestrator.ts
3044
3134
  import { randomUUID } from "crypto";
3045
3135
  function isWriteTool(toolName) {
@@ -3128,6 +3218,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3128
3218
  let explainableLabel = "Local Config";
3129
3219
  let policyMatchedField;
3130
3220
  let policyMatchedWord;
3221
+ let policyRuleDescription;
3131
3222
  let riskMetadata;
3132
3223
  let statefulRecoveryCommand;
3133
3224
  let localSmartRuleMatched = false;
@@ -3221,6 +3312,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3221
3312
  return { approved: true, checkedBy: "audit" };
3222
3313
  }
3223
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
+ }
3224
3330
  if (getActiveTrustSession(toolName)) {
3225
3331
  if (approvers.cloud && creds?.apiKey)
3226
3332
  await auditLocalAllow(toolName, args, "trust", creds, meta);
@@ -3267,7 +3373,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3267
3373
  blockedBy: "local-config",
3268
3374
  blockedByLabel: policyResult.blockedByLabel,
3269
3375
  ruleHit: policyResult.ruleName,
3270
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
3376
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
3377
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
3271
3378
  };
3272
3379
  }
3273
3380
  }
@@ -3275,6 +3382,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3275
3382
  policyMatchedField = policyResult.matchedField;
3276
3383
  policyMatchedWord = policyResult.matchedWord;
3277
3384
  if (policyResult.ruleName) localSmartRuleMatched = true;
3385
+ if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
3386
+ else if (policyResult.reason) policyRuleDescription = policyResult.reason;
3278
3387
  riskMetadata = computeRiskMetadata(
3279
3388
  args,
3280
3389
  policyResult.tier ?? 6,
@@ -3538,7 +3647,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
3538
3647
  hashAuditArgs
3539
3648
  );
3540
3649
  }
3541
- return finalResult;
3650
+ const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
3651
+ return enrichedResult;
3542
3652
  }
3543
3653
  var WRITE_TOOLS;
3544
3654
  var init_orchestrator = __esm({
@@ -3553,6 +3663,7 @@ var init_orchestrator = __esm({
3553
3663
  init_state();
3554
3664
  init_daemon();
3555
3665
  init_cloud();
3666
+ init_loop_detector();
3556
3667
  WRITE_TOOLS = /* @__PURE__ */ new Set([
3557
3668
  "write",
3558
3669
  "write_file",
@@ -5290,8 +5401,8 @@ var init_suggestion_tracker = __esm({
5290
5401
  });
5291
5402
 
5292
5403
  // src/daemon/taint-store.ts
5293
- import fs12 from "fs";
5294
- import path15 from "path";
5404
+ import fs13 from "fs";
5405
+ import path16 from "path";
5295
5406
  var DEFAULT_TTL_MS, TaintStore;
5296
5407
  var init_taint_store = __esm({
5297
5408
  "src/daemon/taint-store.ts"() {
@@ -5360,9 +5471,9 @@ var init_taint_store = __esm({
5360
5471
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5361
5472
  _resolve(filePath) {
5362
5473
  try {
5363
- return fs12.realpathSync.native(path15.resolve(filePath));
5474
+ return fs13.realpathSync.native(path16.resolve(filePath));
5364
5475
  } catch {
5365
- return path15.resolve(filePath);
5476
+ return path16.resolve(filePath);
5366
5477
  }
5367
5478
  }
5368
5479
  };
@@ -5479,15 +5590,15 @@ var init_session_history = __esm({
5479
5590
 
5480
5591
  // src/daemon/state.ts
5481
5592
  import net2 from "net";
5482
- import fs13 from "fs";
5483
- import path16 from "path";
5484
- import os11 from "os";
5593
+ import fs14 from "fs";
5594
+ import path17 from "path";
5595
+ import os12 from "os";
5485
5596
  import { spawn as spawn2 } from "child_process";
5486
5597
  import { randomUUID as randomUUID3 } from "crypto";
5487
5598
  function loadInsightCounts() {
5488
5599
  try {
5489
- if (!fs13.existsSync(INSIGHT_COUNTS_FILE)) return;
5490
- 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"));
5491
5602
  for (const [tool, count] of Object.entries(data)) {
5492
5603
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
5493
5604
  }
@@ -5526,23 +5637,23 @@ function markRejectionHandlerRegistered() {
5526
5637
  daemonRejectionHandlerRegistered = true;
5527
5638
  }
5528
5639
  function atomicWriteSync2(filePath, data, options) {
5529
- const dir = path16.dirname(filePath);
5530
- 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 });
5531
5642
  const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
5532
5643
  try {
5533
- fs13.writeFileSync(tmpPath, data, options);
5644
+ fs14.writeFileSync(tmpPath, data, options);
5534
5645
  } catch (err2) {
5535
5646
  try {
5536
- fs13.unlinkSync(tmpPath);
5647
+ fs14.unlinkSync(tmpPath);
5537
5648
  } catch {
5538
5649
  }
5539
5650
  throw err2;
5540
5651
  }
5541
5652
  try {
5542
- fs13.renameSync(tmpPath, filePath);
5653
+ fs14.renameSync(tmpPath, filePath);
5543
5654
  } catch (err2) {
5544
5655
  try {
5545
- fs13.unlinkSync(tmpPath);
5656
+ fs14.unlinkSync(tmpPath);
5546
5657
  } catch {
5547
5658
  }
5548
5659
  throw err2;
@@ -5566,16 +5677,16 @@ function appendAuditLog(data) {
5566
5677
  decision: data.decision,
5567
5678
  source: "daemon"
5568
5679
  };
5569
- const dir = path16.dirname(AUDIT_LOG_FILE);
5570
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5571
- 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");
5572
5683
  } catch {
5573
5684
  }
5574
5685
  }
5575
5686
  function getAuditHistory(limit = 20) {
5576
5687
  try {
5577
- if (!fs13.existsSync(AUDIT_LOG_FILE)) return [];
5578
- 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");
5579
5690
  if (lines.length === 1 && lines[0] === "") return [];
5580
5691
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
5581
5692
  } catch {
@@ -5584,19 +5695,19 @@ function getAuditHistory(limit = 20) {
5584
5695
  }
5585
5696
  function getOrgName() {
5586
5697
  try {
5587
- if (fs13.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5698
+ if (fs14.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5588
5699
  } catch {
5589
5700
  }
5590
5701
  return null;
5591
5702
  }
5592
5703
  function hasStoredSlackKey() {
5593
- return fs13.existsSync(CREDENTIALS_FILE);
5704
+ return fs14.existsSync(CREDENTIALS_FILE);
5594
5705
  }
5595
5706
  function writeGlobalSetting(key, value) {
5596
5707
  let config = {};
5597
5708
  try {
5598
- if (fs13.existsSync(GLOBAL_CONFIG_FILE)) {
5599
- 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"));
5600
5711
  }
5601
5712
  } catch {
5602
5713
  }
@@ -5608,8 +5719,8 @@ function writeTrustEntry(toolName, durationMs) {
5608
5719
  try {
5609
5720
  let trust = { entries: [] };
5610
5721
  try {
5611
- if (fs13.existsSync(TRUST_FILE2))
5612
- 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"));
5613
5724
  } catch {
5614
5725
  }
5615
5726
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -5620,8 +5731,8 @@ function writeTrustEntry(toolName, durationMs) {
5620
5731
  }
5621
5732
  function readPersistentDecisions() {
5622
5733
  try {
5623
- if (fs13.existsSync(DECISIONS_FILE)) {
5624
- 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"));
5625
5736
  }
5626
5737
  } catch {
5627
5738
  }
@@ -5658,7 +5769,7 @@ function estimateToolCost(tool, args) {
5658
5769
  const filePath = a.file_path ?? a.path;
5659
5770
  if (filePath) {
5660
5771
  try {
5661
- const bytes = fs13.statSync(filePath).size;
5772
+ const bytes = fs14.statSync(filePath).size;
5662
5773
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
5663
5774
  } catch {
5664
5775
  }
@@ -5716,7 +5827,7 @@ function abandonPending() {
5716
5827
  });
5717
5828
  if (autoStarted) {
5718
5829
  try {
5719
- fs13.unlinkSync(DAEMON_PID_FILE);
5830
+ fs14.unlinkSync(DAEMON_PID_FILE);
5720
5831
  } catch {
5721
5832
  }
5722
5833
  setTimeout(() => {
@@ -5727,7 +5838,7 @@ function abandonPending() {
5727
5838
  }
5728
5839
  function startActivitySocket() {
5729
5840
  try {
5730
- fs13.unlinkSync(ACTIVITY_SOCKET_PATH2);
5841
+ fs14.unlinkSync(ACTIVITY_SOCKET_PATH2);
5731
5842
  } catch {
5732
5843
  }
5733
5844
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -5808,7 +5919,7 @@ function startActivitySocket() {
5808
5919
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
5809
5920
  process.on("exit", () => {
5810
5921
  try {
5811
- fs13.unlinkSync(ACTIVITY_SOCKET_PATH2);
5922
+ fs14.unlinkSync(ACTIVITY_SOCKET_PATH2);
5812
5923
  } catch {
5813
5924
  }
5814
5925
  });
@@ -5822,14 +5933,14 @@ var init_state2 = __esm({
5822
5933
  init_taint_store();
5823
5934
  init_session_counters();
5824
5935
  init_session_history();
5825
- homeDir = os11.homedir();
5826
- DAEMON_PID_FILE = path16.join(homeDir, ".node9", "daemon.pid");
5827
- DECISIONS_FILE = path16.join(homeDir, ".node9", "decisions.json");
5828
- AUDIT_LOG_FILE = path16.join(homeDir, ".node9", "audit.log");
5829
- TRUST_FILE2 = path16.join(homeDir, ".node9", "trust.json");
5830
- GLOBAL_CONFIG_FILE = path16.join(homeDir, ".node9", "config.json");
5831
- CREDENTIALS_FILE = path16.join(homeDir, ".node9", "credentials.json");
5832
- 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");
5833
5944
  pending = /* @__PURE__ */ new Map();
5834
5945
  sseClients = /* @__PURE__ */ new Set();
5835
5946
  suggestionTracker = new SuggestionTracker(3);
@@ -5847,7 +5958,7 @@ var init_state2 = __esm({
5847
5958
  "2h": 2 * 60 * 6e4
5848
5959
  };
5849
5960
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5850
- 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");
5851
5962
  ACTIVITY_RING_SIZE = 100;
5852
5963
  activityRing = [];
5853
5964
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5869,14 +5980,14 @@ var init_state2 = __esm({
5869
5980
  });
5870
5981
 
5871
5982
  // src/config/patch.ts
5872
- import fs14 from "fs";
5873
- import path17 from "path";
5874
- import os12 from "os";
5983
+ import fs15 from "fs";
5984
+ import path18 from "path";
5985
+ import os13 from "os";
5875
5986
  function patchConfig(configPath, patch) {
5876
5987
  let config = {};
5877
5988
  try {
5878
- if (fs14.existsSync(configPath)) {
5879
- config = JSON.parse(fs14.readFileSync(configPath, "utf8"));
5989
+ if (fs15.existsSync(configPath)) {
5990
+ config = JSON.parse(fs15.readFileSync(configPath, "utf8"));
5880
5991
  }
5881
5992
  } catch {
5882
5993
  throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
@@ -5895,23 +6006,23 @@ function patchConfig(configPath, patch) {
5895
6006
  ignored.push(patch.toolName);
5896
6007
  }
5897
6008
  }
5898
- const dir = path17.dirname(configPath);
5899
- fs14.mkdirSync(dir, { recursive: true });
6009
+ const dir = path18.dirname(configPath);
6010
+ fs15.mkdirSync(dir, { recursive: true });
5900
6011
  const tmp = configPath + ".node9-tmp";
5901
6012
  try {
5902
- fs14.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
6013
+ fs15.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
5903
6014
  } catch (err2) {
5904
6015
  try {
5905
- fs14.unlinkSync(tmp);
6016
+ fs15.unlinkSync(tmp);
5906
6017
  } catch {
5907
6018
  }
5908
6019
  throw err2;
5909
6020
  }
5910
6021
  try {
5911
- fs14.renameSync(tmp, configPath);
6022
+ fs15.renameSync(tmp, configPath);
5912
6023
  } catch (err2) {
5913
6024
  try {
5914
- fs14.unlinkSync(tmp);
6025
+ fs15.unlinkSync(tmp);
5915
6026
  } catch {
5916
6027
  }
5917
6028
  throw err2;
@@ -5921,14 +6032,14 @@ var GLOBAL_CONFIG_PATH;
5921
6032
  var init_patch = __esm({
5922
6033
  "src/config/patch.ts"() {
5923
6034
  "use strict";
5924
- GLOBAL_CONFIG_PATH = path17.join(os12.homedir(), ".node9", "config.json");
6035
+ GLOBAL_CONFIG_PATH = path18.join(os13.homedir(), ".node9", "config.json");
5925
6036
  }
5926
6037
  });
5927
6038
 
5928
6039
  // src/daemon/server.ts
5929
6040
  import http from "http";
5930
- import fs15 from "fs";
5931
- import path18 from "path";
6041
+ import fs16 from "fs";
6042
+ import path19 from "path";
5932
6043
  import { randomUUID as randomUUID4 } from "crypto";
5933
6044
  import { spawnSync as spawnSync2 } from "child_process";
5934
6045
  import chalk2 from "chalk";
@@ -5948,7 +6059,7 @@ function startDaemon() {
5948
6059
  idleTimer = setTimeout(() => {
5949
6060
  if (autoStarted) {
5950
6061
  try {
5951
- fs15.unlinkSync(DAEMON_PID_FILE);
6062
+ fs16.unlinkSync(DAEMON_PID_FILE);
5952
6063
  } catch {
5953
6064
  }
5954
6065
  }
@@ -6111,7 +6222,7 @@ data: ${JSON.stringify(item.data)}
6111
6222
  status: "pending"
6112
6223
  });
6113
6224
  }
6114
- const projectCwd = typeof cwd === "string" && path18.isAbsolute(cwd) ? cwd : void 0;
6225
+ const projectCwd = typeof cwd === "string" && path19.isAbsolute(cwd) ? cwd : void 0;
6115
6226
  const projectConfig = getConfig(projectCwd);
6116
6227
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6117
6228
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6501,8 +6612,8 @@ data: ${JSON.stringify(item.data)}
6501
6612
  const body = await readBody(req);
6502
6613
  const data = body ? JSON.parse(body) : {};
6503
6614
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6504
- const node9Dir = path18.dirname(GLOBAL_CONFIG_PATH);
6505
- 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)) {
6506
6617
  res.writeHead(400, { "Content-Type": "application/json" });
6507
6618
  return res.end(
6508
6619
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6613,14 +6724,14 @@ data: ${JSON.stringify(item.data)}
6613
6724
  server.on("error", (e) => {
6614
6725
  if (e.code === "EADDRINUSE") {
6615
6726
  try {
6616
- if (fs15.existsSync(DAEMON_PID_FILE)) {
6617
- 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"));
6618
6729
  process.kill(pid, 0);
6619
6730
  return process.exit(0);
6620
6731
  }
6621
6732
  } catch {
6622
6733
  try {
6623
- fs15.unlinkSync(DAEMON_PID_FILE);
6734
+ fs16.unlinkSync(DAEMON_PID_FILE);
6624
6735
  } catch {
6625
6736
  }
6626
6737
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6692,28 +6803,28 @@ var init_server = __esm({
6692
6803
  });
6693
6804
 
6694
6805
  // src/daemon/index.ts
6695
- import fs16 from "fs";
6806
+ import fs17 from "fs";
6696
6807
  import chalk3 from "chalk";
6697
6808
  import { spawnSync as spawnSync3 } from "child_process";
6698
6809
  function stopDaemon() {
6699
- 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."));
6700
6811
  try {
6701
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6812
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6702
6813
  process.kill(pid, "SIGTERM");
6703
6814
  console.log(chalk3.green("\u2705 Stopped."));
6704
6815
  } catch {
6705
6816
  console.log(chalk3.gray("Cleaned up stale PID file."));
6706
6817
  } finally {
6707
6818
  try {
6708
- fs16.unlinkSync(DAEMON_PID_FILE);
6819
+ fs17.unlinkSync(DAEMON_PID_FILE);
6709
6820
  } catch {
6710
6821
  }
6711
6822
  }
6712
6823
  }
6713
6824
  function daemonStatus() {
6714
- if (fs16.existsSync(DAEMON_PID_FILE)) {
6825
+ if (fs17.existsSync(DAEMON_PID_FILE)) {
6715
6826
  try {
6716
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6827
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6717
6828
  process.kill(pid, 0);
6718
6829
  console.log(chalk3.green("Node9 daemon: running"));
6719
6830
  return;
@@ -6747,10 +6858,10 @@ __export(tail_exports, {
6747
6858
  startTail: () => startTail
6748
6859
  });
6749
6860
  import http2 from "http";
6750
- import chalk18 from "chalk";
6751
- import fs26 from "fs";
6752
- import os22 from "os";
6753
- import path29 from "path";
6861
+ import chalk19 from "chalk";
6862
+ import fs28 from "fs";
6863
+ import os24 from "os";
6864
+ import path31 from "path";
6754
6865
  import readline5 from "readline";
6755
6866
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
6756
6867
  function getIcon(tool) {
@@ -6760,44 +6871,64 @@ function getIcon(tool) {
6760
6871
  }
6761
6872
  return "\u{1F6E0}\uFE0F";
6762
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
+ }
6763
6883
  function formatBase(activity) {
6764
6884
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6765
6885
  const icon = getIcon(activity.tool);
6766
6886
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6767
- 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(), "~");
6768
6888
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6769
- 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)}`;
6770
6890
  }
6771
6891
  function renderResult(activity, result) {
6772
6892
  const base = formatBase(activity);
6773
6893
  let status;
6774
6894
  if (result.status === "allow") {
6775
- status = chalk18.green("\u2713 ALLOW");
6895
+ status = chalk19.green("\u2713 ALLOW");
6776
6896
  } else if (result.status === "dlp") {
6777
- status = chalk18.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6897
+ status = chalk19.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6778
6898
  } else {
6779
- status = chalk18.red("\u2717 BLOCK");
6899
+ status = chalk19.red("\u2717 BLOCK");
6780
6900
  }
6781
6901
  const cost = result.costEstimate ?? activity.costEstimate;
6782
- 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"}`);
6783
6903
  if (process.stdout.isTTY) {
6784
- readline5.clearLine(process.stdout, 0);
6785
- 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;
6786
6914
  }
6787
6915
  console.log(`${base} ${status}${costSuffix}`);
6788
6916
  }
6789
6917
  function renderPending(activity) {
6790
6918
  if (!process.stdout.isTTY) return;
6791
- 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`);
6792
6923
  }
6793
6924
  async function ensureDaemon() {
6794
6925
  let pidPort = null;
6795
- if (fs26.existsSync(PID_FILE)) {
6926
+ if (fs28.existsSync(PID_FILE)) {
6796
6927
  try {
6797
- const { port } = JSON.parse(fs26.readFileSync(PID_FILE, "utf-8"));
6928
+ const { port } = JSON.parse(fs28.readFileSync(PID_FILE, "utf-8"));
6798
6929
  pidPort = port;
6799
6930
  } catch {
6800
- 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."));
6801
6932
  }
6802
6933
  }
6803
6934
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6808,7 +6939,7 @@ async function ensureDaemon() {
6808
6939
  if (res.ok) return checkPort;
6809
6940
  } catch {
6810
6941
  }
6811
- console.log(chalk18.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6942
+ console.log(chalk19.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6812
6943
  const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
6813
6944
  detached: true,
6814
6945
  stdio: "ignore",
@@ -6825,7 +6956,7 @@ async function ensureDaemon() {
6825
6956
  } catch {
6826
6957
  }
6827
6958
  }
6828
- 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"));
6829
6960
  process.exit(1);
6830
6961
  }
6831
6962
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6914,9 +7045,9 @@ function buildRecoveryCardLines(req) {
6914
7045
  ];
6915
7046
  }
6916
7047
  function readApproversFromDisk() {
6917
- const configPath = path29.join(os22.homedir(), ".node9", "config.json");
7048
+ const configPath = path31.join(os24.homedir(), ".node9", "config.json");
6918
7049
  try {
6919
- const raw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
7050
+ const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
6920
7051
  const settings = raw.settings ?? {};
6921
7052
  return settings.approvers ?? {};
6922
7053
  } catch {
@@ -6927,20 +7058,20 @@ function approverStatusLine() {
6927
7058
  const a = readApproversFromDisk();
6928
7059
  const fmt = (label, key) => {
6929
7060
  const on = a[key] !== false;
6930
- 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")}`;
6931
7062
  };
6932
7063
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
6933
7064
  }
6934
7065
  function toggleApprover(channel) {
6935
- const configPath = path29.join(os22.homedir(), ".node9", "config.json");
7066
+ const configPath = path31.join(os24.homedir(), ".node9", "config.json");
6936
7067
  try {
6937
- const raw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
7068
+ const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
6938
7069
  const settings = raw.settings ?? {};
6939
7070
  const approvers = settings.approvers ?? {};
6940
7071
  approvers[channel] = approvers[channel] === false;
6941
7072
  settings.approvers = approvers;
6942
7073
  raw.settings = settings;
6943
- fs26.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7074
+ fs28.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
6944
7075
  } catch (err2) {
6945
7076
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
6946
7077
  `);
@@ -6972,7 +7103,7 @@ async function startTail(options = {}) {
6972
7103
  req2.end();
6973
7104
  });
6974
7105
  if (result.ok) {
6975
- console.log(chalk18.green("\u2713 Flight Recorder buffer cleared."));
7106
+ console.log(chalk19.green("\u2713 Flight Recorder buffer cleared."));
6976
7107
  } else if (result.code === "ECONNREFUSED") {
6977
7108
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
6978
7109
  } else if (result.code === "ETIMEDOUT") {
@@ -7016,7 +7147,7 @@ async function startTail(options = {}) {
7016
7147
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7017
7148
  if (channel) {
7018
7149
  toggleApprover(channel);
7019
- console.log(chalk18.dim(` Approvers: ${approverStatusLine()}`));
7150
+ console.log(chalk19.dim(` Approvers: ${approverStatusLine()}`));
7020
7151
  }
7021
7152
  };
7022
7153
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7082,7 +7213,7 @@ async function startTail(options = {}) {
7082
7213
  localAllowCounts.get(req2.toolName) ?? 0
7083
7214
  )
7084
7215
  );
7085
- 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");
7086
7217
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7087
7218
  for (const line of stampedLines) process.stdout.write(line + "\n");
7088
7219
  process.stdout.write(SHOW_CURSOR);
@@ -7110,8 +7241,8 @@ async function startTail(options = {}) {
7110
7241
  }
7111
7242
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7112
7243
  try {
7113
- fs26.appendFileSync(
7114
- path29.join(os22.homedir(), ".node9", "hook-debug.log"),
7244
+ fs28.appendFileSync(
7245
+ path31.join(os24.homedir(), ".node9", "hook-debug.log"),
7115
7246
  `[tail] POST /decision failed: ${String(err2)}
7116
7247
  `
7117
7248
  );
@@ -7133,7 +7264,7 @@ async function startTail(options = {}) {
7133
7264
  );
7134
7265
  const stampedLines = buildCardLines(req2, priorCount);
7135
7266
  if (externalDecision) {
7136
- 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");
7137
7268
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7138
7269
  }
7139
7270
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7192,16 +7323,16 @@ async function startTail(options = {}) {
7192
7323
  }
7193
7324
  } catch {
7194
7325
  }
7195
- console.log(chalk18.cyan.bold(`
7196
- \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}`));
7197
7328
  if (canApprove) {
7198
- console.log(chalk18.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7199
- 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`));
7200
7331
  }
7201
7332
  if (options.history) {
7202
- console.log(chalk18.dim("Showing history + live events.\n"));
7333
+ console.log(chalk19.dim("Showing history + live events.\n"));
7203
7334
  } else {
7204
- 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"));
7205
7336
  }
7206
7337
  process.on("SIGINT", () => {
7207
7338
  exitIdleMode();
@@ -7211,13 +7342,13 @@ async function startTail(options = {}) {
7211
7342
  readline5.clearLine(process.stdout, 0);
7212
7343
  readline5.cursorTo(process.stdout, 0);
7213
7344
  }
7214
- console.log(chalk18.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7345
+ console.log(chalk19.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7215
7346
  process.exit(0);
7216
7347
  });
7217
7348
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7218
7349
  const req = http2.get(sseUrl, (res) => {
7219
7350
  if (res.statusCode !== 200) {
7220
- console.error(chalk18.red(`Failed to connect: HTTP ${res.statusCode}`));
7351
+ console.error(chalk19.red(`Failed to connect: HTTP ${res.statusCode}`));
7221
7352
  process.exit(1);
7222
7353
  }
7223
7354
  if (canApprove) enterIdleMode();
@@ -7248,7 +7379,7 @@ async function startTail(options = {}) {
7248
7379
  readline5.clearLine(process.stdout, 0);
7249
7380
  readline5.cursorTo(process.stdout, 0);
7250
7381
  }
7251
- console.log(chalk18.red("\n\u274C Daemon disconnected."));
7382
+ console.log(chalk19.red("\n\u274C Daemon disconnected."));
7252
7383
  process.exit(1);
7253
7384
  });
7254
7385
  });
@@ -7340,9 +7471,9 @@ async function startTail(options = {}) {
7340
7471
  const hash = data.hash ?? "";
7341
7472
  const summary = data.argsSummary ?? data.tool;
7342
7473
  const fileCount = data.fileCount ?? 0;
7343
- 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"}`) : "";
7344
7475
  process.stdout.write(
7345
- `${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}
7346
7477
  `
7347
7478
  );
7348
7479
  return;
@@ -7359,19 +7490,19 @@ async function startTail(options = {}) {
7359
7490
  }
7360
7491
  req.on("error", (err2) => {
7361
7492
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7362
- console.error(chalk18.red(`
7493
+ console.error(chalk19.red(`
7363
7494
  \u274C ${msg}`));
7364
7495
  process.exit(1);
7365
7496
  });
7366
7497
  }
7367
- 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;
7368
7499
  var init_tail = __esm({
7369
7500
  "src/tui/tail.ts"() {
7370
7501
  "use strict";
7371
7502
  init_daemon2();
7372
7503
  init_daemon();
7373
7504
  init_core();
7374
- PID_FILE = path29.join(os22.homedir(), ".node9", "daemon.pid");
7505
+ PID_FILE = path31.join(os24.homedir(), ".node9", "daemon.pid");
7375
7506
  ICONS = {
7376
7507
  bash: "\u{1F4BB}",
7377
7508
  shell: "\u{1F4BB}",
@@ -7399,6 +7530,8 @@ var init_tail = __esm({
7399
7530
  HIDE_CURSOR = "\x1B[?25l";
7400
7531
  SHOW_CURSOR = "\x1B[?25h";
7401
7532
  ERASE_DOWN = "\x1B[J";
7533
+ pendingShownForId = null;
7534
+ pendingWrappedLines = 0;
7402
7535
  DIVIDER = "\u2500".repeat(60);
7403
7536
  }
7404
7537
  });
@@ -7410,9 +7543,9 @@ __export(hud_exports, {
7410
7543
  main: () => main,
7411
7544
  renderEnvironmentLine: () => renderEnvironmentLine
7412
7545
  });
7413
- import fs27 from "fs";
7414
- import path30 from "path";
7415
- import os23 from "os";
7546
+ import fs29 from "fs";
7547
+ import path32 from "path";
7548
+ import os25 from "os";
7416
7549
  import http3 from "http";
7417
7550
  async function readStdin() {
7418
7551
  const chunks = [];
@@ -7471,10 +7604,10 @@ function bold(s) {
7471
7604
  function color(c, s) {
7472
7605
  return `${c}${s}${RESET3}`;
7473
7606
  }
7474
- function progressBar(pct, warnAt = 70, critAt = 85) {
7475
- 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);
7476
7609
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7477
- const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7610
+ const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
7478
7611
  return `${c}${bar}${RESET3}`;
7479
7612
  }
7480
7613
  function formatTimeLeft(resetsAt) {
@@ -7488,9 +7621,9 @@ function formatTimeLeft(resetsAt) {
7488
7621
  return ` (${m}m left)`;
7489
7622
  }
7490
7623
  function safeReadJson(filePath) {
7491
- if (!fs27.existsSync(filePath)) return null;
7624
+ if (!fs29.existsSync(filePath)) return null;
7492
7625
  try {
7493
- return JSON.parse(fs27.readFileSync(filePath, "utf-8"));
7626
+ return JSON.parse(fs29.readFileSync(filePath, "utf-8"));
7494
7627
  } catch {
7495
7628
  return null;
7496
7629
  }
@@ -7511,12 +7644,12 @@ function countHooksInFile(filePath) {
7511
7644
  return Object.keys(cfg.hooks).length;
7512
7645
  }
7513
7646
  function countRulesInDir(rulesDir) {
7514
- if (!fs27.existsSync(rulesDir)) return 0;
7647
+ if (!fs29.existsSync(rulesDir)) return 0;
7515
7648
  let count = 0;
7516
7649
  try {
7517
- for (const entry of fs27.readdirSync(rulesDir, { withFileTypes: true })) {
7650
+ for (const entry of fs29.readdirSync(rulesDir, { withFileTypes: true })) {
7518
7651
  if (entry.isDirectory()) {
7519
- count += countRulesInDir(path30.join(rulesDir, entry.name));
7652
+ count += countRulesInDir(path32.join(rulesDir, entry.name));
7520
7653
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7521
7654
  count++;
7522
7655
  }
@@ -7527,46 +7660,46 @@ function countRulesInDir(rulesDir) {
7527
7660
  }
7528
7661
  function isSamePath(a, b) {
7529
7662
  try {
7530
- return path30.resolve(a) === path30.resolve(b);
7663
+ return path32.resolve(a) === path32.resolve(b);
7531
7664
  } catch {
7532
7665
  return false;
7533
7666
  }
7534
7667
  }
7535
7668
  function countConfigs(cwd) {
7536
- const homeDir2 = os23.homedir();
7537
- const claudeDir = path30.join(homeDir2, ".claude");
7669
+ const homeDir2 = os25.homedir();
7670
+ const claudeDir = path32.join(homeDir2, ".claude");
7538
7671
  let claudeMdCount = 0;
7539
7672
  let rulesCount = 0;
7540
7673
  let hooksCount = 0;
7541
7674
  const userMcpServers = /* @__PURE__ */ new Set();
7542
7675
  const projectMcpServers = /* @__PURE__ */ new Set();
7543
- if (fs27.existsSync(path30.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7544
- rulesCount += countRulesInDir(path30.join(claudeDir, "rules"));
7545
- 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");
7546
7679
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7547
7680
  hooksCount += countHooksInFile(userSettings);
7548
- const userClaudeJson = path30.join(homeDir2, ".claude.json");
7681
+ const userClaudeJson = path32.join(homeDir2, ".claude.json");
7549
7682
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7550
7683
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7551
7684
  userMcpServers.delete(name);
7552
7685
  }
7553
7686
  if (cwd) {
7554
- if (fs27.existsSync(path30.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7555
- if (fs27.existsSync(path30.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7556
- 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");
7557
7690
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7558
7691
  if (!overlapsUserScope) {
7559
- if (fs27.existsSync(path30.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7560
- rulesCount += countRulesInDir(path30.join(projectClaudeDir, "rules"));
7561
- 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");
7562
7695
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7563
7696
  hooksCount += countHooksInFile(projSettings);
7564
7697
  }
7565
- if (fs27.existsSync(path30.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7566
- 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");
7567
7700
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7568
7701
  hooksCount += countHooksInFile(localSettings);
7569
- const mcpJsonServers = getMcpServerNames(path30.join(cwd, ".mcp.json"));
7702
+ const mcpJsonServers = getMcpServerNames(path32.join(cwd, ".mcp.json"));
7570
7703
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7571
7704
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7572
7705
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7599,12 +7732,12 @@ function readActiveShieldsHud() {
7599
7732
  return shieldsCache.value;
7600
7733
  }
7601
7734
  try {
7602
- const shieldsPath = path30.join(os23.homedir(), ".node9", "shields.json");
7603
- if (!fs27.existsSync(shieldsPath)) {
7735
+ const shieldsPath = path32.join(os25.homedir(), ".node9", "shields.json");
7736
+ if (!fs29.existsSync(shieldsPath)) {
7604
7737
  shieldsCache = { value: [], ts: now };
7605
7738
  return [];
7606
7739
  }
7607
- const parsed = JSON.parse(fs27.readFileSync(shieldsPath, "utf-8"));
7740
+ const parsed = JSON.parse(fs29.readFileSync(shieldsPath, "utf-8"));
7608
7741
  if (!Array.isArray(parsed.active)) {
7609
7742
  shieldsCache = { value: [], ts: now };
7610
7743
  return [];
@@ -7690,15 +7823,15 @@ function renderContextLine(stdin) {
7690
7823
  }
7691
7824
  const rl = stdin.rate_limits;
7692
7825
  if (rl?.five_hour?.used_percentage !== void 0) {
7693
- const pct = Math.round(rl.five_hour.used_percentage);
7694
- const bar = progressBar(pct, 60, 80);
7826
+ const pct2 = Math.round(rl.five_hour.used_percentage);
7827
+ const bar = progressBar(pct2, 60, 80);
7695
7828
  const left = formatTimeLeft(rl.five_hour.resets_at);
7696
- parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
7829
+ parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
7697
7830
  }
7698
7831
  if (rl?.seven_day?.used_percentage !== void 0) {
7699
- const pct = Math.round(rl.seven_day.used_percentage);
7700
- const bar = progressBar(pct, 60, 80);
7701
- 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}%`);
7702
7835
  }
7703
7836
  if (parts.length === 0) return null;
7704
7837
  return parts.join(" ");
@@ -7706,17 +7839,17 @@ function renderContextLine(stdin) {
7706
7839
  async function main() {
7707
7840
  try {
7708
7841
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7709
- if (fs27.existsSync(path30.join(os23.homedir(), ".node9", "hud-debug"))) {
7842
+ if (fs29.existsSync(path32.join(os25.homedir(), ".node9", "hud-debug"))) {
7710
7843
  try {
7711
- const logPath = path30.join(os23.homedir(), ".node9", "hud-debug.log");
7844
+ const logPath = path32.join(os25.homedir(), ".node9", "hud-debug.log");
7712
7845
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7713
7846
  let size = 0;
7714
7847
  try {
7715
- size = fs27.statSync(logPath).size;
7848
+ size = fs29.statSync(logPath).size;
7716
7849
  } catch {
7717
7850
  }
7718
7851
  if (size < MAX_LOG_SIZE) {
7719
- fs27.appendFileSync(
7852
+ fs29.appendFileSync(
7720
7853
  logPath,
7721
7854
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7722
7855
  );
@@ -7737,11 +7870,11 @@ async function main() {
7737
7870
  try {
7738
7871
  const cwd = stdin.cwd ?? process.cwd();
7739
7872
  for (const configPath of [
7740
- path30.join(cwd, "node9.config.json"),
7741
- path30.join(os23.homedir(), ".node9", "config.json")
7873
+ path32.join(cwd, "node9.config.json"),
7874
+ path32.join(os25.homedir(), ".node9", "config.json")
7742
7875
  ]) {
7743
- if (!fs27.existsSync(configPath)) continue;
7744
- 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"));
7745
7878
  const hud = cfg.settings?.hud;
7746
7879
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7747
7880
  }
@@ -7787,9 +7920,9 @@ init_core();
7787
7920
  import { Command } from "commander";
7788
7921
 
7789
7922
  // src/setup.ts
7790
- import fs11 from "fs";
7791
- import path14 from "path";
7792
- import os10 from "os";
7923
+ import fs12 from "fs";
7924
+ import path15 from "path";
7925
+ import os11 from "os";
7793
7926
  import chalk from "chalk";
7794
7927
  import { confirm } from "@inquirer/prompts";
7795
7928
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
@@ -7817,26 +7950,26 @@ function fullPathCommand(subcommand) {
7817
7950
  }
7818
7951
  function readJson(filePath) {
7819
7952
  try {
7820
- if (fs11.existsSync(filePath)) {
7821
- return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
7953
+ if (fs12.existsSync(filePath)) {
7954
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
7822
7955
  }
7823
7956
  } catch {
7824
7957
  }
7825
7958
  return null;
7826
7959
  }
7827
7960
  function writeJson(filePath, data) {
7828
- const dir = path14.dirname(filePath);
7829
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
7830
- 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");
7831
7964
  }
7832
7965
  function isNode9Hook(cmd) {
7833
7966
  if (!cmd) return false;
7834
7967
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
7835
7968
  }
7836
7969
  function teardownClaude() {
7837
- const homeDir2 = os10.homedir();
7838
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7839
- 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");
7840
7973
  let changed = false;
7841
7974
  const settings = readJson(hooksPath);
7842
7975
  if (settings?.hooks) {
@@ -7884,8 +8017,8 @@ function teardownClaude() {
7884
8017
  }
7885
8018
  }
7886
8019
  function teardownGemini() {
7887
- const homeDir2 = os10.homedir();
7888
- const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
8020
+ const homeDir2 = os11.homedir();
8021
+ const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
7889
8022
  const settings = readJson(settingsPath);
7890
8023
  if (!settings) {
7891
8024
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7928,8 +8061,8 @@ function teardownGemini() {
7928
8061
  }
7929
8062
  }
7930
8063
  function teardownCursor() {
7931
- const homeDir2 = os10.homedir();
7932
- const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
8064
+ const homeDir2 = os11.homedir();
8065
+ const mcpPath = path15.join(homeDir2, ".cursor", "mcp.json");
7933
8066
  const mcpConfig = readJson(mcpPath);
7934
8067
  if (!mcpConfig?.mcpServers) {
7935
8068
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -7960,9 +8093,9 @@ function teardownCursor() {
7960
8093
  }
7961
8094
  }
7962
8095
  async function setupClaude() {
7963
- const homeDir2 = os10.homedir();
7964
- const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
7965
- 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");
7966
8099
  const claudeConfig = readJson(mcpPath) ?? {};
7967
8100
  const settings = readJson(hooksPath) ?? {};
7968
8101
  const servers = claudeConfig.mcpServers ?? {};
@@ -8059,8 +8192,8 @@ async function setupClaude() {
8059
8192
  }
8060
8193
  }
8061
8194
  async function setupGemini() {
8062
- const homeDir2 = os10.homedir();
8063
- const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
8195
+ const homeDir2 = os11.homedir();
8196
+ const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
8064
8197
  const settings = readJson(settingsPath) ?? {};
8065
8198
  const servers = settings.mcpServers ?? {};
8066
8199
  let hooksChanged = false;
@@ -8155,10 +8288,10 @@ async function setupGemini() {
8155
8288
  printDaemonTip();
8156
8289
  }
8157
8290
  }
8158
- function detectAgents(homeDir2 = os10.homedir()) {
8291
+ function detectAgents(homeDir2 = os11.homedir()) {
8159
8292
  const exists = (p) => {
8160
8293
  try {
8161
- return fs11.existsSync(p);
8294
+ return fs12.existsSync(p);
8162
8295
  } catch (err2) {
8163
8296
  const code = err2.code;
8164
8297
  if (code !== "ENOENT") {
@@ -8169,15 +8302,15 @@ function detectAgents(homeDir2 = os10.homedir()) {
8169
8302
  }
8170
8303
  };
8171
8304
  return {
8172
- claude: exists(path14.join(homeDir2, ".claude")) || exists(path14.join(homeDir2, ".claude.json")),
8173
- gemini: exists(path14.join(homeDir2, ".gemini")),
8174
- cursor: exists(path14.join(homeDir2, ".cursor")),
8175
- 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"))
8176
8309
  };
8177
8310
  }
8178
8311
  async function setupCursor() {
8179
- const homeDir2 = os10.homedir();
8180
- const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
8312
+ const homeDir2 = os11.homedir();
8313
+ const mcpPath = path15.join(homeDir2, ".cursor", "mcp.json");
8181
8314
  const mcpConfig = readJson(mcpPath) ?? {};
8182
8315
  const servers = mcpConfig.mcpServers ?? {};
8183
8316
  let anythingChanged = false;
@@ -8243,21 +8376,21 @@ async function setupCursor() {
8243
8376
  }
8244
8377
  function readToml(filePath) {
8245
8378
  try {
8246
- if (fs11.existsSync(filePath)) {
8247
- return parseToml(fs11.readFileSync(filePath, "utf-8"));
8379
+ if (fs12.existsSync(filePath)) {
8380
+ return parseToml(fs12.readFileSync(filePath, "utf-8"));
8248
8381
  }
8249
8382
  } catch {
8250
8383
  }
8251
8384
  return null;
8252
8385
  }
8253
8386
  function writeToml(filePath, data) {
8254
- const dir = path14.dirname(filePath);
8255
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
8256
- 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));
8257
8390
  }
8258
8391
  async function setupCodex() {
8259
- const homeDir2 = os10.homedir();
8260
- const configPath = path14.join(homeDir2, ".codex", "config.toml");
8392
+ const homeDir2 = os11.homedir();
8393
+ const configPath = path15.join(homeDir2, ".codex", "config.toml");
8261
8394
  const config = readToml(configPath) ?? {};
8262
8395
  const servers = config.mcp_servers ?? {};
8263
8396
  let anythingChanged = false;
@@ -8322,8 +8455,8 @@ async function setupCodex() {
8322
8455
  }
8323
8456
  }
8324
8457
  function setupHud() {
8325
- const homeDir2 = os10.homedir();
8326
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8458
+ const homeDir2 = os11.homedir();
8459
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8327
8460
  const settings = readJson(hooksPath) ?? {};
8328
8461
  const hudCommand = fullPathCommand("hud");
8329
8462
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8349,8 +8482,8 @@ function setupHud() {
8349
8482
  console.log(chalk.gray(" Restart Claude Code to activate."));
8350
8483
  }
8351
8484
  function teardownHud() {
8352
- const homeDir2 = os10.homedir();
8353
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8485
+ const homeDir2 = os11.homedir();
8486
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8354
8487
  const settings = readJson(hooksPath);
8355
8488
  if (!settings) {
8356
8489
  console.log(chalk.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8370,10 +8503,10 @@ function teardownHud() {
8370
8503
 
8371
8504
  // src/cli.ts
8372
8505
  init_daemon2();
8373
- import chalk19 from "chalk";
8374
- import fs28 from "fs";
8375
- import path31 from "path";
8376
- import os24 from "os";
8506
+ import chalk20 from "chalk";
8507
+ import fs30 from "fs";
8508
+ import path33 from "path";
8509
+ import os26 from "os";
8377
8510
  import { confirm as confirm2 } from "@inquirer/prompts";
8378
8511
 
8379
8512
  // src/utils/duration.ts
@@ -8602,19 +8735,19 @@ init_daemon();
8602
8735
  init_config();
8603
8736
  init_policy();
8604
8737
  import chalk5 from "chalk";
8605
- import fs18 from "fs";
8738
+ import fs19 from "fs";
8606
8739
  import { spawn as spawn6 } from "child_process";
8607
- import path20 from "path";
8608
- import os14 from "os";
8740
+ import path21 from "path";
8741
+ import os15 from "os";
8609
8742
 
8610
8743
  // src/undo.ts
8611
8744
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
8612
- import crypto2 from "crypto";
8613
- import fs17 from "fs";
8745
+ import crypto3 from "crypto";
8746
+ import fs18 from "fs";
8614
8747
  import net3 from "net";
8615
- import path19 from "path";
8616
- import os13 from "os";
8617
- 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");
8618
8751
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8619
8752
  try {
8620
8753
  const payload = JSON.stringify({
@@ -8634,22 +8767,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8634
8767
  } catch {
8635
8768
  }
8636
8769
  }
8637
- var SNAPSHOT_STACK_PATH = path19.join(os13.homedir(), ".node9", "snapshots.json");
8638
- 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");
8639
8772
  var MAX_SNAPSHOTS = 10;
8640
8773
  var GIT_TIMEOUT = 15e3;
8641
8774
  function readStack() {
8642
8775
  try {
8643
- if (fs17.existsSync(SNAPSHOT_STACK_PATH))
8644
- 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"));
8645
8778
  } catch {
8646
8779
  }
8647
8780
  return [];
8648
8781
  }
8649
8782
  function writeStack(stack) {
8650
- const dir = path19.dirname(SNAPSHOT_STACK_PATH);
8651
- if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
8652
- 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));
8653
8786
  }
8654
8787
  function extractFilePath(args) {
8655
8788
  if (!args || typeof args !== "object") return null;
@@ -8669,12 +8802,12 @@ function buildArgsSummary(tool, args) {
8669
8802
  return "";
8670
8803
  }
8671
8804
  function findProjectRoot(filePath) {
8672
- let dir = path19.dirname(filePath);
8805
+ let dir = path20.dirname(filePath);
8673
8806
  while (true) {
8674
- 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"))) {
8675
8808
  return dir;
8676
8809
  }
8677
- const parent = path19.dirname(dir);
8810
+ const parent = path20.dirname(dir);
8678
8811
  if (parent === dir) return process.cwd();
8679
8812
  dir = parent;
8680
8813
  }
@@ -8682,7 +8815,7 @@ function findProjectRoot(filePath) {
8682
8815
  function normalizeCwdForHash(cwd) {
8683
8816
  let normalized;
8684
8817
  try {
8685
- normalized = fs17.realpathSync(cwd);
8818
+ normalized = fs18.realpathSync(cwd);
8686
8819
  } catch {
8687
8820
  normalized = cwd;
8688
8821
  }
@@ -8691,17 +8824,17 @@ function normalizeCwdForHash(cwd) {
8691
8824
  return normalized;
8692
8825
  }
8693
8826
  function getShadowRepoDir(cwd) {
8694
- const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8695
- 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);
8696
8829
  }
8697
8830
  function cleanOrphanedIndexFiles(shadowDir) {
8698
8831
  try {
8699
8832
  const cutoff = Date.now() - 6e4;
8700
- for (const f of fs17.readdirSync(shadowDir)) {
8833
+ for (const f of fs18.readdirSync(shadowDir)) {
8701
8834
  if (f.startsWith("index_")) {
8702
- const fp = path19.join(shadowDir, f);
8835
+ const fp = path20.join(shadowDir, f);
8703
8836
  try {
8704
- if (fs17.statSync(fp).mtimeMs < cutoff) fs17.unlinkSync(fp);
8837
+ if (fs18.statSync(fp).mtimeMs < cutoff) fs18.unlinkSync(fp);
8705
8838
  } catch {
8706
8839
  }
8707
8840
  }
@@ -8713,7 +8846,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8713
8846
  const hardcoded = [".git", ".node9"];
8714
8847
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8715
8848
  try {
8716
- fs17.writeFileSync(path19.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8849
+ fs18.writeFileSync(path20.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8717
8850
  } catch {
8718
8851
  }
8719
8852
  }
@@ -8726,25 +8859,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8726
8859
  timeout: 3e3
8727
8860
  });
8728
8861
  if (check.status === 0) {
8729
- const ptPath = path19.join(shadowDir, "project-path.txt");
8862
+ const ptPath = path20.join(shadowDir, "project-path.txt");
8730
8863
  try {
8731
- const stored = fs17.readFileSync(ptPath, "utf8").trim();
8864
+ const stored = fs18.readFileSync(ptPath, "utf8").trim();
8732
8865
  if (stored === normalizedCwd) return true;
8733
8866
  if (process.env.NODE9_DEBUG === "1")
8734
8867
  console.error(
8735
8868
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8736
8869
  );
8737
- fs17.rmSync(shadowDir, { recursive: true, force: true });
8870
+ fs18.rmSync(shadowDir, { recursive: true, force: true });
8738
8871
  } catch {
8739
8872
  try {
8740
- fs17.writeFileSync(ptPath, normalizedCwd, "utf8");
8873
+ fs18.writeFileSync(ptPath, normalizedCwd, "utf8");
8741
8874
  } catch {
8742
8875
  }
8743
8876
  return true;
8744
8877
  }
8745
8878
  }
8746
8879
  try {
8747
- fs17.mkdirSync(shadowDir, { recursive: true });
8880
+ fs18.mkdirSync(shadowDir, { recursive: true });
8748
8881
  } catch {
8749
8882
  }
8750
8883
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8753,7 +8886,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8753
8886
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8754
8887
  return false;
8755
8888
  }
8756
- const configFile = path19.join(shadowDir, "config");
8889
+ const configFile = path20.join(shadowDir, "config");
8757
8890
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8758
8891
  timeout: 3e3
8759
8892
  });
@@ -8761,7 +8894,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8761
8894
  timeout: 3e3
8762
8895
  });
8763
8896
  try {
8764
- fs17.writeFileSync(path19.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8897
+ fs18.writeFileSync(path20.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8765
8898
  } catch {
8766
8899
  }
8767
8900
  return true;
@@ -8781,12 +8914,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8781
8914
  let indexFile = null;
8782
8915
  try {
8783
8916
  const rawFilePath = extractFilePath(args);
8784
- const absFilePath = rawFilePath && path19.isAbsolute(rawFilePath) ? rawFilePath : null;
8917
+ const absFilePath = rawFilePath && path20.isAbsolute(rawFilePath) ? rawFilePath : null;
8785
8918
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8786
8919
  const shadowDir = getShadowRepoDir(cwd);
8787
8920
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8788
8921
  writeShadowExcludes(shadowDir, ignorePaths);
8789
- indexFile = path19.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8922
+ indexFile = path20.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8790
8923
  const shadowEnv = {
8791
8924
  ...process.env,
8792
8925
  GIT_DIR: shadowDir,
@@ -8858,7 +8991,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8858
8991
  writeStack(stack);
8859
8992
  const entry = stack[stack.length - 1];
8860
8993
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8861
- fs17.writeFileSync(UNDO_LATEST_PATH, commitHash);
8994
+ fs18.writeFileSync(UNDO_LATEST_PATH, commitHash);
8862
8995
  if (shouldGc) {
8863
8996
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8864
8997
  }
@@ -8869,7 +9002,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8869
9002
  } finally {
8870
9003
  if (indexFile) {
8871
9004
  try {
8872
- fs17.unlinkSync(indexFile);
9005
+ fs18.unlinkSync(indexFile);
8873
9006
  } catch {
8874
9007
  }
8875
9008
  }
@@ -8945,9 +9078,9 @@ function applyUndo(hash, cwd) {
8945
9078
  timeout: GIT_TIMEOUT
8946
9079
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8947
9080
  for (const file of [...tracked, ...untracked]) {
8948
- const fullPath = path19.join(dir, file);
8949
- if (!snapshotFiles.has(file) && fs17.existsSync(fullPath)) {
8950
- fs17.unlinkSync(fullPath);
9081
+ const fullPath = path20.join(dir, file);
9082
+ if (!snapshotFiles.has(file) && fs18.existsSync(fullPath)) {
9083
+ fs18.unlinkSync(fullPath);
8951
9084
  }
8952
9085
  }
8953
9086
  return true;
@@ -8971,9 +9104,9 @@ function registerCheckCommand(program2) {
8971
9104
  } catch (err2) {
8972
9105
  const tempConfig = getConfig();
8973
9106
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
8974
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9107
+ const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
8975
9108
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
8976
- fs18.appendFileSync(
9109
+ fs19.appendFileSync(
8977
9110
  logPath,
8978
9111
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
8979
9112
  RAW: ${raw}
@@ -8986,10 +9119,10 @@ RAW: ${raw}
8986
9119
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
8987
9120
  try {
8988
9121
  const scriptPath = process.argv[1];
8989
- if (typeof scriptPath !== "string" || !path20.isAbsolute(scriptPath))
9122
+ if (typeof scriptPath !== "string" || !path21.isAbsolute(scriptPath))
8990
9123
  throw new Error("node9: argv[1] is not an absolute path");
8991
- const resolvedScript = fs18.realpathSync(scriptPath);
8992
- 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"));
8993
9126
  if (resolvedScript !== expectedCli)
8994
9127
  throw new Error(
8995
9128
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9015,10 +9148,10 @@ RAW: ${raw}
9015
9148
  }
9016
9149
  }
9017
9150
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9018
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9019
- if (!fs18.existsSync(path20.dirname(logPath)))
9020
- fs18.mkdirSync(path20.dirname(logPath), { recursive: true });
9021
- 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}
9022
9155
  `);
9023
9156
  }
9024
9157
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9031,8 +9164,8 @@ RAW: ${raw}
9031
9164
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9032
9165
  let ttyFd = null;
9033
9166
  try {
9034
- ttyFd = fs18.openSync("/dev/tty", "w");
9035
- const writeTty = (line) => fs18.writeSync(ttyFd, line + "\n");
9167
+ ttyFd = fs19.openSync("/dev/tty", "w");
9168
+ const writeTty = (line) => fs19.writeSync(ttyFd, line + "\n");
9036
9169
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9037
9170
  writeTty(chalk5.bgRed.white.bold(`
9038
9171
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9041,6 +9174,7 @@ RAW: ${raw}
9041
9174
  writeTty(chalk5.red(`
9042
9175
  \u{1F6D1} Node9 blocked "${toolName}"`));
9043
9176
  }
9177
+ if (result2?.ruleDescription) writeTty(chalk5.white(` ${result2.ruleDescription}`));
9044
9178
  writeTty(chalk5.gray(` Triggered by: ${blockedByContext}`));
9045
9179
  if (result2?.changeHint) writeTty(chalk5.cyan(` To change: ${result2.changeHint}`));
9046
9180
  if (result2?.recoveryCommand)
@@ -9050,7 +9184,7 @@ RAW: ${raw}
9050
9184
  } finally {
9051
9185
  if (ttyFd !== null)
9052
9186
  try {
9053
- fs18.closeSync(ttyFd);
9187
+ fs19.closeSync(ttyFd);
9054
9188
  } catch {
9055
9189
  }
9056
9190
  }
@@ -9082,7 +9216,7 @@ RAW: ${raw}
9082
9216
  if (shouldSnapshot(toolName, toolInput, config)) {
9083
9217
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9084
9218
  }
9085
- 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;
9086
9220
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9087
9221
  cwd: safeCwdForAuth
9088
9222
  });
@@ -9094,12 +9228,12 @@ RAW: ${raw}
9094
9228
  }
9095
9229
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9096
9230
  try {
9097
- const tty = fs18.openSync("/dev/tty", "w");
9098
- fs18.writeSync(
9231
+ const tty = fs19.openSync("/dev/tty", "w");
9232
+ fs19.writeSync(
9099
9233
  tty,
9100
9234
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9101
9235
  );
9102
- fs18.closeSync(tty);
9236
+ fs19.closeSync(tty);
9103
9237
  } catch {
9104
9238
  }
9105
9239
  const daemonReady = await autoStartDaemonAndWait();
@@ -9126,9 +9260,9 @@ RAW: ${raw}
9126
9260
  });
9127
9261
  } catch (err2) {
9128
9262
  if (process.env.NODE9_DEBUG === "1") {
9129
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9263
+ const logPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9130
9264
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9131
- fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9265
+ fs19.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9132
9266
  `);
9133
9267
  }
9134
9268
  process.exit(0);
@@ -9165,9 +9299,9 @@ RAW: ${raw}
9165
9299
  init_audit();
9166
9300
  init_config();
9167
9301
  init_policy();
9168
- import fs19 from "fs";
9169
- import path21 from "path";
9170
- import os15 from "os";
9302
+ import fs20 from "fs";
9303
+ import path22 from "path";
9304
+ import os16 from "os";
9171
9305
  init_daemon();
9172
9306
 
9173
9307
  // src/utils/cp-mv-parser.ts
@@ -9208,9 +9342,9 @@ function containsShellMetachar(token) {
9208
9342
  }
9209
9343
 
9210
9344
  // src/cli/commands/log.ts
9211
- 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;
9212
9346
  function detectTestResult(command, output) {
9213
- if (!TEST_COMMAND_RE.test(command)) return null;
9347
+ if (!TEST_COMMAND_RE2.test(command)) return null;
9214
9348
  const out = output.toLowerCase();
9215
9349
  if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
9216
9350
  out
@@ -9240,10 +9374,10 @@ function registerLogCommand(program2) {
9240
9374
  decision: "allowed",
9241
9375
  source: "post-hook"
9242
9376
  };
9243
- const logPath = path21.join(os15.homedir(), ".node9", "audit.log");
9244
- if (!fs19.existsSync(path21.dirname(logPath)))
9245
- fs19.mkdirSync(path21.dirname(logPath), { recursive: true });
9246
- 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");
9247
9381
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9248
9382
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9249
9383
  if (command) {
@@ -9259,16 +9393,24 @@ function registerLogCommand(program2) {
9259
9393
  if (bashCommand && output) {
9260
9394
  const testResult = detectTestResult(bashCommand, output);
9261
9395
  if (testResult) {
9262
- await notifyActivitySocket({
9263
- id: "test-result",
9264
- ts: Date.now(),
9396
+ appendToLog(LOCAL_AUDIT_LOG, {
9397
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9265
9398
  tool,
9266
- status: testResult === "pass" ? "test_pass" : "test_fail"
9399
+ testResult,
9400
+ source: "test-result"
9267
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
+ }
9268
9410
  }
9269
9411
  }
9270
9412
  }
9271
- 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;
9272
9414
  const config = getConfig(safeCwd);
9273
9415
  if (shouldSnapshot(tool, {}, config)) {
9274
9416
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9277,9 +9419,9 @@ function registerLogCommand(program2) {
9277
9419
  const msg = err2 instanceof Error ? err2.message : String(err2);
9278
9420
  process.stderr.write(`[Node9] audit log error: ${msg}
9279
9421
  `);
9280
- const debugPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9422
+ const debugPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9281
9423
  try {
9282
- fs19.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9424
+ fs20.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9283
9425
  `);
9284
9426
  } catch {
9285
9427
  }
@@ -9680,13 +9822,13 @@ function registerConfigShowCommand(program2) {
9680
9822
  // src/cli/commands/doctor.ts
9681
9823
  init_daemon();
9682
9824
  import chalk7 from "chalk";
9683
- import fs20 from "fs";
9684
- import path22 from "path";
9685
- import os16 from "os";
9825
+ import fs21 from "fs";
9826
+ import path23 from "path";
9827
+ import os17 from "os";
9686
9828
  import { execSync as execSync2 } from "child_process";
9687
9829
  function registerDoctorCommand(program2, version2) {
9688
9830
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9689
- const homeDir2 = os16.homedir();
9831
+ const homeDir2 = os17.homedir();
9690
9832
  let failures = 0;
9691
9833
  function pass(msg) {
9692
9834
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -9735,10 +9877,10 @@ function registerDoctorCommand(program2, version2) {
9735
9877
  );
9736
9878
  }
9737
9879
  section("Configuration");
9738
- const globalConfigPath = path22.join(homeDir2, ".node9", "config.json");
9739
- if (fs20.existsSync(globalConfigPath)) {
9880
+ const globalConfigPath = path23.join(homeDir2, ".node9", "config.json");
9881
+ if (fs21.existsSync(globalConfigPath)) {
9740
9882
  try {
9741
- JSON.parse(fs20.readFileSync(globalConfigPath, "utf-8"));
9883
+ JSON.parse(fs21.readFileSync(globalConfigPath, "utf-8"));
9742
9884
  pass("~/.node9/config.json found and valid");
9743
9885
  } catch {
9744
9886
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9746,10 +9888,10 @@ function registerDoctorCommand(program2, version2) {
9746
9888
  } else {
9747
9889
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9748
9890
  }
9749
- const projectConfigPath = path22.join(process.cwd(), "node9.config.json");
9750
- if (fs20.existsSync(projectConfigPath)) {
9891
+ const projectConfigPath = path23.join(process.cwd(), "node9.config.json");
9892
+ if (fs21.existsSync(projectConfigPath)) {
9751
9893
  try {
9752
- JSON.parse(fs20.readFileSync(projectConfigPath, "utf-8"));
9894
+ JSON.parse(fs21.readFileSync(projectConfigPath, "utf-8"));
9753
9895
  pass("node9.config.json found and valid (project)");
9754
9896
  } catch {
9755
9897
  fail(
@@ -9758,8 +9900,8 @@ function registerDoctorCommand(program2, version2) {
9758
9900
  );
9759
9901
  }
9760
9902
  }
9761
- const credsPath = path22.join(homeDir2, ".node9", "credentials.json");
9762
- if (fs20.existsSync(credsPath)) {
9903
+ const credsPath = path23.join(homeDir2, ".node9", "credentials.json");
9904
+ if (fs21.existsSync(credsPath)) {
9763
9905
  pass("Cloud credentials found (~/.node9/credentials.json)");
9764
9906
  } else {
9765
9907
  warn(
@@ -9768,10 +9910,10 @@ function registerDoctorCommand(program2, version2) {
9768
9910
  );
9769
9911
  }
9770
9912
  section("Agent Hooks");
9771
- const claudeSettingsPath = path22.join(homeDir2, ".claude", "settings.json");
9772
- if (fs20.existsSync(claudeSettingsPath)) {
9913
+ const claudeSettingsPath = path23.join(homeDir2, ".claude", "settings.json");
9914
+ if (fs21.existsSync(claudeSettingsPath)) {
9773
9915
  try {
9774
- const cs = JSON.parse(fs20.readFileSync(claudeSettingsPath, "utf-8"));
9916
+ const cs = JSON.parse(fs21.readFileSync(claudeSettingsPath, "utf-8"));
9775
9917
  const hasHook = cs.hooks?.PreToolUse?.some(
9776
9918
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9777
9919
  );
@@ -9787,10 +9929,10 @@ function registerDoctorCommand(program2, version2) {
9787
9929
  } else {
9788
9930
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9789
9931
  }
9790
- const geminiSettingsPath = path22.join(homeDir2, ".gemini", "settings.json");
9791
- if (fs20.existsSync(geminiSettingsPath)) {
9932
+ const geminiSettingsPath = path23.join(homeDir2, ".gemini", "settings.json");
9933
+ if (fs21.existsSync(geminiSettingsPath)) {
9792
9934
  try {
9793
- const gs = JSON.parse(fs20.readFileSync(geminiSettingsPath, "utf-8"));
9935
+ const gs = JSON.parse(fs21.readFileSync(geminiSettingsPath, "utf-8"));
9794
9936
  const hasHook = gs.hooks?.BeforeTool?.some(
9795
9937
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9796
9938
  );
@@ -9806,10 +9948,10 @@ function registerDoctorCommand(program2, version2) {
9806
9948
  } else {
9807
9949
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9808
9950
  }
9809
- const cursorHooksPath = path22.join(homeDir2, ".cursor", "hooks.json");
9810
- if (fs20.existsSync(cursorHooksPath)) {
9951
+ const cursorHooksPath = path23.join(homeDir2, ".cursor", "hooks.json");
9952
+ if (fs21.existsSync(cursorHooksPath)) {
9811
9953
  try {
9812
- const cur = JSON.parse(fs20.readFileSync(cursorHooksPath, "utf-8"));
9954
+ const cur = JSON.parse(fs21.readFileSync(cursorHooksPath, "utf-8"));
9813
9955
  const hasHook = cur.hooks?.preToolUse?.some(
9814
9956
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9815
9957
  );
@@ -9847,9 +9989,9 @@ function registerDoctorCommand(program2, version2) {
9847
9989
 
9848
9990
  // src/cli/commands/audit.ts
9849
9991
  import chalk8 from "chalk";
9850
- import fs21 from "fs";
9851
- import path23 from "path";
9852
- import os17 from "os";
9992
+ import fs22 from "fs";
9993
+ import path24 from "path";
9994
+ import os18 from "os";
9853
9995
  function formatRelativeTime(timestamp) {
9854
9996
  const diff = Date.now() - new Date(timestamp).getTime();
9855
9997
  const sec = Math.floor(diff / 1e3);
@@ -9862,14 +10004,14 @@ function formatRelativeTime(timestamp) {
9862
10004
  }
9863
10005
  function registerAuditCommand(program2) {
9864
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) => {
9865
- const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9866
- if (!fs21.existsSync(logPath)) {
10007
+ const logPath = path24.join(os18.homedir(), ".node9", "audit.log");
10008
+ if (!fs22.existsSync(logPath)) {
9867
10009
  console.log(
9868
10010
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
9869
10011
  );
9870
10012
  return;
9871
10013
  }
9872
- const raw = fs21.readFileSync(logPath, "utf-8");
10014
+ const raw = fs22.readFileSync(logPath, "utf-8");
9873
10015
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
9874
10016
  let entries = lines.flatMap((line) => {
9875
10017
  try {
@@ -9921,10 +10063,398 @@ function registerAuditCommand(program2) {
9921
10063
  });
9922
10064
  }
9923
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
+
9924
10454
  // src/cli/commands/daemon-cmd.ts
9925
10455
  init_daemon2();
9926
10456
  init_daemon();
9927
- import chalk9 from "chalk";
10457
+ import chalk10 from "chalk";
9928
10458
  import { spawn as spawn7 } from "child_process";
9929
10459
  function registerDaemonCommand(program2) {
9930
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(
@@ -9937,7 +10467,7 @@ function registerDaemonCommand(program2) {
9937
10467
  if (cmd === "status") return daemonStatus();
9938
10468
  if (cmd !== "start" && action !== void 0) {
9939
10469
  console.error(
9940
- chalk9.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10470
+ chalk10.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
9941
10471
  );
9942
10472
  process.exit(1);
9943
10473
  }
@@ -9945,7 +10475,7 @@ function registerDaemonCommand(program2) {
9945
10475
  process.env.NODE9_WATCH_MODE = "1";
9946
10476
  setTimeout(() => {
9947
10477
  openBrowserLocal();
9948
- 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}/`));
9949
10479
  }, 600);
9950
10480
  startDaemon();
9951
10481
  return;
@@ -9953,7 +10483,7 @@ function registerDaemonCommand(program2) {
9953
10483
  if (options.openui) {
9954
10484
  if (isDaemonRunning()) {
9955
10485
  openBrowserLocal();
9956
- 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}/`));
9957
10487
  process.exit(0);
9958
10488
  }
9959
10489
  const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
@@ -9966,7 +10496,7 @@ function registerDaemonCommand(program2) {
9966
10496
  if (isDaemonRunning()) break;
9967
10497
  }
9968
10498
  openBrowserLocal();
9969
- console.log(chalk9.green(`
10499
+ console.log(chalk10.green(`
9970
10500
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
9971
10501
  process.exit(0);
9972
10502
  }
@@ -9976,7 +10506,7 @@ function registerDaemonCommand(program2) {
9976
10506
  stdio: "ignore"
9977
10507
  });
9978
10508
  child.unref();
9979
- console.log(chalk9.green(`
10509
+ console.log(chalk10.green(`
9980
10510
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
9981
10511
  process.exit(0);
9982
10512
  }
@@ -9988,13 +10518,13 @@ function registerDaemonCommand(program2) {
9988
10518
  // src/cli/commands/status.ts
9989
10519
  init_core();
9990
10520
  init_daemon();
9991
- import chalk10 from "chalk";
9992
- import fs22 from "fs";
9993
- import path24 from "path";
9994
- import os18 from "os";
10521
+ import chalk11 from "chalk";
10522
+ import fs24 from "fs";
10523
+ import path26 from "path";
10524
+ import os20 from "os";
9995
10525
  function readJson2(filePath) {
9996
10526
  try {
9997
- 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"));
9998
10528
  } catch {
9999
10529
  }
10000
10530
  return null;
@@ -10008,21 +10538,21 @@ function wrappedMcpServers(servers) {
10008
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(" ")}`);
10009
10539
  }
10010
10540
  function printAgentSection(label, hookPairs, wrapped) {
10011
- console.log(chalk10.bold(` ${label}`));
10541
+ console.log(chalk11.bold(` ${label}`));
10012
10542
  for (const { name, present } of hookPairs) {
10013
10543
  if (present) {
10014
- console.log(chalk10.green(` \u2713 ${name}`));
10544
+ console.log(chalk11.green(` \u2713 ${name}`));
10015
10545
  } else {
10016
- console.log(chalk10.red(` \u2717 ${name}`) + chalk10.gray(" (not wired)"));
10546
+ console.log(chalk11.red(` \u2717 ${name}`) + chalk11.gray(" (not wired)"));
10017
10547
  }
10018
10548
  }
10019
10549
  if (wrapped.length > 0) {
10020
- console.log(chalk10.cyan(` MCP proxied:`));
10550
+ console.log(chalk11.cyan(` MCP proxied:`));
10021
10551
  for (const entry of wrapped) {
10022
- console.log(chalk10.gray(` \u2022 ${entry}`));
10552
+ console.log(chalk11.gray(` \u2022 ${entry}`));
10023
10553
  }
10024
10554
  } else {
10025
- console.log(chalk10.gray(` MCP proxied: none`));
10555
+ console.log(chalk11.gray(` MCP proxied: none`));
10026
10556
  }
10027
10557
  }
10028
10558
  function registerStatusCommand(program2) {
@@ -10033,58 +10563,58 @@ function registerStatusCommand(program2) {
10033
10563
  const settings = mergedConfig.settings;
10034
10564
  console.log("");
10035
10565
  if (creds && settings.approvers.cloud) {
10036
- 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"));
10037
10567
  } else if (creds && !settings.approvers.cloud) {
10038
10568
  console.log(
10039
- 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")
10040
10570
  );
10041
10571
  } else {
10042
10572
  console.log(
10043
- 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)")
10044
10574
  );
10045
10575
  }
10046
10576
  console.log("");
10047
10577
  if (daemonRunning) {
10048
10578
  console.log(
10049
- 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}/`)
10050
10580
  );
10051
10581
  } else {
10052
- console.log(chalk10.gray(" \u25CB Daemon stopped"));
10582
+ console.log(chalk11.gray(" \u25CB Daemon stopped"));
10053
10583
  }
10054
10584
  if (settings.enableUndo) {
10055
10585
  console.log(
10056
- 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`)
10057
10587
  );
10058
10588
  }
10059
10589
  console.log("");
10060
- 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");
10061
10591
  console.log(` Mode: ${modeLabel}`);
10062
- const projectConfig = path24.join(process.cwd(), "node9.config.json");
10063
- 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");
10064
10594
  console.log(
10065
- ` 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")}`
10066
10596
  );
10067
10597
  console.log(
10068
- ` 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")}`
10069
10599
  );
10070
10600
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10071
10601
  console.log(
10072
- ` Sandbox: ${chalk10.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10602
+ ` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10073
10603
  );
10074
10604
  }
10075
- const homeDir2 = os18.homedir();
10605
+ const homeDir2 = os20.homedir();
10076
10606
  const claudeSettings = readJson2(
10077
- path24.join(homeDir2, ".claude", "settings.json")
10607
+ path26.join(homeDir2, ".claude", "settings.json")
10078
10608
  );
10079
- const claudeConfig = readJson2(path24.join(homeDir2, ".claude.json"));
10609
+ const claudeConfig = readJson2(path26.join(homeDir2, ".claude.json"));
10080
10610
  const geminiSettings = readJson2(
10081
- path24.join(homeDir2, ".gemini", "settings.json")
10611
+ path26.join(homeDir2, ".gemini", "settings.json")
10082
10612
  );
10083
- const cursorConfig = readJson2(path24.join(homeDir2, ".cursor", "mcp.json"));
10613
+ const cursorConfig = readJson2(path26.join(homeDir2, ".cursor", "mcp.json"));
10084
10614
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10085
10615
  if (agentFound) {
10086
10616
  console.log("");
10087
- console.log(chalk10.bold(" Agent Wiring:"));
10617
+ console.log(chalk11.bold(" Agent Wiring:"));
10088
10618
  console.log("");
10089
10619
  if (claudeSettings || claudeConfig) {
10090
10620
  const preHook = claudeSettings?.hooks?.PreToolUse?.some(
@@ -10130,7 +10660,7 @@ function registerStatusCommand(program2) {
10130
10660
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
10131
10661
  console.log("");
10132
10662
  console.log(
10133
- 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")
10134
10664
  );
10135
10665
  }
10136
10666
  console.log("");
@@ -10139,10 +10669,10 @@ function registerStatusCommand(program2) {
10139
10669
 
10140
10670
  // src/cli/commands/init.ts
10141
10671
  init_core();
10142
- import chalk11 from "chalk";
10143
- import fs23 from "fs";
10144
- import path25 from "path";
10145
- import os19 from "os";
10672
+ import chalk12 from "chalk";
10673
+ import fs25 from "fs";
10674
+ import path27 from "path";
10675
+ import os21 from "os";
10146
10676
  import https2 from "https";
10147
10677
  init_shields();
10148
10678
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
@@ -10177,7 +10707,7 @@ function fireTelemetryPing(agents) {
10177
10707
  }
10178
10708
  function registerInitCommand(program2) {
10179
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) => {
10180
- 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"));
10181
10711
  let chosenMode = options.mode.toLowerCase();
10182
10712
  if (!["standard", "strict", "audit"].includes(chosenMode)) {
10183
10713
  chosenMode = DEFAULT_CONFIG.settings.mode;
@@ -10196,37 +10726,37 @@ function registerInitCommand(program2) {
10196
10726
  const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
10197
10727
  if (hasNewShields) writeActiveShields(merged);
10198
10728
  } catch (err2) {
10199
- 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)}`));
10200
10730
  }
10201
10731
  }
10202
10732
  console.log("");
10203
10733
  }
10204
- const configPath = path25.join(os19.homedir(), ".node9", "config.json");
10205
- if (fs23.existsSync(configPath) && !options.force) {
10734
+ const configPath = path27.join(os21.homedir(), ".node9", "config.json");
10735
+ if (fs25.existsSync(configPath) && !options.force) {
10206
10736
  try {
10207
- const existing = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
10737
+ const existing = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
10208
10738
  const settings = existing.settings ?? {};
10209
10739
  if (settings.mode !== chosenMode) {
10210
10740
  settings.mode = chosenMode;
10211
10741
  existing.settings = settings;
10212
- fs23.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10213
- 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}`));
10214
10744
  } else {
10215
- console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10745
+ console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10216
10746
  }
10217
10747
  } catch {
10218
- console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10748
+ console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10219
10749
  }
10220
10750
  } else {
10221
10751
  const configToSave = {
10222
10752
  ...DEFAULT_CONFIG,
10223
10753
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10224
10754
  };
10225
- const dir = path25.dirname(configPath);
10226
- if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
10227
- fs23.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10228
- console.log(chalk11.green(`\u2705 Config created: ${configPath}`));
10229
- 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}`));
10230
10760
  }
10231
10761
  if (options.skipSetup) return;
10232
10762
  console.log("");
@@ -10236,18 +10766,18 @@ function registerInitCommand(program2) {
10236
10766
  );
10237
10767
  if (found.length === 0) {
10238
10768
  console.log(
10239
- 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")
10240
10770
  );
10241
- 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>"));
10242
10772
  return;
10243
10773
  }
10244
- console.log(chalk11.bold("Detected agents:"));
10774
+ console.log(chalk12.bold("Detected agents:"));
10245
10775
  for (const agent of found) {
10246
- console.log(chalk11.green(` \u2713 ${agent}`));
10776
+ console.log(chalk12.green(` \u2713 ${agent}`));
10247
10777
  }
10248
10778
  console.log("");
10249
10779
  for (const agent of found) {
10250
- console.log(chalk11.bold(`Wiring ${agent}...`));
10780
+ console.log(chalk12.bold(`Wiring ${agent}...`));
10251
10781
  if (agent === "claude") await setupClaude();
10252
10782
  else if (agent === "gemini") await setupGemini();
10253
10783
  else if (agent === "cursor") await setupCursor();
@@ -10264,26 +10794,26 @@ function registerInitCommand(program2) {
10264
10794
  console.log("");
10265
10795
  }
10266
10796
  const agentList = found.join(", ");
10267
- 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}!`));
10268
10798
  console.log("");
10269
- console.log(chalk11.white(" Watch live: ") + chalk11.cyan("node9 tail"));
10270
- 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"));
10271
10801
  console.log("");
10272
- 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"));
10273
10803
  console.log(
10274
- 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")
10275
10805
  );
10276
- 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"));
10277
10807
  });
10278
10808
  }
10279
10809
 
10280
10810
  // src/cli/commands/undo.ts
10281
- import path26 from "path";
10282
- import chalk13 from "chalk";
10811
+ import path28 from "path";
10812
+ import chalk14 from "chalk";
10283
10813
 
10284
10814
  // src/tui/undo-navigator.ts
10285
10815
  import readline2 from "readline";
10286
- import chalk12 from "chalk";
10816
+ import chalk13 from "chalk";
10287
10817
  var RESET = "\x1B[0m";
10288
10818
  var BOLD = "\x1B[1m";
10289
10819
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
@@ -10301,15 +10831,15 @@ function renderDiff(raw) {
10301
10831
  );
10302
10832
  for (const line of lines) {
10303
10833
  if (line.startsWith("+++") || line.startsWith("---")) {
10304
- process.stdout.write(chalk12.bold(line) + "\n");
10834
+ process.stdout.write(chalk13.bold(line) + "\n");
10305
10835
  } else if (line.startsWith("+")) {
10306
- process.stdout.write(chalk12.green(line) + "\n");
10836
+ process.stdout.write(chalk13.green(line) + "\n");
10307
10837
  } else if (line.startsWith("-")) {
10308
- process.stdout.write(chalk12.red(line) + "\n");
10838
+ process.stdout.write(chalk13.red(line) + "\n");
10309
10839
  } else if (line.startsWith("@@")) {
10310
- process.stdout.write(chalk12.cyan(line) + "\n");
10840
+ process.stdout.write(chalk13.cyan(line) + "\n");
10311
10841
  } else {
10312
- process.stdout.write(chalk12.gray(line) + "\n");
10842
+ process.stdout.write(chalk13.gray(line) + "\n");
10313
10843
  }
10314
10844
  }
10315
10845
  }
@@ -10328,23 +10858,23 @@ function render(entries, idx) {
10328
10858
  const step = idx + 1;
10329
10859
  process.stdout.write(CLEAR_SCREEN);
10330
10860
  process.stdout.write(
10331
- 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(
10332
10862
  ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
10333
10863
  ) : "") + "\n\n"
10334
10864
  );
10335
10865
  process.stdout.write(
10336
- ` ${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"
10337
10867
  );
10338
- process.stdout.write(` ${BOLD}When:${RESET} ${chalk12.gray(formatAge(entry.timestamp))}
10868
+ process.stdout.write(` ${BOLD}When:${RESET} ${chalk13.gray(formatAge(entry.timestamp))}
10339
10869
  `);
10340
- process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk12.gray(entry.cwd)}
10870
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk13.gray(entry.cwd)}
10341
10871
  `);
10342
10872
  if (entry.files && entry.files.length > 0) {
10343
- process.stdout.write(` ${BOLD}Files:${RESET} ${chalk12.gray(entry.files.join(", "))}
10873
+ process.stdout.write(` ${BOLD}Files:${RESET} ${chalk13.gray(entry.files.join(", "))}
10344
10874
  `);
10345
10875
  }
10346
10876
  if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
10347
- 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"));
10348
10878
  }
10349
10879
  process.stdout.write("\n");
10350
10880
  const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
@@ -10352,12 +10882,12 @@ function render(entries, idx) {
10352
10882
  renderDiff(diff);
10353
10883
  } else {
10354
10884
  process.stdout.write(
10355
- 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")
10356
10886
  );
10357
10887
  }
10358
10888
  process.stdout.write("\n");
10359
10889
  process.stdout.write(
10360
- 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"
10361
10891
  );
10362
10892
  }
10363
10893
  async function runUndoNavigator(entries) {
@@ -10411,19 +10941,19 @@ async function runUndoNavigator(entries) {
10411
10941
  cleanup();
10412
10942
  process.stdout.write(CLEAR_SCREEN);
10413
10943
  const entry = display[idx];
10414
- 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"));
10415
10945
  if (applyUndo(entry.hash, entry.cwd)) {
10416
- process.stdout.write(chalk12.green("\u2705 Reverted successfully.\n\n"));
10946
+ process.stdout.write(chalk13.green("\u2705 Reverted successfully.\n\n"));
10417
10947
  resolve({ restored: true });
10418
10948
  } else {
10419
- process.stdout.write(chalk12.red("\u274C Undo failed.\n\n"));
10949
+ process.stdout.write(chalk13.red("\u274C Undo failed.\n\n"));
10420
10950
  resolve({ restored: false });
10421
10951
  }
10422
10952
  } else if (name === "q" || key?.ctrl && name === "c") {
10423
10953
  done = true;
10424
10954
  cleanup();
10425
10955
  process.stdout.write(CLEAR_SCREEN);
10426
- process.stdout.write(chalk12.gray("\nCancelled.\n\n"));
10956
+ process.stdout.write(chalk13.gray("\nCancelled.\n\n"));
10427
10957
  resolve({ restored: false });
10428
10958
  }
10429
10959
  };
@@ -10437,7 +10967,7 @@ function findMatchingCwd(startDir, history) {
10437
10967
  let dir = startDir;
10438
10968
  while (true) {
10439
10969
  if (cwds.has(dir)) return dir;
10440
- const parent = path26.dirname(dir);
10970
+ const parent = path28.dirname(dir);
10441
10971
  if (parent === dir) return null;
10442
10972
  dir = parent;
10443
10973
  }
@@ -10459,39 +10989,39 @@ function registerUndoCommand(program2) {
10459
10989
  if (history.length === 0) {
10460
10990
  if (!options.all && allHistory.length > 0) {
10461
10991
  console.log(
10462
- chalk13.yellow(
10992
+ chalk14.yellow(
10463
10993
  `
10464
10994
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
10465
- 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.
10466
10996
  `
10467
10997
  )
10468
10998
  );
10469
10999
  } else {
10470
- 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"));
10471
11001
  }
10472
11002
  return;
10473
11003
  }
10474
11004
  if (options.list) {
10475
- console.log(chalk13.magenta.bold("\n\u23EA Snapshot History\n"));
11005
+ console.log(chalk14.magenta.bold("\n\u23EA Snapshot History\n"));
10476
11006
  console.log(
10477
- chalk13.gray(
11007
+ chalk14.gray(
10478
11008
  ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
10479
11009
  )
10480
11010
  );
10481
- console.log(chalk13.gray(" " + "\u2500".repeat(80)));
11011
+ console.log(chalk14.gray(" " + "\u2500".repeat(80)));
10482
11012
  const display = [...history].reverse();
10483
11013
  let prevTs = null;
10484
11014
  for (let i = 0; i < display.length; i++) {
10485
11015
  const e = display[i];
10486
11016
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
10487
- if (isGap) console.log(chalk13.gray(" \u2500\u2500 earlier \u2500\u2500"));
11017
+ if (isGap) console.log(chalk14.gray(" \u2500\u2500 earlier \u2500\u2500"));
10488
11018
  const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
10489
11019
  const tool = e.tool.slice(0, 8).padEnd(8);
10490
11020
  const when = formatAge2(e.timestamp).padEnd(10);
10491
11021
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
10492
11022
  console.log(
10493
- chalk13.white(
10494
- ` ${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)}`
10495
11025
  )
10496
11026
  );
10497
11027
  prevTs = e.timestamp;
@@ -10504,7 +11034,7 @@ function registerUndoCommand(program2) {
10504
11034
  const idx = history.length - steps;
10505
11035
  if (idx < 0) {
10506
11036
  console.log(
10507
- chalk13.yellow(
11037
+ chalk14.yellow(
10508
11038
  `
10509
11039
  \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10510
11040
  `
@@ -10515,47 +11045,47 @@ function registerUndoCommand(program2) {
10515
11045
  const snapshot = history[idx];
10516
11046
  const ageStr = formatAge2(snapshot.timestamp);
10517
11047
  console.log(
10518
- chalk13.magenta.bold(`
11048
+ chalk14.magenta.bold(`
10519
11049
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
10520
11050
  );
10521
11051
  console.log(
10522
- chalk13.white(
10523
- ` 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) : ""}`
10524
11054
  )
10525
11055
  );
10526
- console.log(chalk13.white(` When: ${chalk13.gray(ageStr)}`));
10527
- 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)}`));
10528
11058
  if (steps > 1)
10529
11059
  console.log(
10530
- 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.`)
10531
11061
  );
10532
11062
  console.log("");
10533
11063
  const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10534
11064
  if (diff) {
10535
11065
  const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10536
11066
  for (const line of lines) {
10537
- if (line.startsWith("+++") || line.startsWith("---")) console.log(chalk13.bold(line));
10538
- else if (line.startsWith("+")) console.log(chalk13.green(line));
10539
- else if (line.startsWith("-")) console.log(chalk13.red(line));
10540
- else if (line.startsWith("@@")) console.log(chalk13.cyan(line));
10541
- 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));
10542
11072
  }
10543
11073
  console.log("");
10544
11074
  } else {
10545
11075
  console.log(
10546
- 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")
10547
11077
  );
10548
11078
  }
10549
11079
  const { confirm: confirm3 } = await import("@inquirer/prompts");
10550
11080
  const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10551
11081
  if (proceed) {
10552
11082
  if (applyUndo(snapshot.hash, snapshot.cwd)) {
10553
- console.log(chalk13.green("\n\u2705 Reverted successfully.\n"));
11083
+ console.log(chalk14.green("\n\u2705 Reverted successfully.\n"));
10554
11084
  } else {
10555
- 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"));
10556
11086
  }
10557
11087
  } else {
10558
- console.log(chalk13.gray("\nCancelled.\n"));
11088
+ console.log(chalk14.gray("\nCancelled.\n"));
10559
11089
  }
10560
11090
  return;
10561
11091
  }
@@ -10565,7 +11095,7 @@ function registerUndoCommand(program2) {
10565
11095
 
10566
11096
  // src/cli/commands/watch.ts
10567
11097
  init_daemon();
10568
- import chalk14 from "chalk";
11098
+ import chalk15 from "chalk";
10569
11099
  import { spawn as spawn8, spawnSync as spawnSync5 } from "child_process";
10570
11100
  function registerWatchCommand(program2) {
10571
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) => {
@@ -10581,7 +11111,7 @@ function registerWatchCommand(program2) {
10581
11111
  throw new Error("not running");
10582
11112
  }
10583
11113
  } catch {
10584
- 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)..."));
10585
11115
  const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
10586
11116
  detached: true,
10587
11117
  stdio: "ignore",
@@ -10603,12 +11133,12 @@ function registerWatchCommand(program2) {
10603
11133
  }
10604
11134
  }
10605
11135
  if (!ready) {
10606
- 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"));
10607
11137
  process.exit(1);
10608
11138
  }
10609
11139
  }
10610
11140
  console.error(
10611
- 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(
10612
11142
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
10613
11143
  )
10614
11144
  );
@@ -10617,7 +11147,7 @@ function registerWatchCommand(program2) {
10617
11147
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
10618
11148
  });
10619
11149
  if (result.error) {
10620
- 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}`));
10621
11151
  process.exit(1);
10622
11152
  }
10623
11153
  process.exit(result.status ?? 0);
@@ -10627,18 +11157,18 @@ function registerWatchCommand(program2) {
10627
11157
  // src/mcp-gateway/index.ts
10628
11158
  init_orchestrator();
10629
11159
  import readline3 from "readline";
10630
- import chalk15 from "chalk";
11160
+ import chalk16 from "chalk";
10631
11161
  import { spawn as spawn9 } from "child_process";
10632
11162
  import { execa as execa2 } from "execa";
10633
11163
  init_provenance();
10634
11164
 
10635
11165
  // src/mcp-pin.ts
10636
- import fs24 from "fs";
10637
- import path27 from "path";
10638
- import os20 from "os";
10639
- import crypto3 from "crypto";
11166
+ import fs26 from "fs";
11167
+ import path29 from "path";
11168
+ import os22 from "os";
11169
+ import crypto4 from "crypto";
10640
11170
  function getPinsFilePath() {
10641
- return path27.join(os20.homedir(), ".node9", "mcp-pins.json");
11171
+ return path29.join(os22.homedir(), ".node9", "mcp-pins.json");
10642
11172
  }
10643
11173
  function hashToolDefinitions(tools) {
10644
11174
  const sorted = [...tools].sort((a, b) => {
@@ -10647,15 +11177,15 @@ function hashToolDefinitions(tools) {
10647
11177
  return nameA.localeCompare(nameB);
10648
11178
  });
10649
11179
  const canonical = JSON.stringify(sorted);
10650
- return crypto3.createHash("sha256").update(canonical).digest("hex");
11180
+ return crypto4.createHash("sha256").update(canonical).digest("hex");
10651
11181
  }
10652
11182
  function getServerKey(upstreamCommand) {
10653
- return crypto3.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
11183
+ return crypto4.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
10654
11184
  }
10655
11185
  function readMcpPinsSafe() {
10656
11186
  const filePath = getPinsFilePath();
10657
11187
  try {
10658
- const raw = fs24.readFileSync(filePath, "utf-8");
11188
+ const raw = fs26.readFileSync(filePath, "utf-8");
10659
11189
  if (!raw.trim()) {
10660
11190
  return { ok: false, reason: "corrupt", detail: "empty file" };
10661
11191
  }
@@ -10679,10 +11209,10 @@ function readMcpPins() {
10679
11209
  }
10680
11210
  function writeMcpPins(data) {
10681
11211
  const filePath = getPinsFilePath();
10682
- fs24.mkdirSync(path27.dirname(filePath), { recursive: true });
10683
- const tmp = `${filePath}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
10684
- fs24.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10685
- 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);
10686
11216
  }
10687
11217
  function checkPin(serverKey, currentHash) {
10688
11218
  const result = readMcpPinsSafe();
@@ -10774,13 +11304,13 @@ async function runMcpGateway(upstreamCommand) {
10774
11304
  const prov = checkProvenance(executable);
10775
11305
  if (prov.trustLevel === "suspect") {
10776
11306
  console.error(
10777
- chalk15.red(
11307
+ chalk16.red(
10778
11308
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
10779
11309
  )
10780
11310
  );
10781
- console.error(chalk15.red(" Verify this binary is trusted before proceeding."));
11311
+ console.error(chalk16.red(" Verify this binary is trusted before proceeding."));
10782
11312
  }
10783
- console.error(chalk15.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
11313
+ console.error(chalk16.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10784
11314
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
10785
11315
  "NODE_OPTIONS",
10786
11316
  "NODE_PATH",
@@ -10883,10 +11413,10 @@ async function runMcpGateway(upstreamCommand) {
10883
11413
  mcpServer
10884
11414
  });
10885
11415
  if (!result.approved) {
10886
- console.error(chalk15.red(`
11416
+ console.error(chalk16.red(`
10887
11417
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
10888
- console.error(chalk15.gray(` Tool: ${toolName}`));
10889
- 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"}
10890
11420
  `));
10891
11421
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
10892
11422
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -10965,7 +11495,7 @@ async function runMcpGateway(upstreamCommand) {
10965
11495
  updatePin(serverKey, upstreamCommand, currentHash, toolNames);
10966
11496
  pinState = "validated";
10967
11497
  console.error(
10968
- chalk15.green(
11498
+ chalk16.green(
10969
11499
  `\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
10970
11500
  )
10971
11501
  );
@@ -10978,11 +11508,11 @@ async function runMcpGateway(upstreamCommand) {
10978
11508
  } else if (pinStatus === "corrupt") {
10979
11509
  pinState = "quarantined";
10980
11510
  console.error(
10981
- 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!")
10982
11512
  );
10983
- 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."));
10984
11514
  console.error(
10985
- 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)
10986
11516
  `)
10987
11517
  );
10988
11518
  const errorResponse = {
@@ -10999,13 +11529,13 @@ async function runMcpGateway(upstreamCommand) {
10999
11529
  } else {
11000
11530
  pinState = "quarantined";
11001
11531
  console.error(
11002
- 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!")
11003
11533
  );
11004
11534
  console.error(
11005
- 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).")
11006
11536
  );
11007
- console.error(chalk15.red(" Session quarantined \u2014 all tool calls blocked."));
11008
- 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}
11009
11539
  `));
11010
11540
  const errorResponse = {
11011
11541
  jsonrpc: "2.0",
@@ -11054,9 +11584,9 @@ function registerMcpGatewayCommand(program2) {
11054
11584
 
11055
11585
  // src/mcp-server/index.ts
11056
11586
  import readline4 from "readline";
11057
- import fs25 from "fs";
11058
- import os21 from "os";
11059
- import path28 from "path";
11587
+ import fs27 from "fs";
11588
+ import os23 from "os";
11589
+ import path30 from "path";
11060
11590
  init_core();
11061
11591
  init_daemon();
11062
11592
  init_shields();
@@ -11231,13 +11761,13 @@ function handleStatus() {
11231
11761
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11232
11762
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11233
11763
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11234
- const projectConfig = path28.join(process.cwd(), "node9.config.json");
11235
- 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");
11236
11766
  lines.push(
11237
- `Project config (node9.config.json): ${fs25.existsSync(projectConfig) ? "present" : "not found"}`
11767
+ `Project config (node9.config.json): ${fs27.existsSync(projectConfig) ? "present" : "not found"}`
11238
11768
  );
11239
11769
  lines.push(
11240
- `Global config (~/.node9/config.json): ${fs25.existsSync(globalConfig) ? "present" : "not found"}`
11770
+ `Global config (~/.node9/config.json): ${fs27.existsSync(globalConfig) ? "present" : "not found"}`
11241
11771
  );
11242
11772
  return lines.join("\n");
11243
11773
  }
@@ -11311,21 +11841,21 @@ function handleShieldDisable(args) {
11311
11841
  writeActiveShields(active.filter((s) => s !== name));
11312
11842
  return `Shield "${name}" disabled.`;
11313
11843
  }
11314
- var GLOBAL_CONFIG_PATH2 = path28.join(os21.homedir(), ".node9", "config.json");
11844
+ var GLOBAL_CONFIG_PATH2 = path30.join(os23.homedir(), ".node9", "config.json");
11315
11845
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11316
11846
  function readGlobalConfigRaw() {
11317
11847
  try {
11318
- if (fs25.existsSync(GLOBAL_CONFIG_PATH2)) {
11319
- 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"));
11320
11850
  }
11321
11851
  } catch {
11322
11852
  }
11323
11853
  return {};
11324
11854
  }
11325
11855
  function writeGlobalConfigRaw(data) {
11326
- const dir = path28.dirname(GLOBAL_CONFIG_PATH2);
11327
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
11328
- 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");
11329
11859
  }
11330
11860
  function handleApproverList() {
11331
11861
  const config = getConfig();
@@ -11368,9 +11898,9 @@ function handleApproverSet(args) {
11368
11898
  }
11369
11899
  function handleAuditGet(args) {
11370
11900
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11371
- const auditPath = path28.join(os21.homedir(), ".node9", "audit.log");
11372
- if (!fs25.existsSync(auditPath)) return "No audit log found.";
11373
- 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);
11374
11904
  const recent = lines.slice(-limit);
11375
11905
  const entries = recent.map((line) => {
11376
11906
  try {
@@ -11558,7 +12088,7 @@ function registerMcpServerCommand(program2) {
11558
12088
 
11559
12089
  // src/cli/commands/trust.ts
11560
12090
  init_trusted_hosts();
11561
- import chalk16 from "chalk";
12091
+ import chalk17 from "chalk";
11562
12092
  function isValidHost(host) {
11563
12093
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
11564
12094
  }
@@ -11568,51 +12098,51 @@ function registerTrustCommand(program2) {
11568
12098
  const normalized = normalizeHost(host.trim());
11569
12099
  if (!isValidHost(normalized)) {
11570
12100
  console.error(
11571
- chalk16.red(`
12101
+ chalk17.red(`
11572
12102
  \u274C Invalid host: "${host}"
11573
- `) + 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")
11574
12104
  );
11575
12105
  process.exit(1);
11576
12106
  }
11577
12107
  addTrustedHost(normalized);
11578
- console.log(chalk16.green(`
12108
+ console.log(chalk17.green(`
11579
12109
  \u2705 ${normalized} added to trusted hosts.`));
11580
12110
  console.log(
11581
- 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")
11582
12112
  );
11583
12113
  });
11584
12114
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
11585
12115
  const normalized = normalizeHost(host.trim());
11586
12116
  const removed = removeTrustedHost(normalized);
11587
12117
  if (!removed) {
11588
- console.error(chalk16.yellow(`
12118
+ console.error(chalk17.yellow(`
11589
12119
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
11590
12120
  `));
11591
12121
  process.exit(1);
11592
12122
  }
11593
- console.log(chalk16.green(`
12123
+ console.log(chalk17.green(`
11594
12124
  \u2705 ${normalized} removed from trusted hosts.
11595
12125
  `));
11596
12126
  });
11597
12127
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
11598
12128
  const hosts = readTrustedHosts();
11599
12129
  if (hosts.length === 0) {
11600
- console.log(chalk16.gray("\n No trusted hosts configured.\n"));
11601
- 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")}
11602
12132
  `);
11603
12133
  return;
11604
12134
  }
11605
- console.log(chalk16.bold("\n\u{1F513} Trusted Hosts\n"));
12135
+ console.log(chalk17.bold("\n\u{1F513} Trusted Hosts\n"));
11606
12136
  for (const entry of hosts) {
11607
12137
  const date = new Date(entry.addedAt).toLocaleDateString();
11608
- 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}`)}`);
11609
12139
  }
11610
12140
  console.log("");
11611
12141
  });
11612
12142
  }
11613
12143
 
11614
12144
  // src/cli/commands/mcp-pin.ts
11615
- import chalk17 from "chalk";
12145
+ import chalk18 from "chalk";
11616
12146
  function registerMcpPinCommand(program2) {
11617
12147
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
11618
12148
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -11620,31 +12150,31 @@ function registerMcpPinCommand(program2) {
11620
12150
  const result = readMcpPinsSafe();
11621
12151
  if (!result.ok) {
11622
12152
  if (result.reason === "missing") {
11623
- console.log(chalk17.gray("\nNo MCP servers are pinned yet."));
12153
+ console.log(chalk18.gray("\nNo MCP servers are pinned yet."));
11624
12154
  console.log(
11625
- 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")
11626
12156
  );
11627
12157
  return;
11628
12158
  }
11629
- console.error(chalk17.red(`
12159
+ console.error(chalk18.red(`
11630
12160
  \u274C Pin file is corrupt: ${result.detail}`));
11631
- console.error(chalk17.yellow(" Run: node9 mcp pin reset\n"));
12161
+ console.error(chalk18.yellow(" Run: node9 mcp pin reset\n"));
11632
12162
  process.exit(1);
11633
12163
  }
11634
12164
  const entries = Object.entries(result.pins.servers);
11635
12165
  if (entries.length === 0) {
11636
- console.log(chalk17.gray("\nNo MCP servers are pinned yet."));
12166
+ console.log(chalk18.gray("\nNo MCP servers are pinned yet."));
11637
12167
  console.log(
11638
- 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")
11639
12169
  );
11640
12170
  return;
11641
12171
  }
11642
- console.log(chalk17.bold("\n\u{1F512} Pinned MCP Servers\n"));
12172
+ console.log(chalk18.bold("\n\u{1F512} Pinned MCP Servers\n"));
11643
12173
  for (const [key, entry] of entries) {
11644
- console.log(` ${chalk17.cyan(key)} ${chalk17.gray(entry.label)}`);
11645
- console.log(` Tools (${entry.toolCount}): ${chalk17.white(entry.toolNames.join(", "))}`);
11646
- console.log(` Hash: ${chalk17.gray(entry.toolsHash.slice(0, 16))}...`);
11647
- 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)}`);
11648
12178
  console.log("");
11649
12179
  }
11650
12180
  });
@@ -11655,55 +12185,55 @@ function registerMcpPinCommand(program2) {
11655
12185
  try {
11656
12186
  pins = readMcpPins();
11657
12187
  } catch {
11658
- console.error(chalk17.red("\n\u274C Pin file is corrupt."));
11659
- 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"));
11660
12190
  process.exit(1);
11661
12191
  }
11662
12192
  if (!pins.servers[serverKey]) {
11663
- console.error(chalk17.red(`
12193
+ console.error(chalk18.red(`
11664
12194
  \u274C No pin found for server key "${serverKey}"
11665
12195
  `));
11666
- 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.
11667
12197
  `);
11668
12198
  process.exit(1);
11669
12199
  }
11670
12200
  const label = pins.servers[serverKey].label;
11671
12201
  removePin(serverKey);
11672
- console.log(chalk17.green(`
11673
- \u{1F513} Pin removed for ${chalk17.cyan(serverKey)}`));
11674
- console.log(chalk17.gray(` Server: ${label}`));
11675
- 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"));
11676
12206
  });
11677
12207
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
11678
12208
  const result = readMcpPinsSafe();
11679
12209
  if (!result.ok && result.reason === "missing") {
11680
- console.log(chalk17.gray("\nNo pins to clear.\n"));
12210
+ console.log(chalk18.gray("\nNo pins to clear.\n"));
11681
12211
  return;
11682
12212
  }
11683
12213
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
11684
12214
  clearAllPins();
11685
- console.log(chalk17.green(`
12215
+ console.log(chalk18.green(`
11686
12216
  \u{1F513} Cleared ${count} MCP pin(s).`));
11687
- 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"));
11688
12218
  });
11689
12219
  }
11690
12220
 
11691
12221
  // src/cli.ts
11692
12222
  var { version } = JSON.parse(
11693
- fs28.readFileSync(path31.join(__dirname, "../package.json"), "utf-8")
12223
+ fs30.readFileSync(path33.join(__dirname, "../package.json"), "utf-8")
11694
12224
  );
11695
12225
  var program = new Command();
11696
12226
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
11697
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) => {
11698
12228
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
11699
- const credPath = path31.join(os24.homedir(), ".node9", "credentials.json");
11700
- if (!fs28.existsSync(path31.dirname(credPath)))
11701
- 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 });
11702
12232
  const profileName = options.profile || "default";
11703
12233
  let existingCreds = {};
11704
12234
  try {
11705
- if (fs28.existsSync(credPath)) {
11706
- const raw = JSON.parse(fs28.readFileSync(credPath, "utf-8"));
12235
+ if (fs30.existsSync(credPath)) {
12236
+ const raw = JSON.parse(fs30.readFileSync(credPath, "utf-8"));
11707
12237
  if (raw.apiKey) {
11708
12238
  existingCreds = {
11709
12239
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -11715,13 +12245,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11715
12245
  } catch {
11716
12246
  }
11717
12247
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
11718
- fs28.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12248
+ fs30.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
11719
12249
  if (profileName === "default") {
11720
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
12250
+ const configPath = path33.join(os26.homedir(), ".node9", "config.json");
11721
12251
  let config = {};
11722
12252
  try {
11723
- if (fs28.existsSync(configPath))
11724
- config = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
12253
+ if (fs30.existsSync(configPath))
12254
+ config = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
11725
12255
  } catch {
11726
12256
  }
11727
12257
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -11736,19 +12266,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11736
12266
  approvers.cloud = false;
11737
12267
  }
11738
12268
  s.approvers = approvers;
11739
- if (!fs28.existsSync(path31.dirname(configPath)))
11740
- fs28.mkdirSync(path31.dirname(configPath), { recursive: true });
11741
- 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 });
11742
12272
  }
11743
12273
  if (options.profile && profileName !== "default") {
11744
- console.log(chalk19.green(`\u2705 Profile "${profileName}" saved`));
11745
- 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`));
11746
12276
  } else if (options.local) {
11747
- console.log(chalk19.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
11748
- 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.`));
11749
12279
  } else {
11750
- console.log(chalk19.green(`\u2705 Logged in \u2014 agent mode`));
11751
- 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.`));
11752
12282
  }
11753
12283
  });
11754
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) => {
@@ -11756,19 +12286,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
11756
12286
  if (target === "claude") return await setupClaude();
11757
12287
  if (target === "cursor") return await setupCursor();
11758
12288
  if (target === "hud") return setupHud();
11759
- 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`));
11760
12290
  process.exit(1);
11761
12291
  });
11762
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) => {
11763
12293
  if (!target) {
11764
- console.log(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
11765
- 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");
11766
12296
  console.log(" Targets:");
11767
- console.log(" " + chalk19.green("claude") + " \u2014 Claude Code (hook mode)");
11768
- console.log(" " + chalk19.green("gemini") + " \u2014 Gemini CLI (hook mode)");
11769
- 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)");
11770
12300
  process.stdout.write(
11771
- " " + chalk19.green("hud") + " \u2014 Claude Code security statusline\n"
12301
+ " " + chalk20.green("hud") + " \u2014 Claude Code security statusline\n"
11772
12302
  );
11773
12303
  console.log("");
11774
12304
  return;
@@ -11778,7 +12308,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
11778
12308
  if (t === "claude") return await setupClaude();
11779
12309
  if (t === "cursor") return await setupCursor();
11780
12310
  if (t === "hud") return setupHud();
11781
- 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`));
11782
12312
  process.exit(1);
11783
12313
  });
11784
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) => {
@@ -11789,31 +12319,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
11789
12319
  else if (target === "hud") fn = teardownHud;
11790
12320
  else {
11791
12321
  console.error(
11792
- chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
12322
+ chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
11793
12323
  );
11794
12324
  process.exit(1);
11795
12325
  }
11796
- console.log(chalk19.cyan(`
12326
+ console.log(chalk20.cyan(`
11797
12327
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
11798
12328
  `));
11799
12329
  try {
11800
12330
  fn();
11801
12331
  } catch (err2) {
11802
- 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)}`));
11803
12333
  process.exit(1);
11804
12334
  }
11805
- 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."));
11806
12336
  });
11807
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) => {
11808
- console.log(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
11809
- 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..."));
11810
12340
  try {
11811
12341
  stopDaemon();
11812
- console.log(chalk19.green(" \u2705 Daemon stopped"));
12342
+ console.log(chalk20.green(" \u2705 Daemon stopped"));
11813
12343
  } catch {
11814
- console.log(chalk19.blue(" \u2139\uFE0F Daemon was not running"));
12344
+ console.log(chalk20.blue(" \u2139\uFE0F Daemon was not running"));
11815
12345
  }
11816
- console.log(chalk19.bold("\nRemoving hooks..."));
12346
+ console.log(chalk20.bold("\nRemoving hooks..."));
11817
12347
  let teardownFailed = false;
11818
12348
  for (const [label, fn] of [
11819
12349
  ["Claude", teardownClaude],
@@ -11825,45 +12355,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
11825
12355
  } catch (err2) {
11826
12356
  teardownFailed = true;
11827
12357
  console.error(
11828
- chalk19.red(
12358
+ chalk20.red(
11829
12359
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
11830
12360
  )
11831
12361
  );
11832
12362
  }
11833
12363
  }
11834
12364
  if (options.purge) {
11835
- const node9Dir = path31.join(os24.homedir(), ".node9");
11836
- if (fs28.existsSync(node9Dir)) {
12365
+ const node9Dir = path33.join(os26.homedir(), ".node9");
12366
+ if (fs30.existsSync(node9Dir)) {
11837
12367
  const confirmed = await confirm2({
11838
12368
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
11839
12369
  default: false
11840
12370
  });
11841
12371
  if (confirmed) {
11842
- fs28.rmSync(node9Dir, { recursive: true });
11843
- if (fs28.existsSync(node9Dir)) {
12372
+ fs30.rmSync(node9Dir, { recursive: true });
12373
+ if (fs30.existsSync(node9Dir)) {
11844
12374
  console.error(
11845
- 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.")
11846
12376
  );
11847
12377
  } else {
11848
- 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)"));
11849
12379
  }
11850
12380
  } else {
11851
- console.log(chalk19.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12381
+ console.log(chalk20.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
11852
12382
  }
11853
12383
  } else {
11854
- 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"));
11855
12385
  }
11856
12386
  } else {
11857
12387
  console.log(
11858
- 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")
11859
12389
  );
11860
12390
  }
11861
12391
  if (teardownFailed) {
11862
- 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."));
11863
12393
  process.exit(1);
11864
12394
  }
11865
- console.log(chalk19.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
11866
- 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"));
11867
12397
  });
11868
12398
  registerDoctorCommand(program, version);
11869
12399
  program.command("explain").description(
@@ -11876,7 +12406,7 @@ program.command("explain").description(
11876
12406
  try {
11877
12407
  args = JSON.parse(trimmed);
11878
12408
  } catch {
11879
- console.error(chalk19.red(`
12409
+ console.error(chalk20.red(`
11880
12410
  \u274C Invalid JSON: ${trimmed}
11881
12411
  `));
11882
12412
  process.exit(1);
@@ -11887,60 +12417,61 @@ program.command("explain").description(
11887
12417
  }
11888
12418
  const result = await explainPolicy(tool, args);
11889
12419
  console.log("");
11890
- console.log(chalk19.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12420
+ console.log(chalk20.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
11891
12421
  console.log("");
11892
- console.log(` ${chalk19.bold("Tool:")} ${chalk19.white(result.tool)}`);
12422
+ console.log(` ${chalk20.bold("Tool:")} ${chalk20.white(result.tool)}`);
11893
12423
  if (argsRaw) {
11894
12424
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
11895
- console.log(` ${chalk19.bold("Input:")} ${chalk19.gray(preview)}`);
12425
+ console.log(` ${chalk20.bold("Input:")} ${chalk20.gray(preview)}`);
11896
12426
  }
11897
12427
  console.log("");
11898
- console.log(chalk19.bold("Config Sources (Waterfall):"));
12428
+ console.log(chalk20.bold("Config Sources (Waterfall):"));
11899
12429
  for (const tier of result.waterfall) {
11900
- const num = chalk19.gray(` ${tier.tier}.`);
12430
+ const num2 = chalk20.gray(` ${tier.tier}.`);
11901
12431
  const label = tier.label.padEnd(16);
11902
12432
  let statusStr;
11903
12433
  if (tier.tier === 1) {
11904
- statusStr = chalk19.gray(tier.note ?? "");
12434
+ statusStr = chalk20.gray(tier.note ?? "");
11905
12435
  } else if (tier.status === "active") {
11906
- const loc = tier.path ? chalk19.gray(tier.path) : "";
11907
- const note = tier.note ? chalk19.gray(`(${tier.note})`) : "";
11908
- 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 : "");
11909
12439
  } else {
11910
- statusStr = chalk19.gray("\u25CB " + (tier.note ?? "not found"));
12440
+ statusStr = chalk20.gray("\u25CB " + (tier.note ?? "not found"));
11911
12441
  }
11912
- console.log(`${num} ${chalk19.white(label)} ${statusStr}`);
12442
+ console.log(`${num2} ${chalk20.white(label)} ${statusStr}`);
11913
12443
  }
11914
12444
  console.log("");
11915
- console.log(chalk19.bold("Policy Evaluation:"));
12445
+ console.log(chalk20.bold("Policy Evaluation:"));
11916
12446
  for (const step of result.steps) {
11917
12447
  const isFinal = step.isFinal;
11918
12448
  let icon;
11919
- if (step.outcome === "allow") icon = chalk19.green(" \u2705");
11920
- else if (step.outcome === "review") icon = chalk19.red(" \u{1F534}");
11921
- else if (step.outcome === "skip") icon = chalk19.gray(" \u2500 ");
11922
- 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 ");
11923
12453
  const name = step.name.padEnd(18);
11924
- const nameStr = isFinal ? chalk19.white.bold(name) : chalk19.white(name);
11925
- const detail = isFinal ? chalk19.white(step.detail) : chalk19.gray(step.detail);
11926
- 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") : "";
11927
12457
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
11928
12458
  }
11929
12459
  console.log("");
11930
12460
  if (result.decision === "allow") {
11931
- 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"));
11932
12462
  } else {
11933
12463
  console.log(
11934
- 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")
11935
12465
  );
11936
12466
  if (result.blockedByLabel) {
11937
- console.log(chalk19.gray(` Reason: ${result.blockedByLabel}`));
12467
+ console.log(chalk20.gray(` Reason: ${result.blockedByLabel}`));
11938
12468
  }
11939
12469
  }
11940
12470
  console.log("");
11941
12471
  });
11942
12472
  registerInitCommand(program);
11943
12473
  registerAuditCommand(program);
12474
+ registerReportCommand(program);
11944
12475
  registerStatusCommand(program);
11945
12476
  registerDaemonCommand(program);
11946
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) => {
@@ -11948,7 +12479,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
11948
12479
  try {
11949
12480
  await startTail2(options);
11950
12481
  } catch (err2) {
11951
- 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)}`));
11952
12483
  process.exit(1);
11953
12484
  }
11954
12485
  });
@@ -11980,14 +12511,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
11980
12511
  Run "node9 addto claude" to register it as the statusLine.`
11981
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) => {
11982
12513
  if (subcommand === "debug") {
11983
- const flagFile = path31.join(os24.homedir(), ".node9", "hud-debug");
12514
+ const flagFile = path33.join(os26.homedir(), ".node9", "hud-debug");
11984
12515
  if (state === "on") {
11985
- fs28.mkdirSync(path31.dirname(flagFile), { recursive: true });
11986
- fs28.writeFileSync(flagFile, "");
12516
+ fs30.mkdirSync(path33.dirname(flagFile), { recursive: true });
12517
+ fs30.writeFileSync(flagFile, "");
11987
12518
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
11988
12519
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
11989
12520
  } else if (state === "off") {
11990
- if (fs28.existsSync(flagFile)) fs28.unlinkSync(flagFile);
12521
+ if (fs30.existsSync(flagFile)) fs30.unlinkSync(flagFile);
11991
12522
  console.log("HUD debug logging disabled.");
11992
12523
  } else {
11993
12524
  console.error("Usage: node9 hud debug on|off");
@@ -12002,7 +12533,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12002
12533
  const ms = parseDuration(options.duration);
12003
12534
  if (ms === null) {
12004
12535
  console.error(
12005
- chalk19.red(`
12536
+ chalk20.red(`
12006
12537
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12007
12538
  `)
12008
12539
  );
@@ -12010,20 +12541,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12010
12541
  }
12011
12542
  pauseNode9(ms, options.duration);
12012
12543
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12013
- console.log(chalk19.yellow(`
12544
+ console.log(chalk20.yellow(`
12014
12545
  \u23F8 Node9 paused until ${expiresAt}`));
12015
- console.log(chalk19.gray(` All tool calls will be allowed without review.`));
12016
- 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.
12017
12548
  `));
12018
12549
  });
12019
12550
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12020
12551
  const { paused } = checkPause();
12021
12552
  if (!paused) {
12022
- 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"));
12023
12554
  return;
12024
12555
  }
12025
12556
  resumeNode9();
12026
- 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"));
12027
12558
  });
12028
12559
  var HOOK_BASED_AGENTS = {
12029
12560
  claude: "claude",
@@ -12036,15 +12567,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12036
12567
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12037
12568
  const target = HOOK_BASED_AGENTS[firstArg2];
12038
12569
  console.error(
12039
- chalk19.yellow(`
12570
+ chalk20.yellow(`
12040
12571
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12041
12572
  );
12042
- console.error(chalk19.white(`
12573
+ console.error(chalk20.white(`
12043
12574
  "${target}" uses its own hook system. Use:`));
12044
12575
  console.error(
12045
- chalk19.green(` node9 addto ${target} `) + chalk19.gray("# one-time setup")
12576
+ chalk20.green(` node9 addto ${target} `) + chalk20.gray("# one-time setup")
12046
12577
  );
12047
- console.error(chalk19.green(` ${target} `) + chalk19.gray("# run normally"));
12578
+ console.error(chalk20.green(` ${target} `) + chalk20.gray("# run normally"));
12048
12579
  process.exit(1);
12049
12580
  }
12050
12581
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12061,7 +12592,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12061
12592
  }
12062
12593
  );
12063
12594
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12064
- 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..."));
12065
12596
  const daemonReady = await autoStartDaemonAndWait();
12066
12597
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12067
12598
  }
@@ -12074,12 +12605,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12074
12605
  }
12075
12606
  if (!result.approved) {
12076
12607
  console.error(
12077
- chalk19.red(`
12608
+ chalk20.red(`
12078
12609
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12079
12610
  );
12080
12611
  process.exit(1);
12081
12612
  }
12082
- console.error(chalk19.green("\n\u2705 Approved \u2014 running command...\n"));
12613
+ console.error(chalk20.green("\n\u2705 Approved \u2014 running command...\n"));
12083
12614
  await runProxy(fullCommand);
12084
12615
  } else {
12085
12616
  program.help();
@@ -12094,9 +12625,9 @@ if (process.argv[2] !== "daemon") {
12094
12625
  const isCheckHook = process.argv[2] === "check";
12095
12626
  if (isCheckHook) {
12096
12627
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12097
- const logPath = path31.join(os24.homedir(), ".node9", "hook-debug.log");
12628
+ const logPath = path33.join(os26.homedir(), ".node9", "hook-debug.log");
12098
12629
  const msg = reason instanceof Error ? reason.message : String(reason);
12099
- fs28.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12630
+ fs30.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12100
12631
  `);
12101
12632
  }
12102
12633
  process.exit(0);