@node9/proxy 1.9.3 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.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 path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
+ return ` \u2022 ${path35}: ${issue.message}`;
144
152
  });
145
153
  return {
146
154
  sanitized,
@@ -223,7 +231,8 @@ var init_config_schema = __esm({
223
231
  slackEnabled: z.boolean().optional(),
224
232
  enableTrustSessions: z.boolean().optional(),
225
233
  allowGlobalPause: z.boolean().optional(),
226
- auditHashArgs: z.boolean().optional()
234
+ auditHashArgs: z.boolean().optional(),
235
+ agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional()
227
236
  }).optional(),
228
237
  policy: z.object({
229
238
  sandboxPaths: z.array(z.string()).optional(),
@@ -239,6 +248,11 @@ var init_config_schema = __esm({
239
248
  dlp: z.object({
240
249
  enabled: z.boolean().optional(),
241
250
  scanIgnoredTools: z.boolean().optional()
251
+ }).optional(),
252
+ loopDetection: z.object({
253
+ enabled: z.boolean().optional(),
254
+ threshold: z.number().min(2).optional(),
255
+ windowSeconds: z.number().min(10).optional()
242
256
  }).optional()
243
257
  }).optional(),
244
258
  environments: z.record(z.object({ requireApproval: z.boolean().optional() })).optional()
@@ -536,7 +550,8 @@ function getConfig(cwd) {
536
550
  onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
537
551
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
538
552
  },
539
- dlp: { ...DEFAULT_CONFIG.policy.dlp }
553
+ dlp: { ...DEFAULT_CONFIG.policy.dlp },
554
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
540
555
  };
541
556
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
542
557
  const applyLayer = (source) => {
@@ -575,6 +590,13 @@ function getConfig(cwd) {
575
590
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
576
591
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
577
592
  }
593
+ if (p.loopDetection) {
594
+ const ld = p.loopDetection;
595
+ if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
596
+ if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
597
+ if (ld.windowSeconds !== void 0)
598
+ mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
599
+ }
578
600
  const envs = source.environments || {};
579
601
  for (const [envName, envConfig] of Object.entries(envs)) {
580
602
  if (envConfig && typeof envConfig === "object") {
@@ -878,7 +900,8 @@ var init_config = __esm({
878
900
  description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
879
901
  }
880
902
  ],
881
- dlp: { enabled: true, scanIgnoredTools: true }
903
+ dlp: { enabled: true, scanIgnoredTools: true },
904
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
882
905
  },
883
906
  environments: {}
884
907
  };
@@ -1687,9 +1710,9 @@ function matchesPattern(text, patterns) {
1687
1710
  const withoutDotSlash = text.replace(/^\.\//, "");
1688
1711
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1689
1712
  }
1690
- function getNestedValue(obj, path32) {
1713
+ function getNestedValue(obj, path35) {
1691
1714
  if (!obj || typeof obj !== "object") return null;
1692
- return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
1715
+ return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
1693
1716
  }
1694
1717
  function shouldSnapshot(toolName, args, config) {
1695
1718
  if (!config.settings.enableUndo) return false;
@@ -2769,11 +2792,12 @@ ${smartTruncate(str, 500)}`
2769
2792
  function escapePango(text) {
2770
2793
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2771
2794
  }
2772
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2795
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2773
2796
  const lines = [];
2774
2797
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2775
2798
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2776
2799
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2800
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2777
2801
  lines.push("");
2778
2802
  lines.push(formattedArgs);
2779
2803
  if (allowCount >= 3) {
@@ -2786,7 +2810,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2786
2810
  }
2787
2811
  return lines.join("\n");
2788
2812
  }
2789
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2813
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2790
2814
  const lines = [];
2791
2815
  if (locked) {
2792
2816
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2796,6 +2820,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2796
2820
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2797
2821
  );
2798
2822
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2823
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2799
2824
  lines.push("");
2800
2825
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2801
2826
  if (allowCount >= 3) {
@@ -2812,7 +2837,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2812
2837
  }
2813
2838
  return lines.join("\n");
2814
2839
  }
2815
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2840
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2816
2841
  if (isTestEnv()) return "deny";
2817
2842
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2818
2843
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2823,7 +2848,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2823
2848
  agent,
2824
2849
  explainableLabel,
2825
2850
  locked,
2826
- allowCount
2851
+ allowCount,
2852
+ ruleDescription
2827
2853
  );
2828
2854
  return new Promise((resolve) => {
2829
2855
  let childProcess = null;
@@ -2857,7 +2883,8 @@ end run`;
2857
2883
  agent,
2858
2884
  explainableLabel,
2859
2885
  locked,
2860
- allowCount
2886
+ allowCount,
2887
+ ruleDescription
2861
2888
  );
2862
2889
  const argsList = [
2863
2890
  locked ? "--info" : "--question",
@@ -2931,7 +2958,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2931
2958
  }).catch(() => {
2932
2959
  });
2933
2960
  }
2934
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2961
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2935
2962
  const controller = new AbortController();
2936
2963
  const timeout = setTimeout(() => controller.abort(), 1e4);
2937
2964
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -2976,7 +3003,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2976
3003
  platform: os9.platform()
2977
3004
  },
2978
3005
  ...riskMetadata && { riskMetadata },
2979
- ...ciContext && { ciContext }
3006
+ ...ciContext && { ciContext },
3007
+ ...agentPolicy && { policy: agentPolicy },
3008
+ ...forceReview && { forceReview: true }
2980
3009
  }),
2981
3010
  signal: controller.signal
2982
3011
  });
@@ -3056,6 +3085,58 @@ var init_cloud = __esm({
3056
3085
  }
3057
3086
  });
3058
3087
 
3088
+ // src/loop-detector.ts
3089
+ import fs11 from "fs";
3090
+ import path14 from "path";
3091
+ import os10 from "os";
3092
+ import crypto2 from "crypto";
3093
+ function loopStateFile() {
3094
+ return path14.join(os10.homedir(), ".node9", "loop-state.json");
3095
+ }
3096
+ function computeArgsHash(args) {
3097
+ const str = JSON.stringify(args ?? "");
3098
+ return crypto2.createHash("sha256").update(str).digest("hex").slice(0, 16);
3099
+ }
3100
+ function readState() {
3101
+ try {
3102
+ if (!fs11.existsSync(loopStateFile())) return [];
3103
+ const raw = fs11.readFileSync(loopStateFile(), "utf-8");
3104
+ const parsed = JSON.parse(raw);
3105
+ if (!Array.isArray(parsed)) return [];
3106
+ return parsed;
3107
+ } catch {
3108
+ return [];
3109
+ }
3110
+ }
3111
+ function writeState(records) {
3112
+ const dir = path14.dirname(loopStateFile());
3113
+ if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
3114
+ const tmpPath = `${loopStateFile()}.${os10.hostname()}.${process.pid}.tmp`;
3115
+ fs11.writeFileSync(tmpPath, JSON.stringify(records));
3116
+ fs11.renameSync(tmpPath, loopStateFile());
3117
+ }
3118
+ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
3119
+ try {
3120
+ const hash = computeArgsHash(args);
3121
+ const now = Date.now();
3122
+ const cutoff = now - windowMs;
3123
+ const records = readState().filter((r) => r.ts >= cutoff);
3124
+ records.push({ t: tool, h: hash, ts: now });
3125
+ const count = records.filter((r) => r.t === tool && r.h === hash).length;
3126
+ writeState(records.slice(-MAX_RECORDS));
3127
+ return { looping: count >= threshold, count };
3128
+ } catch {
3129
+ return { looping: false, count: 0 };
3130
+ }
3131
+ }
3132
+ var MAX_RECORDS;
3133
+ var init_loop_detector = __esm({
3134
+ "src/loop-detector.ts"() {
3135
+ "use strict";
3136
+ MAX_RECORDS = 500;
3137
+ }
3138
+ });
3139
+
3059
3140
  // src/auth/orchestrator.ts
3060
3141
  import { randomUUID } from "crypto";
3061
3142
  function isWriteTool(toolName) {
@@ -3144,6 +3225,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3144
3225
  let explainableLabel = "Local Config";
3145
3226
  let policyMatchedField;
3146
3227
  let policyMatchedWord;
3228
+ let policyRuleDescription;
3147
3229
  let riskMetadata;
3148
3230
  let statefulRecoveryCommand;
3149
3231
  let localSmartRuleMatched = false;
@@ -3237,6 +3319,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3237
3319
  return { approved: true, checkedBy: "audit" };
3238
3320
  }
3239
3321
  if (!taintWarning && !isIgnoredTool(toolName)) {
3322
+ const ld = config.policy.loopDetection;
3323
+ if (ld.enabled) {
3324
+ const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
3325
+ if (loopResult.looping) {
3326
+ 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?`;
3327
+ if (!isManual)
3328
+ appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
3329
+ return {
3330
+ approved: false,
3331
+ reason,
3332
+ blockedBy: "loop-detection",
3333
+ blockedByLabel: "\u{1F504} Loop Detected"
3334
+ };
3335
+ }
3336
+ }
3240
3337
  if (getActiveTrustSession(toolName)) {
3241
3338
  if (approvers.cloud && creds?.apiKey)
3242
3339
  await auditLocalAllow(toolName, args, "trust", creds, meta);
@@ -3292,6 +3389,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3292
3389
  policyMatchedField = policyResult.matchedField;
3293
3390
  policyMatchedWord = policyResult.matchedWord;
3294
3391
  if (policyResult.ruleName) localSmartRuleMatched = true;
3392
+ if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
3393
+ else if (policyResult.reason) policyRuleDescription = policyResult.reason;
3295
3394
  riskMetadata = computeRiskMetadata(
3296
3395
  args,
3297
3396
  policyResult.tier ?? 6,
@@ -3300,6 +3399,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3300
3399
  policyMatchedWord,
3301
3400
  policyResult.ruleName
3302
3401
  );
3402
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
3303
3403
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
3304
3404
  if (persistent === "allow") {
3305
3405
  if (approvers.cloud && creds?.apiKey)
@@ -3334,9 +3434,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3334
3434
  }
3335
3435
  let cloudRequestId = null;
3336
3436
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
3337
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3437
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
3438
+ if (cloudEnforced) {
3338
3439
  try {
3339
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
3440
+ const initResult = await initNode9SaaS(
3441
+ toolName,
3442
+ args,
3443
+ creds,
3444
+ meta,
3445
+ riskMetadata,
3446
+ config.settings.agentPolicy,
3447
+ forceReview
3448
+ );
3340
3449
  if (!initResult.pending) {
3341
3450
  if (initResult.shadowMode) {
3342
3451
  return { approved: true, checkedBy: "cloud" };
@@ -3351,9 +3460,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3351
3460
  };
3352
3461
  }
3353
3462
  }
3354
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
3355
- cloudRequestId = initResult.requestId || null;
3356
- }
3463
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
3357
3464
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
3358
3465
  } catch {
3359
3466
  }
@@ -3410,7 +3517,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3410
3517
  }
3411
3518
  }
3412
3519
  }
3413
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3520
+ if (cloudEnforced && cloudRequestId) {
3414
3521
  racePromises.push(
3415
3522
  (async () => {
3416
3523
  try {
@@ -3442,7 +3549,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3442
3549
  signal,
3443
3550
  policyMatchedField,
3444
3551
  policyMatchedWord,
3445
- daemonAllowCount
3552
+ daemonAllowCount,
3553
+ riskMetadata?.ruleDescription
3446
3554
  );
3447
3555
  if (decision === "always_allow") {
3448
3556
  writeTrustSession(toolName, 36e5);
@@ -3555,7 +3663,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
3555
3663
  hashAuditArgs
3556
3664
  );
3557
3665
  }
3558
- return finalResult;
3666
+ const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
3667
+ return enrichedResult;
3559
3668
  }
3560
3669
  var WRITE_TOOLS;
3561
3670
  var init_orchestrator = __esm({
@@ -3570,6 +3679,7 @@ var init_orchestrator = __esm({
3570
3679
  init_state();
3571
3680
  init_daemon();
3572
3681
  init_cloud();
3682
+ init_loop_detector();
3573
3683
  WRITE_TOOLS = /* @__PURE__ */ new Set([
3574
3684
  "write",
3575
3685
  "write_file",
@@ -5307,8 +5417,8 @@ var init_suggestion_tracker = __esm({
5307
5417
  });
5308
5418
 
5309
5419
  // src/daemon/taint-store.ts
5310
- import fs12 from "fs";
5311
- import path15 from "path";
5420
+ import fs13 from "fs";
5421
+ import path16 from "path";
5312
5422
  var DEFAULT_TTL_MS, TaintStore;
5313
5423
  var init_taint_store = __esm({
5314
5424
  "src/daemon/taint-store.ts"() {
@@ -5377,9 +5487,9 @@ var init_taint_store = __esm({
5377
5487
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5378
5488
  _resolve(filePath) {
5379
5489
  try {
5380
- return fs12.realpathSync.native(path15.resolve(filePath));
5490
+ return fs13.realpathSync.native(path16.resolve(filePath));
5381
5491
  } catch {
5382
- return path15.resolve(filePath);
5492
+ return path16.resolve(filePath);
5383
5493
  }
5384
5494
  }
5385
5495
  };
@@ -5496,15 +5606,15 @@ var init_session_history = __esm({
5496
5606
 
5497
5607
  // src/daemon/state.ts
5498
5608
  import net2 from "net";
5499
- import fs13 from "fs";
5500
- import path16 from "path";
5501
- import os11 from "os";
5609
+ import fs14 from "fs";
5610
+ import path17 from "path";
5611
+ import os12 from "os";
5502
5612
  import { spawn as spawn2 } from "child_process";
5503
5613
  import { randomUUID as randomUUID3 } from "crypto";
5504
5614
  function loadInsightCounts() {
5505
5615
  try {
5506
- if (!fs13.existsSync(INSIGHT_COUNTS_FILE)) return;
5507
- const data = JSON.parse(fs13.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5616
+ if (!fs14.existsSync(INSIGHT_COUNTS_FILE)) return;
5617
+ const data = JSON.parse(fs14.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5508
5618
  for (const [tool, count] of Object.entries(data)) {
5509
5619
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
5510
5620
  }
@@ -5543,23 +5653,23 @@ function markRejectionHandlerRegistered() {
5543
5653
  daemonRejectionHandlerRegistered = true;
5544
5654
  }
5545
5655
  function atomicWriteSync2(filePath, data, options) {
5546
- const dir = path16.dirname(filePath);
5547
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5656
+ const dir = path17.dirname(filePath);
5657
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
5548
5658
  const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
5549
5659
  try {
5550
- fs13.writeFileSync(tmpPath, data, options);
5660
+ fs14.writeFileSync(tmpPath, data, options);
5551
5661
  } catch (err2) {
5552
5662
  try {
5553
- fs13.unlinkSync(tmpPath);
5663
+ fs14.unlinkSync(tmpPath);
5554
5664
  } catch {
5555
5665
  }
5556
5666
  throw err2;
5557
5667
  }
5558
5668
  try {
5559
- fs13.renameSync(tmpPath, filePath);
5669
+ fs14.renameSync(tmpPath, filePath);
5560
5670
  } catch (err2) {
5561
5671
  try {
5562
- fs13.unlinkSync(tmpPath);
5672
+ fs14.unlinkSync(tmpPath);
5563
5673
  } catch {
5564
5674
  }
5565
5675
  throw err2;
@@ -5583,16 +5693,16 @@ function appendAuditLog(data) {
5583
5693
  decision: data.decision,
5584
5694
  source: "daemon"
5585
5695
  };
5586
- const dir = path16.dirname(AUDIT_LOG_FILE);
5587
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
5588
- fs13.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5696
+ const dir = path17.dirname(AUDIT_LOG_FILE);
5697
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
5698
+ fs14.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5589
5699
  } catch {
5590
5700
  }
5591
5701
  }
5592
5702
  function getAuditHistory(limit = 20) {
5593
5703
  try {
5594
- if (!fs13.existsSync(AUDIT_LOG_FILE)) return [];
5595
- const lines = fs13.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5704
+ if (!fs14.existsSync(AUDIT_LOG_FILE)) return [];
5705
+ const lines = fs14.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5596
5706
  if (lines.length === 1 && lines[0] === "") return [];
5597
5707
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
5598
5708
  } catch {
@@ -5601,19 +5711,19 @@ function getAuditHistory(limit = 20) {
5601
5711
  }
5602
5712
  function getOrgName() {
5603
5713
  try {
5604
- if (fs13.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5714
+ if (fs14.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5605
5715
  } catch {
5606
5716
  }
5607
5717
  return null;
5608
5718
  }
5609
5719
  function hasStoredSlackKey() {
5610
- return fs13.existsSync(CREDENTIALS_FILE);
5720
+ return fs14.existsSync(CREDENTIALS_FILE);
5611
5721
  }
5612
5722
  function writeGlobalSetting(key, value) {
5613
5723
  let config = {};
5614
5724
  try {
5615
- if (fs13.existsSync(GLOBAL_CONFIG_FILE)) {
5616
- config = JSON.parse(fs13.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5725
+ if (fs14.existsSync(GLOBAL_CONFIG_FILE)) {
5726
+ config = JSON.parse(fs14.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5617
5727
  }
5618
5728
  } catch {
5619
5729
  }
@@ -5625,8 +5735,8 @@ function writeTrustEntry(toolName, durationMs) {
5625
5735
  try {
5626
5736
  let trust = { entries: [] };
5627
5737
  try {
5628
- if (fs13.existsSync(TRUST_FILE2))
5629
- trust = JSON.parse(fs13.readFileSync(TRUST_FILE2, "utf-8"));
5738
+ if (fs14.existsSync(TRUST_FILE2))
5739
+ trust = JSON.parse(fs14.readFileSync(TRUST_FILE2, "utf-8"));
5630
5740
  } catch {
5631
5741
  }
5632
5742
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -5637,8 +5747,8 @@ function writeTrustEntry(toolName, durationMs) {
5637
5747
  }
5638
5748
  function readPersistentDecisions() {
5639
5749
  try {
5640
- if (fs13.existsSync(DECISIONS_FILE)) {
5641
- return JSON.parse(fs13.readFileSync(DECISIONS_FILE, "utf-8"));
5750
+ if (fs14.existsSync(DECISIONS_FILE)) {
5751
+ return JSON.parse(fs14.readFileSync(DECISIONS_FILE, "utf-8"));
5642
5752
  }
5643
5753
  } catch {
5644
5754
  }
@@ -5675,7 +5785,7 @@ function estimateToolCost(tool, args) {
5675
5785
  const filePath = a.file_path ?? a.path;
5676
5786
  if (filePath) {
5677
5787
  try {
5678
- const bytes = fs13.statSync(filePath).size;
5788
+ const bytes = fs14.statSync(filePath).size;
5679
5789
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
5680
5790
  } catch {
5681
5791
  }
@@ -5733,7 +5843,7 @@ function abandonPending() {
5733
5843
  });
5734
5844
  if (autoStarted) {
5735
5845
  try {
5736
- fs13.unlinkSync(DAEMON_PID_FILE);
5846
+ fs14.unlinkSync(DAEMON_PID_FILE);
5737
5847
  } catch {
5738
5848
  }
5739
5849
  setTimeout(() => {
@@ -5744,7 +5854,7 @@ function abandonPending() {
5744
5854
  }
5745
5855
  function startActivitySocket() {
5746
5856
  try {
5747
- fs13.unlinkSync(ACTIVITY_SOCKET_PATH2);
5857
+ fs14.unlinkSync(ACTIVITY_SOCKET_PATH2);
5748
5858
  } catch {
5749
5859
  }
5750
5860
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -5825,7 +5935,7 @@ function startActivitySocket() {
5825
5935
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
5826
5936
  process.on("exit", () => {
5827
5937
  try {
5828
- fs13.unlinkSync(ACTIVITY_SOCKET_PATH2);
5938
+ fs14.unlinkSync(ACTIVITY_SOCKET_PATH2);
5829
5939
  } catch {
5830
5940
  }
5831
5941
  });
@@ -5839,14 +5949,14 @@ var init_state2 = __esm({
5839
5949
  init_taint_store();
5840
5950
  init_session_counters();
5841
5951
  init_session_history();
5842
- homeDir = os11.homedir();
5843
- DAEMON_PID_FILE = path16.join(homeDir, ".node9", "daemon.pid");
5844
- DECISIONS_FILE = path16.join(homeDir, ".node9", "decisions.json");
5845
- AUDIT_LOG_FILE = path16.join(homeDir, ".node9", "audit.log");
5846
- TRUST_FILE2 = path16.join(homeDir, ".node9", "trust.json");
5847
- GLOBAL_CONFIG_FILE = path16.join(homeDir, ".node9", "config.json");
5848
- CREDENTIALS_FILE = path16.join(homeDir, ".node9", "credentials.json");
5849
- INSIGHT_COUNTS_FILE = path16.join(homeDir, ".node9", "insight-counts.json");
5952
+ homeDir = os12.homedir();
5953
+ DAEMON_PID_FILE = path17.join(homeDir, ".node9", "daemon.pid");
5954
+ DECISIONS_FILE = path17.join(homeDir, ".node9", "decisions.json");
5955
+ AUDIT_LOG_FILE = path17.join(homeDir, ".node9", "audit.log");
5956
+ TRUST_FILE2 = path17.join(homeDir, ".node9", "trust.json");
5957
+ GLOBAL_CONFIG_FILE = path17.join(homeDir, ".node9", "config.json");
5958
+ CREDENTIALS_FILE = path17.join(homeDir, ".node9", "credentials.json");
5959
+ INSIGHT_COUNTS_FILE = path17.join(homeDir, ".node9", "insight-counts.json");
5850
5960
  pending = /* @__PURE__ */ new Map();
5851
5961
  sseClients = /* @__PURE__ */ new Set();
5852
5962
  suggestionTracker = new SuggestionTracker(3);
@@ -5864,7 +5974,7 @@ var init_state2 = __esm({
5864
5974
  "2h": 2 * 60 * 6e4
5865
5975
  };
5866
5976
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5867
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path16.join(os11.tmpdir(), "node9-activity.sock");
5977
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path17.join(os12.tmpdir(), "node9-activity.sock");
5868
5978
  ACTIVITY_RING_SIZE = 100;
5869
5979
  activityRing = [];
5870
5980
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5886,14 +5996,14 @@ var init_state2 = __esm({
5886
5996
  });
5887
5997
 
5888
5998
  // src/config/patch.ts
5889
- import fs14 from "fs";
5890
- import path17 from "path";
5891
- import os12 from "os";
5999
+ import fs15 from "fs";
6000
+ import path18 from "path";
6001
+ import os13 from "os";
5892
6002
  function patchConfig(configPath, patch) {
5893
6003
  let config = {};
5894
6004
  try {
5895
- if (fs14.existsSync(configPath)) {
5896
- config = JSON.parse(fs14.readFileSync(configPath, "utf8"));
6005
+ if (fs15.existsSync(configPath)) {
6006
+ config = JSON.parse(fs15.readFileSync(configPath, "utf8"));
5897
6007
  }
5898
6008
  } catch {
5899
6009
  throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
@@ -5912,23 +6022,23 @@ function patchConfig(configPath, patch) {
5912
6022
  ignored.push(patch.toolName);
5913
6023
  }
5914
6024
  }
5915
- const dir = path17.dirname(configPath);
5916
- fs14.mkdirSync(dir, { recursive: true });
6025
+ const dir = path18.dirname(configPath);
6026
+ fs15.mkdirSync(dir, { recursive: true });
5917
6027
  const tmp = configPath + ".node9-tmp";
5918
6028
  try {
5919
- fs14.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
6029
+ fs15.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
5920
6030
  } catch (err2) {
5921
6031
  try {
5922
- fs14.unlinkSync(tmp);
6032
+ fs15.unlinkSync(tmp);
5923
6033
  } catch {
5924
6034
  }
5925
6035
  throw err2;
5926
6036
  }
5927
6037
  try {
5928
- fs14.renameSync(tmp, configPath);
6038
+ fs15.renameSync(tmp, configPath);
5929
6039
  } catch (err2) {
5930
6040
  try {
5931
- fs14.unlinkSync(tmp);
6041
+ fs15.unlinkSync(tmp);
5932
6042
  } catch {
5933
6043
  }
5934
6044
  throw err2;
@@ -5938,18 +6048,185 @@ var GLOBAL_CONFIG_PATH;
5938
6048
  var init_patch = __esm({
5939
6049
  "src/config/patch.ts"() {
5940
6050
  "use strict";
5941
- GLOBAL_CONFIG_PATH = path17.join(os12.homedir(), ".node9", "config.json");
6051
+ GLOBAL_CONFIG_PATH = path18.join(os13.homedir(), ".node9", "config.json");
6052
+ }
6053
+ });
6054
+
6055
+ // src/costSync.ts
6056
+ import fs16 from "fs";
6057
+ import path19 from "path";
6058
+ import os14 from "os";
6059
+ function normalizeModel(raw) {
6060
+ return raw.replace(/-\d{8}$/, "");
6061
+ }
6062
+ function pricingFor(model) {
6063
+ const norm = normalizeModel(model);
6064
+ if (PRICING[norm]) return PRICING[norm];
6065
+ let best = null;
6066
+ for (const key of Object.keys(PRICING)) {
6067
+ if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
6068
+ }
6069
+ return best ? PRICING[best] : null;
6070
+ }
6071
+ function parseJSONLFile(filePath) {
6072
+ let content;
6073
+ try {
6074
+ content = fs16.readFileSync(filePath, "utf8");
6075
+ } catch {
6076
+ return /* @__PURE__ */ new Map();
6077
+ }
6078
+ const daily = /* @__PURE__ */ new Map();
6079
+ for (const line of content.split("\n")) {
6080
+ if (!line.trim()) continue;
6081
+ let row;
6082
+ try {
6083
+ row = JSON.parse(line);
6084
+ } catch {
6085
+ continue;
6086
+ }
6087
+ if (row["type"] !== "assistant") continue;
6088
+ const msg = row["message"];
6089
+ if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
6090
+ const usage = msg["usage"];
6091
+ const model = msg["model"];
6092
+ const timestamp = row["timestamp"];
6093
+ if (typeof timestamp !== "string" || timestamp.length < 10) continue;
6094
+ const date = timestamp.slice(0, 10);
6095
+ const p = pricingFor(model);
6096
+ if (!p) continue;
6097
+ const inp = Number(usage["input_tokens"] ?? 0);
6098
+ const out = Number(usage["output_tokens"] ?? 0);
6099
+ const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
6100
+ const cr = Number(usage["cache_read_input_tokens"] ?? 0);
6101
+ const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
6102
+ const norm = normalizeModel(model);
6103
+ const key = `${date}::${norm}`;
6104
+ const prev = daily.get(key);
6105
+ if (prev) {
6106
+ prev.costUSD += cost;
6107
+ prev.inputTokens += inp;
6108
+ prev.outputTokens += out;
6109
+ prev.cacheWriteTokens += cw;
6110
+ prev.cacheReadTokens += cr;
6111
+ } else {
6112
+ daily.set(key, {
6113
+ date,
6114
+ model: norm,
6115
+ costUSD: cost,
6116
+ inputTokens: inp,
6117
+ outputTokens: out,
6118
+ cacheWriteTokens: cw,
6119
+ cacheReadTokens: cr
6120
+ });
6121
+ }
6122
+ }
6123
+ return daily;
6124
+ }
6125
+ function collectEntries() {
6126
+ const projectsDir = path19.join(os14.homedir(), ".claude", "projects");
6127
+ if (!fs16.existsSync(projectsDir)) return [];
6128
+ const combined = /* @__PURE__ */ new Map();
6129
+ let dirs;
6130
+ try {
6131
+ dirs = fs16.readdirSync(projectsDir);
6132
+ } catch {
6133
+ return [];
6134
+ }
6135
+ for (const dir of dirs) {
6136
+ const dirPath = path19.join(projectsDir, dir);
6137
+ try {
6138
+ if (!fs16.statSync(dirPath).isDirectory()) continue;
6139
+ } catch {
6140
+ continue;
6141
+ }
6142
+ let files;
6143
+ try {
6144
+ files = fs16.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
6145
+ } catch {
6146
+ continue;
6147
+ }
6148
+ for (const file of files) {
6149
+ const entries = parseJSONLFile(path19.join(dirPath, file));
6150
+ for (const [key, e] of entries) {
6151
+ const prev = combined.get(key);
6152
+ if (prev) {
6153
+ prev.costUSD += e.costUSD;
6154
+ prev.inputTokens += e.inputTokens;
6155
+ prev.outputTokens += e.outputTokens;
6156
+ prev.cacheWriteTokens += e.cacheWriteTokens;
6157
+ prev.cacheReadTokens += e.cacheReadTokens;
6158
+ } else {
6159
+ combined.set(key, { ...e });
6160
+ }
6161
+ }
6162
+ }
6163
+ }
6164
+ return [...combined.values()];
6165
+ }
6166
+ async function syncCost() {
6167
+ const creds = getCredentials();
6168
+ if (!creds?.apiKey || !creds?.apiUrl) return;
6169
+ const entries = collectEntries();
6170
+ if (entries.length === 0) return;
6171
+ let username = "unknown";
6172
+ try {
6173
+ username = os14.userInfo().username;
6174
+ } catch {
6175
+ }
6176
+ const machineId = `${os14.hostname()}:${username}`;
6177
+ try {
6178
+ const res = await fetch(`${creds.apiUrl}/cost-sync`, {
6179
+ method: "POST",
6180
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
6181
+ body: JSON.stringify({ machineId, entries }),
6182
+ signal: AbortSignal.timeout(15e3)
6183
+ });
6184
+ if (!res.ok) {
6185
+ fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
6186
+ `);
6187
+ }
6188
+ } catch (err2) {
6189
+ fs16.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
6190
+ `);
6191
+ }
6192
+ }
6193
+ function startCostSync() {
6194
+ syncCost().catch(() => {
6195
+ });
6196
+ const timer = setInterval(() => {
6197
+ syncCost().catch(() => {
6198
+ });
6199
+ }, SYNC_INTERVAL_MS);
6200
+ timer.unref();
6201
+ }
6202
+ var SYNC_INTERVAL_MS, PRICING;
6203
+ var init_costSync = __esm({
6204
+ "src/costSync.ts"() {
6205
+ "use strict";
6206
+ init_config();
6207
+ init_audit();
6208
+ SYNC_INTERVAL_MS = 10 * 60 * 1e3;
6209
+ PRICING = {
6210
+ "claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
6211
+ "claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
6212
+ "claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
6213
+ "claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6214
+ "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6215
+ "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
6216
+ "claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
6217
+ };
5942
6218
  }
5943
6219
  });
5944
6220
 
5945
6221
  // src/daemon/server.ts
5946
6222
  import http from "http";
5947
- import fs15 from "fs";
5948
- import path18 from "path";
6223
+ import fs17 from "fs";
6224
+ import path20 from "path";
5949
6225
  import { randomUUID as randomUUID4 } from "crypto";
5950
6226
  import { spawnSync as spawnSync2 } from "child_process";
5951
6227
  import chalk2 from "chalk";
5952
6228
  function startDaemon() {
6229
+ startCostSync();
5953
6230
  loadInsightCounts();
5954
6231
  const csrfToken = randomUUID4();
5955
6232
  const internalToken = randomUUID4();
@@ -5965,7 +6242,7 @@ function startDaemon() {
5965
6242
  idleTimer = setTimeout(() => {
5966
6243
  if (autoStarted) {
5967
6244
  try {
5968
- fs15.unlinkSync(DAEMON_PID_FILE);
6245
+ fs17.unlinkSync(DAEMON_PID_FILE);
5969
6246
  } catch {
5970
6247
  }
5971
6248
  }
@@ -6128,7 +6405,7 @@ data: ${JSON.stringify(item.data)}
6128
6405
  status: "pending"
6129
6406
  });
6130
6407
  }
6131
- const projectCwd = typeof cwd === "string" && path18.isAbsolute(cwd) ? cwd : void 0;
6408
+ const projectCwd = typeof cwd === "string" && path20.isAbsolute(cwd) ? cwd : void 0;
6132
6409
  const projectConfig = getConfig(projectCwd);
6133
6410
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6134
6411
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6518,8 +6795,8 @@ data: ${JSON.stringify(item.data)}
6518
6795
  const body = await readBody(req);
6519
6796
  const data = body ? JSON.parse(body) : {};
6520
6797
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6521
- const node9Dir = path18.dirname(GLOBAL_CONFIG_PATH);
6522
- if (!path18.resolve(configPath).startsWith(node9Dir + path18.sep)) {
6798
+ const node9Dir = path20.dirname(GLOBAL_CONFIG_PATH);
6799
+ if (!path20.resolve(configPath).startsWith(node9Dir + path20.sep)) {
6523
6800
  res.writeHead(400, { "Content-Type": "application/json" });
6524
6801
  return res.end(
6525
6802
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6630,14 +6907,14 @@ data: ${JSON.stringify(item.data)}
6630
6907
  server.on("error", (e) => {
6631
6908
  if (e.code === "EADDRINUSE") {
6632
6909
  try {
6633
- if (fs15.existsSync(DAEMON_PID_FILE)) {
6634
- const { pid } = JSON.parse(fs15.readFileSync(DAEMON_PID_FILE, "utf-8"));
6910
+ if (fs17.existsSync(DAEMON_PID_FILE)) {
6911
+ const { pid } = JSON.parse(fs17.readFileSync(DAEMON_PID_FILE, "utf-8"));
6635
6912
  process.kill(pid, 0);
6636
6913
  return process.exit(0);
6637
6914
  }
6638
6915
  } catch {
6639
6916
  try {
6640
- fs15.unlinkSync(DAEMON_PID_FILE);
6917
+ fs17.unlinkSync(DAEMON_PID_FILE);
6641
6918
  } catch {
6642
6919
  }
6643
6920
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6705,32 +6982,33 @@ var init_server = __esm({
6705
6982
  init_state2();
6706
6983
  init_patch();
6707
6984
  init_config_schema();
6985
+ init_costSync();
6708
6986
  }
6709
6987
  });
6710
6988
 
6711
6989
  // src/daemon/index.ts
6712
- import fs16 from "fs";
6990
+ import fs18 from "fs";
6713
6991
  import chalk3 from "chalk";
6714
6992
  import { spawnSync as spawnSync3 } from "child_process";
6715
6993
  function stopDaemon() {
6716
- if (!fs16.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6994
+ if (!fs18.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
6717
6995
  try {
6718
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
6996
+ const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
6719
6997
  process.kill(pid, "SIGTERM");
6720
6998
  console.log(chalk3.green("\u2705 Stopped."));
6721
6999
  } catch {
6722
7000
  console.log(chalk3.gray("Cleaned up stale PID file."));
6723
7001
  } finally {
6724
7002
  try {
6725
- fs16.unlinkSync(DAEMON_PID_FILE);
7003
+ fs18.unlinkSync(DAEMON_PID_FILE);
6726
7004
  } catch {
6727
7005
  }
6728
7006
  }
6729
7007
  }
6730
7008
  function daemonStatus() {
6731
- if (fs16.existsSync(DAEMON_PID_FILE)) {
7009
+ if (fs18.existsSync(DAEMON_PID_FILE)) {
6732
7010
  try {
6733
- const { pid } = JSON.parse(fs16.readFileSync(DAEMON_PID_FILE, "utf-8"));
7011
+ const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
6734
7012
  process.kill(pid, 0);
6735
7013
  console.log(chalk3.green("Node9 daemon: running"));
6736
7014
  return;
@@ -6764,10 +7042,10 @@ __export(tail_exports, {
6764
7042
  startTail: () => startTail
6765
7043
  });
6766
7044
  import http2 from "http";
6767
- import chalk18 from "chalk";
6768
- import fs26 from "fs";
6769
- import os22 from "os";
6770
- import path29 from "path";
7045
+ import chalk19 from "chalk";
7046
+ import fs29 from "fs";
7047
+ import os25 from "os";
7048
+ import path32 from "path";
6771
7049
  import readline5 from "readline";
6772
7050
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
6773
7051
  function getIcon(tool) {
@@ -6777,44 +7055,64 @@ function getIcon(tool) {
6777
7055
  }
6778
7056
  return "\u{1F6E0}\uFE0F";
6779
7057
  }
7058
+ function visibleLength(s) {
7059
+ return s.replace(/\x1B\[[0-9;]*m/g, "").length;
7060
+ }
7061
+ function wrappedLineCount(text) {
7062
+ const cols = process.stdout.columns;
7063
+ if (!cols) return 1;
7064
+ const len = visibleLength(text);
7065
+ return Math.max(1, Math.ceil(len / cols));
7066
+ }
6780
7067
  function formatBase(activity) {
6781
7068
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6782
7069
  const icon = getIcon(activity.tool);
6783
7070
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6784
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os22.homedir(), "~");
7071
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os25.homedir(), "~");
6785
7072
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6786
- return `${chalk18.gray(time)} ${icon} ${chalk18.white.bold(toolName)} ${chalk18.dim(argsPreview)}`;
7073
+ return `${chalk19.gray(time)} ${icon} ${chalk19.white.bold(toolName)} ${chalk19.dim(argsPreview)}`;
6787
7074
  }
6788
7075
  function renderResult(activity, result) {
6789
7076
  const base = formatBase(activity);
6790
7077
  let status;
6791
7078
  if (result.status === "allow") {
6792
- status = chalk18.green("\u2713 ALLOW");
7079
+ status = chalk19.green("\u2713 ALLOW");
6793
7080
  } else if (result.status === "dlp") {
6794
- status = chalk18.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7081
+ status = chalk19.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6795
7082
  } else {
6796
- status = chalk18.red("\u2717 BLOCK");
7083
+ status = chalk19.red("\u2717 BLOCK");
6797
7084
  }
6798
7085
  const cost = result.costEstimate ?? activity.costEstimate;
6799
- const costSuffix = cost == null ? "" : chalk18.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7086
+ const costSuffix = cost == null ? "" : chalk19.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
6800
7087
  if (process.stdout.isTTY) {
6801
- readline5.clearLine(process.stdout, 0);
6802
- readline5.cursorTo(process.stdout, 0);
7088
+ if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7089
+ readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
7090
+ readline5.cursorTo(process.stdout, 0);
7091
+ process.stdout.write(ERASE_DOWN);
7092
+ } else {
7093
+ readline5.clearLine(process.stdout, 0);
7094
+ readline5.cursorTo(process.stdout, 0);
7095
+ }
7096
+ pendingShownForId = null;
7097
+ pendingWrappedLines = 0;
6803
7098
  }
6804
7099
  console.log(`${base} ${status}${costSuffix}`);
6805
7100
  }
6806
7101
  function renderPending(activity) {
6807
7102
  if (!process.stdout.isTTY) return;
6808
- process.stdout.write(`${formatBase(activity)} ${chalk18.yellow("\u25CF \u2026")}\r`);
7103
+ const line = `${formatBase(activity)} ${chalk19.yellow("\u25CF \u2026")}`;
7104
+ pendingShownForId = activity.id;
7105
+ pendingWrappedLines = wrappedLineCount(line);
7106
+ process.stdout.write(`${line}\r`);
6809
7107
  }
6810
7108
  async function ensureDaemon() {
6811
7109
  let pidPort = null;
6812
- if (fs26.existsSync(PID_FILE)) {
7110
+ if (fs29.existsSync(PID_FILE)) {
6813
7111
  try {
6814
- const { port } = JSON.parse(fs26.readFileSync(PID_FILE, "utf-8"));
7112
+ const { port } = JSON.parse(fs29.readFileSync(PID_FILE, "utf-8"));
6815
7113
  pidPort = port;
6816
7114
  } catch {
6817
- console.error(chalk18.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7115
+ console.error(chalk19.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6818
7116
  }
6819
7117
  }
6820
7118
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6825,7 +7123,7 @@ async function ensureDaemon() {
6825
7123
  if (res.ok) return checkPort;
6826
7124
  } catch {
6827
7125
  }
6828
- console.log(chalk18.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7126
+ console.log(chalk19.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6829
7127
  const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
6830
7128
  detached: true,
6831
7129
  stdio: "ignore",
@@ -6842,7 +7140,7 @@ async function ensureDaemon() {
6842
7140
  } catch {
6843
7141
  }
6844
7142
  }
6845
- console.error(chalk18.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7143
+ console.error(chalk19.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6846
7144
  process.exit(1);
6847
7145
  }
6848
7146
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6888,6 +7186,9 @@ function buildCardLines(req, localCount = 0) {
6888
7186
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
6889
7187
  `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
6890
7188
  ];
7189
+ if (req.riskMetadata?.ruleDescription) {
7190
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7191
+ }
6891
7192
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
6892
7193
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
6893
7194
  }
@@ -6931,9 +7232,9 @@ function buildRecoveryCardLines(req) {
6931
7232
  ];
6932
7233
  }
6933
7234
  function readApproversFromDisk() {
6934
- const configPath = path29.join(os22.homedir(), ".node9", "config.json");
7235
+ const configPath = path32.join(os25.homedir(), ".node9", "config.json");
6935
7236
  try {
6936
- const raw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
7237
+ const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
6937
7238
  const settings = raw.settings ?? {};
6938
7239
  return settings.approvers ?? {};
6939
7240
  } catch {
@@ -6944,20 +7245,20 @@ function approverStatusLine() {
6944
7245
  const a = readApproversFromDisk();
6945
7246
  const fmt = (label, key) => {
6946
7247
  const on = a[key] !== false;
6947
- return `[${key[0]}]${label.slice(1)} ${on ? chalk18.green("\u2713") : chalk18.dim("\u2717")}`;
7248
+ return `[${key[0]}]${label.slice(1)} ${on ? chalk19.green("\u2713") : chalk19.dim("\u2717")}`;
6948
7249
  };
6949
7250
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
6950
7251
  }
6951
7252
  function toggleApprover(channel) {
6952
- const configPath = path29.join(os22.homedir(), ".node9", "config.json");
7253
+ const configPath = path32.join(os25.homedir(), ".node9", "config.json");
6953
7254
  try {
6954
- const raw = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
7255
+ const raw = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
6955
7256
  const settings = raw.settings ?? {};
6956
7257
  const approvers = settings.approvers ?? {};
6957
7258
  approvers[channel] = approvers[channel] === false;
6958
7259
  settings.approvers = approvers;
6959
7260
  raw.settings = settings;
6960
- fs26.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7261
+ fs29.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
6961
7262
  } catch (err2) {
6962
7263
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
6963
7264
  `);
@@ -6989,7 +7290,7 @@ async function startTail(options = {}) {
6989
7290
  req2.end();
6990
7291
  });
6991
7292
  if (result.ok) {
6992
- console.log(chalk18.green("\u2713 Flight Recorder buffer cleared."));
7293
+ console.log(chalk19.green("\u2713 Flight Recorder buffer cleared."));
6993
7294
  } else if (result.code === "ECONNREFUSED") {
6994
7295
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
6995
7296
  } else if (result.code === "ETIMEDOUT") {
@@ -7033,7 +7334,7 @@ async function startTail(options = {}) {
7033
7334
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7034
7335
  if (channel) {
7035
7336
  toggleApprover(channel);
7036
- console.log(chalk18.dim(` Approvers: ${approverStatusLine()}`));
7337
+ console.log(chalk19.dim(` Approvers: ${approverStatusLine()}`));
7037
7338
  }
7038
7339
  };
7039
7340
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7099,7 +7400,7 @@ async function startTail(options = {}) {
7099
7400
  localAllowCounts.get(req2.toolName) ?? 0
7100
7401
  )
7101
7402
  );
7102
- const decisionStamp = action === "always-allow" ? chalk18.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk18.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk18.green("\u2713 ALLOWED") : action === "redirect" ? chalk18.yellow("\u21A9 REDIRECT AI") : chalk18.red("\u2717 DENIED");
7403
+ const decisionStamp = action === "always-allow" ? chalk19.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk19.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk19.green("\u2713 ALLOWED") : action === "redirect" ? chalk19.yellow("\u21A9 REDIRECT AI") : chalk19.red("\u2717 DENIED");
7103
7404
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7104
7405
  for (const line of stampedLines) process.stdout.write(line + "\n");
7105
7406
  process.stdout.write(SHOW_CURSOR);
@@ -7127,8 +7428,8 @@ async function startTail(options = {}) {
7127
7428
  }
7128
7429
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7129
7430
  try {
7130
- fs26.appendFileSync(
7131
- path29.join(os22.homedir(), ".node9", "hook-debug.log"),
7431
+ fs29.appendFileSync(
7432
+ path32.join(os25.homedir(), ".node9", "hook-debug.log"),
7132
7433
  `[tail] POST /decision failed: ${String(err2)}
7133
7434
  `
7134
7435
  );
@@ -7150,7 +7451,7 @@ async function startTail(options = {}) {
7150
7451
  );
7151
7452
  const stampedLines = buildCardLines(req2, priorCount);
7152
7453
  if (externalDecision) {
7153
- const source = externalDecision === "allow" ? chalk18.green("\u2713 ALLOWED") : chalk18.red("\u2717 DENIED");
7454
+ const source = externalDecision === "allow" ? chalk19.green("\u2713 ALLOWED") : chalk19.red("\u2717 DENIED");
7154
7455
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7155
7456
  }
7156
7457
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7209,16 +7510,16 @@ async function startTail(options = {}) {
7209
7510
  }
7210
7511
  } catch {
7211
7512
  }
7212
- console.log(chalk18.cyan.bold(`
7213
- \u{1F6F0}\uFE0F Node9 tail `) + chalk18.dim(`\u2192 ${dashboardUrl}`));
7513
+ console.log(chalk19.cyan.bold(`
7514
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk19.dim(`\u2192 ${dashboardUrl}`));
7214
7515
  if (canApprove) {
7215
- console.log(chalk18.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7216
- console.log(chalk18.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7516
+ console.log(chalk19.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7517
+ console.log(chalk19.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7217
7518
  }
7218
7519
  if (options.history) {
7219
- console.log(chalk18.dim("Showing history + live events.\n"));
7520
+ console.log(chalk19.dim("Showing history + live events.\n"));
7220
7521
  } else {
7221
- console.log(chalk18.dim("Showing live events only. Use --history to include past.\n"));
7522
+ console.log(chalk19.dim("Showing live events only. Use --history to include past.\n"));
7222
7523
  }
7223
7524
  process.on("SIGINT", () => {
7224
7525
  exitIdleMode();
@@ -7228,13 +7529,13 @@ async function startTail(options = {}) {
7228
7529
  readline5.clearLine(process.stdout, 0);
7229
7530
  readline5.cursorTo(process.stdout, 0);
7230
7531
  }
7231
- console.log(chalk18.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7532
+ console.log(chalk19.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7232
7533
  process.exit(0);
7233
7534
  });
7234
7535
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7235
7536
  const req = http2.get(sseUrl, (res) => {
7236
7537
  if (res.statusCode !== 200) {
7237
- console.error(chalk18.red(`Failed to connect: HTTP ${res.statusCode}`));
7538
+ console.error(chalk19.red(`Failed to connect: HTTP ${res.statusCode}`));
7238
7539
  process.exit(1);
7239
7540
  }
7240
7541
  if (canApprove) enterIdleMode();
@@ -7265,7 +7566,7 @@ async function startTail(options = {}) {
7265
7566
  readline5.clearLine(process.stdout, 0);
7266
7567
  readline5.cursorTo(process.stdout, 0);
7267
7568
  }
7268
- console.log(chalk18.red("\n\u274C Daemon disconnected."));
7569
+ console.log(chalk19.red("\n\u274C Daemon disconnected."));
7269
7570
  process.exit(1);
7270
7571
  });
7271
7572
  });
@@ -7357,9 +7658,9 @@ async function startTail(options = {}) {
7357
7658
  const hash = data.hash ?? "";
7358
7659
  const summary = data.argsSummary ?? data.tool;
7359
7660
  const fileCount = data.fileCount ?? 0;
7360
- const files = fileCount > 0 ? chalk18.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7661
+ const files = fileCount > 0 ? chalk19.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7361
7662
  process.stdout.write(
7362
- `${chalk18.dim(time)} ${chalk18.cyan("\u{1F4F8} snapshot")} ${chalk18.dim(hash)} ${summary}${files}
7663
+ `${chalk19.dim(time)} ${chalk19.cyan("\u{1F4F8} snapshot")} ${chalk19.dim(hash)} ${summary}${files}
7363
7664
  `
7364
7665
  );
7365
7666
  return;
@@ -7376,19 +7677,19 @@ async function startTail(options = {}) {
7376
7677
  }
7377
7678
  req.on("error", (err2) => {
7378
7679
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7379
- console.error(chalk18.red(`
7680
+ console.error(chalk19.red(`
7380
7681
  \u274C ${msg}`));
7381
7682
  process.exit(1);
7382
7683
  });
7383
7684
  }
7384
- var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7685
+ var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7385
7686
  var init_tail = __esm({
7386
7687
  "src/tui/tail.ts"() {
7387
7688
  "use strict";
7388
7689
  init_daemon2();
7389
7690
  init_daemon();
7390
7691
  init_core();
7391
- PID_FILE = path29.join(os22.homedir(), ".node9", "daemon.pid");
7692
+ PID_FILE = path32.join(os25.homedir(), ".node9", "daemon.pid");
7392
7693
  ICONS = {
7393
7694
  bash: "\u{1F4BB}",
7394
7695
  shell: "\u{1F4BB}",
@@ -7416,6 +7717,8 @@ var init_tail = __esm({
7416
7717
  HIDE_CURSOR = "\x1B[?25l";
7417
7718
  SHOW_CURSOR = "\x1B[?25h";
7418
7719
  ERASE_DOWN = "\x1B[J";
7720
+ pendingShownForId = null;
7721
+ pendingWrappedLines = 0;
7419
7722
  DIVIDER = "\u2500".repeat(60);
7420
7723
  }
7421
7724
  });
@@ -7427,9 +7730,9 @@ __export(hud_exports, {
7427
7730
  main: () => main,
7428
7731
  renderEnvironmentLine: () => renderEnvironmentLine
7429
7732
  });
7430
- import fs27 from "fs";
7431
- import path30 from "path";
7432
- import os23 from "os";
7733
+ import fs30 from "fs";
7734
+ import path33 from "path";
7735
+ import os26 from "os";
7433
7736
  import http3 from "http";
7434
7737
  async function readStdin() {
7435
7738
  const chunks = [];
@@ -7488,10 +7791,10 @@ function bold(s) {
7488
7791
  function color(c, s) {
7489
7792
  return `${c}${s}${RESET3}`;
7490
7793
  }
7491
- function progressBar(pct, warnAt = 70, critAt = 85) {
7492
- const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
7794
+ function progressBar(pct2, warnAt = 70, critAt = 85) {
7795
+ const filled = Math.round(Math.min(pct2, 100) / 100 * BAR_WIDTH);
7493
7796
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7494
- const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7797
+ const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
7495
7798
  return `${c}${bar}${RESET3}`;
7496
7799
  }
7497
7800
  function formatTimeLeft(resetsAt) {
@@ -7505,9 +7808,9 @@ function formatTimeLeft(resetsAt) {
7505
7808
  return ` (${m}m left)`;
7506
7809
  }
7507
7810
  function safeReadJson(filePath) {
7508
- if (!fs27.existsSync(filePath)) return null;
7811
+ if (!fs30.existsSync(filePath)) return null;
7509
7812
  try {
7510
- return JSON.parse(fs27.readFileSync(filePath, "utf-8"));
7813
+ return JSON.parse(fs30.readFileSync(filePath, "utf-8"));
7511
7814
  } catch {
7512
7815
  return null;
7513
7816
  }
@@ -7528,12 +7831,12 @@ function countHooksInFile(filePath) {
7528
7831
  return Object.keys(cfg.hooks).length;
7529
7832
  }
7530
7833
  function countRulesInDir(rulesDir) {
7531
- if (!fs27.existsSync(rulesDir)) return 0;
7834
+ if (!fs30.existsSync(rulesDir)) return 0;
7532
7835
  let count = 0;
7533
7836
  try {
7534
- for (const entry of fs27.readdirSync(rulesDir, { withFileTypes: true })) {
7837
+ for (const entry of fs30.readdirSync(rulesDir, { withFileTypes: true })) {
7535
7838
  if (entry.isDirectory()) {
7536
- count += countRulesInDir(path30.join(rulesDir, entry.name));
7839
+ count += countRulesInDir(path33.join(rulesDir, entry.name));
7537
7840
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7538
7841
  count++;
7539
7842
  }
@@ -7544,46 +7847,46 @@ function countRulesInDir(rulesDir) {
7544
7847
  }
7545
7848
  function isSamePath(a, b) {
7546
7849
  try {
7547
- return path30.resolve(a) === path30.resolve(b);
7850
+ return path33.resolve(a) === path33.resolve(b);
7548
7851
  } catch {
7549
7852
  return false;
7550
7853
  }
7551
7854
  }
7552
7855
  function countConfigs(cwd) {
7553
- const homeDir2 = os23.homedir();
7554
- const claudeDir = path30.join(homeDir2, ".claude");
7856
+ const homeDir2 = os26.homedir();
7857
+ const claudeDir = path33.join(homeDir2, ".claude");
7555
7858
  let claudeMdCount = 0;
7556
7859
  let rulesCount = 0;
7557
7860
  let hooksCount = 0;
7558
7861
  const userMcpServers = /* @__PURE__ */ new Set();
7559
7862
  const projectMcpServers = /* @__PURE__ */ new Set();
7560
- if (fs27.existsSync(path30.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7561
- rulesCount += countRulesInDir(path30.join(claudeDir, "rules"));
7562
- const userSettings = path30.join(claudeDir, "settings.json");
7863
+ if (fs30.existsSync(path33.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7864
+ rulesCount += countRulesInDir(path33.join(claudeDir, "rules"));
7865
+ const userSettings = path33.join(claudeDir, "settings.json");
7563
7866
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7564
7867
  hooksCount += countHooksInFile(userSettings);
7565
- const userClaudeJson = path30.join(homeDir2, ".claude.json");
7868
+ const userClaudeJson = path33.join(homeDir2, ".claude.json");
7566
7869
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7567
7870
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7568
7871
  userMcpServers.delete(name);
7569
7872
  }
7570
7873
  if (cwd) {
7571
- if (fs27.existsSync(path30.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7572
- if (fs27.existsSync(path30.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7573
- const projectClaudeDir = path30.join(cwd, ".claude");
7874
+ if (fs30.existsSync(path33.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7875
+ if (fs30.existsSync(path33.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7876
+ const projectClaudeDir = path33.join(cwd, ".claude");
7574
7877
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7575
7878
  if (!overlapsUserScope) {
7576
- if (fs27.existsSync(path30.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7577
- rulesCount += countRulesInDir(path30.join(projectClaudeDir, "rules"));
7578
- const projSettings = path30.join(projectClaudeDir, "settings.json");
7879
+ if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7880
+ rulesCount += countRulesInDir(path33.join(projectClaudeDir, "rules"));
7881
+ const projSettings = path33.join(projectClaudeDir, "settings.json");
7579
7882
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7580
7883
  hooksCount += countHooksInFile(projSettings);
7581
7884
  }
7582
- if (fs27.existsSync(path30.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7583
- const localSettings = path30.join(projectClaudeDir, "settings.local.json");
7885
+ if (fs30.existsSync(path33.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7886
+ const localSettings = path33.join(projectClaudeDir, "settings.local.json");
7584
7887
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7585
7888
  hooksCount += countHooksInFile(localSettings);
7586
- const mcpJsonServers = getMcpServerNames(path30.join(cwd, ".mcp.json"));
7889
+ const mcpJsonServers = getMcpServerNames(path33.join(cwd, ".mcp.json"));
7587
7890
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7588
7891
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7589
7892
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7616,12 +7919,12 @@ function readActiveShieldsHud() {
7616
7919
  return shieldsCache.value;
7617
7920
  }
7618
7921
  try {
7619
- const shieldsPath = path30.join(os23.homedir(), ".node9", "shields.json");
7620
- if (!fs27.existsSync(shieldsPath)) {
7922
+ const shieldsPath = path33.join(os26.homedir(), ".node9", "shields.json");
7923
+ if (!fs30.existsSync(shieldsPath)) {
7621
7924
  shieldsCache = { value: [], ts: now };
7622
7925
  return [];
7623
7926
  }
7624
- const parsed = JSON.parse(fs27.readFileSync(shieldsPath, "utf-8"));
7927
+ const parsed = JSON.parse(fs30.readFileSync(shieldsPath, "utf-8"));
7625
7928
  if (!Array.isArray(parsed.active)) {
7626
7929
  shieldsCache = { value: [], ts: now };
7627
7930
  return [];
@@ -7707,15 +8010,15 @@ function renderContextLine(stdin) {
7707
8010
  }
7708
8011
  const rl = stdin.rate_limits;
7709
8012
  if (rl?.five_hour?.used_percentage !== void 0) {
7710
- const pct = Math.round(rl.five_hour.used_percentage);
7711
- const bar = progressBar(pct, 60, 80);
8013
+ const pct2 = Math.round(rl.five_hour.used_percentage);
8014
+ const bar = progressBar(pct2, 60, 80);
7712
8015
  const left = formatTimeLeft(rl.five_hour.resets_at);
7713
- parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
8016
+ parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
7714
8017
  }
7715
8018
  if (rl?.seven_day?.used_percentage !== void 0) {
7716
- const pct = Math.round(rl.seven_day.used_percentage);
7717
- const bar = progressBar(pct, 60, 80);
7718
- parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
8019
+ const pct2 = Math.round(rl.seven_day.used_percentage);
8020
+ const bar = progressBar(pct2, 60, 80);
8021
+ parts.push(`${dim("\u2502")} 7d ${bar} ${pct2}%`);
7719
8022
  }
7720
8023
  if (parts.length === 0) return null;
7721
8024
  return parts.join(" ");
@@ -7723,17 +8026,17 @@ function renderContextLine(stdin) {
7723
8026
  async function main() {
7724
8027
  try {
7725
8028
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7726
- if (fs27.existsSync(path30.join(os23.homedir(), ".node9", "hud-debug"))) {
8029
+ if (fs30.existsSync(path33.join(os26.homedir(), ".node9", "hud-debug"))) {
7727
8030
  try {
7728
- const logPath = path30.join(os23.homedir(), ".node9", "hud-debug.log");
8031
+ const logPath = path33.join(os26.homedir(), ".node9", "hud-debug.log");
7729
8032
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7730
8033
  let size = 0;
7731
8034
  try {
7732
- size = fs27.statSync(logPath).size;
8035
+ size = fs30.statSync(logPath).size;
7733
8036
  } catch {
7734
8037
  }
7735
8038
  if (size < MAX_LOG_SIZE) {
7736
- fs27.appendFileSync(
8039
+ fs30.appendFileSync(
7737
8040
  logPath,
7738
8041
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7739
8042
  );
@@ -7754,11 +8057,11 @@ async function main() {
7754
8057
  try {
7755
8058
  const cwd = stdin.cwd ?? process.cwd();
7756
8059
  for (const configPath of [
7757
- path30.join(cwd, "node9.config.json"),
7758
- path30.join(os23.homedir(), ".node9", "config.json")
8060
+ path33.join(cwd, "node9.config.json"),
8061
+ path33.join(os26.homedir(), ".node9", "config.json")
7759
8062
  ]) {
7760
- if (!fs27.existsSync(configPath)) continue;
7761
- const cfg = JSON.parse(fs27.readFileSync(configPath, "utf-8"));
8063
+ if (!fs30.existsSync(configPath)) continue;
8064
+ const cfg = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
7762
8065
  const hud = cfg.settings?.hud;
7763
8066
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7764
8067
  }
@@ -7804,9 +8107,9 @@ init_core();
7804
8107
  import { Command } from "commander";
7805
8108
 
7806
8109
  // src/setup.ts
7807
- import fs11 from "fs";
7808
- import path14 from "path";
7809
- import os10 from "os";
8110
+ import fs12 from "fs";
8111
+ import path15 from "path";
8112
+ import os11 from "os";
7810
8113
  import chalk from "chalk";
7811
8114
  import { confirm } from "@inquirer/prompts";
7812
8115
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
@@ -7834,26 +8137,26 @@ function fullPathCommand(subcommand) {
7834
8137
  }
7835
8138
  function readJson(filePath) {
7836
8139
  try {
7837
- if (fs11.existsSync(filePath)) {
7838
- return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
8140
+ if (fs12.existsSync(filePath)) {
8141
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
7839
8142
  }
7840
8143
  } catch {
7841
8144
  }
7842
8145
  return null;
7843
8146
  }
7844
8147
  function writeJson(filePath, data) {
7845
- const dir = path14.dirname(filePath);
7846
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
7847
- fs11.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
8148
+ const dir = path15.dirname(filePath);
8149
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
8150
+ fs12.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7848
8151
  }
7849
8152
  function isNode9Hook(cmd) {
7850
8153
  if (!cmd) return false;
7851
8154
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
7852
8155
  }
7853
8156
  function teardownClaude() {
7854
- const homeDir2 = os10.homedir();
7855
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
7856
- const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
8157
+ const homeDir2 = os11.homedir();
8158
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8159
+ const mcpPath = path15.join(homeDir2, ".claude", ".mcp.json");
7857
8160
  let changed = false;
7858
8161
  const settings = readJson(hooksPath);
7859
8162
  if (settings?.hooks) {
@@ -7901,8 +8204,8 @@ function teardownClaude() {
7901
8204
  }
7902
8205
  }
7903
8206
  function teardownGemini() {
7904
- const homeDir2 = os10.homedir();
7905
- const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
8207
+ const homeDir2 = os11.homedir();
8208
+ const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
7906
8209
  const settings = readJson(settingsPath);
7907
8210
  if (!settings) {
7908
8211
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7945,8 +8248,8 @@ function teardownGemini() {
7945
8248
  }
7946
8249
  }
7947
8250
  function teardownCursor() {
7948
- const homeDir2 = os10.homedir();
7949
- const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
8251
+ const homeDir2 = os11.homedir();
8252
+ const mcpPath = path15.join(homeDir2, ".cursor", "mcp.json");
7950
8253
  const mcpConfig = readJson(mcpPath);
7951
8254
  if (!mcpConfig?.mcpServers) {
7952
8255
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -7977,9 +8280,9 @@ function teardownCursor() {
7977
8280
  }
7978
8281
  }
7979
8282
  async function setupClaude() {
7980
- const homeDir2 = os10.homedir();
7981
- const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
7982
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8283
+ const homeDir2 = os11.homedir();
8284
+ const mcpPath = path15.join(homeDir2, ".claude", ".mcp.json");
8285
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
7983
8286
  const claudeConfig = readJson(mcpPath) ?? {};
7984
8287
  const settings = readJson(hooksPath) ?? {};
7985
8288
  const servers = claudeConfig.mcpServers ?? {};
@@ -8076,8 +8379,8 @@ async function setupClaude() {
8076
8379
  }
8077
8380
  }
8078
8381
  async function setupGemini() {
8079
- const homeDir2 = os10.homedir();
8080
- const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
8382
+ const homeDir2 = os11.homedir();
8383
+ const settingsPath = path15.join(homeDir2, ".gemini", "settings.json");
8081
8384
  const settings = readJson(settingsPath) ?? {};
8082
8385
  const servers = settings.mcpServers ?? {};
8083
8386
  let hooksChanged = false;
@@ -8172,10 +8475,10 @@ async function setupGemini() {
8172
8475
  printDaemonTip();
8173
8476
  }
8174
8477
  }
8175
- function detectAgents(homeDir2 = os10.homedir()) {
8478
+ function detectAgents(homeDir2 = os11.homedir()) {
8176
8479
  const exists = (p) => {
8177
8480
  try {
8178
- return fs11.existsSync(p);
8481
+ return fs12.existsSync(p);
8179
8482
  } catch (err2) {
8180
8483
  const code = err2.code;
8181
8484
  if (code !== "ENOENT") {
@@ -8186,15 +8489,15 @@ function detectAgents(homeDir2 = os10.homedir()) {
8186
8489
  }
8187
8490
  };
8188
8491
  return {
8189
- claude: exists(path14.join(homeDir2, ".claude")) || exists(path14.join(homeDir2, ".claude.json")),
8190
- gemini: exists(path14.join(homeDir2, ".gemini")),
8191
- cursor: exists(path14.join(homeDir2, ".cursor")),
8192
- codex: exists(path14.join(homeDir2, ".codex"))
8492
+ claude: exists(path15.join(homeDir2, ".claude")) || exists(path15.join(homeDir2, ".claude.json")),
8493
+ gemini: exists(path15.join(homeDir2, ".gemini")),
8494
+ cursor: exists(path15.join(homeDir2, ".cursor")),
8495
+ codex: exists(path15.join(homeDir2, ".codex"))
8193
8496
  };
8194
8497
  }
8195
8498
  async function setupCursor() {
8196
- const homeDir2 = os10.homedir();
8197
- const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
8499
+ const homeDir2 = os11.homedir();
8500
+ const mcpPath = path15.join(homeDir2, ".cursor", "mcp.json");
8198
8501
  const mcpConfig = readJson(mcpPath) ?? {};
8199
8502
  const servers = mcpConfig.mcpServers ?? {};
8200
8503
  let anythingChanged = false;
@@ -8260,21 +8563,21 @@ async function setupCursor() {
8260
8563
  }
8261
8564
  function readToml(filePath) {
8262
8565
  try {
8263
- if (fs11.existsSync(filePath)) {
8264
- return parseToml(fs11.readFileSync(filePath, "utf-8"));
8566
+ if (fs12.existsSync(filePath)) {
8567
+ return parseToml(fs12.readFileSync(filePath, "utf-8"));
8265
8568
  }
8266
8569
  } catch {
8267
8570
  }
8268
8571
  return null;
8269
8572
  }
8270
8573
  function writeToml(filePath, data) {
8271
- const dir = path14.dirname(filePath);
8272
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
8273
- fs11.writeFileSync(filePath, stringifyToml(data));
8574
+ const dir = path15.dirname(filePath);
8575
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
8576
+ fs12.writeFileSync(filePath, stringifyToml(data));
8274
8577
  }
8275
8578
  async function setupCodex() {
8276
- const homeDir2 = os10.homedir();
8277
- const configPath = path14.join(homeDir2, ".codex", "config.toml");
8579
+ const homeDir2 = os11.homedir();
8580
+ const configPath = path15.join(homeDir2, ".codex", "config.toml");
8278
8581
  const config = readToml(configPath) ?? {};
8279
8582
  const servers = config.mcp_servers ?? {};
8280
8583
  let anythingChanged = false;
@@ -8339,8 +8642,8 @@ async function setupCodex() {
8339
8642
  }
8340
8643
  }
8341
8644
  function setupHud() {
8342
- const homeDir2 = os10.homedir();
8343
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8645
+ const homeDir2 = os11.homedir();
8646
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8344
8647
  const settings = readJson(hooksPath) ?? {};
8345
8648
  const hudCommand = fullPathCommand("hud");
8346
8649
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8366,8 +8669,8 @@ function setupHud() {
8366
8669
  console.log(chalk.gray(" Restart Claude Code to activate."));
8367
8670
  }
8368
8671
  function teardownHud() {
8369
- const homeDir2 = os10.homedir();
8370
- const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
8672
+ const homeDir2 = os11.homedir();
8673
+ const hooksPath = path15.join(homeDir2, ".claude", "settings.json");
8371
8674
  const settings = readJson(hooksPath);
8372
8675
  if (!settings) {
8373
8676
  console.log(chalk.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8387,10 +8690,10 @@ function teardownHud() {
8387
8690
 
8388
8691
  // src/cli.ts
8389
8692
  init_daemon2();
8390
- import chalk19 from "chalk";
8391
- import fs28 from "fs";
8392
- import path31 from "path";
8393
- import os24 from "os";
8693
+ import chalk20 from "chalk";
8694
+ import fs31 from "fs";
8695
+ import path34 from "path";
8696
+ import os27 from "os";
8394
8697
  import { confirm as confirm2 } from "@inquirer/prompts";
8395
8698
 
8396
8699
  // src/utils/duration.ts
@@ -8619,19 +8922,19 @@ init_daemon();
8619
8922
  init_config();
8620
8923
  init_policy();
8621
8924
  import chalk5 from "chalk";
8622
- import fs18 from "fs";
8925
+ import fs20 from "fs";
8623
8926
  import { spawn as spawn6 } from "child_process";
8624
- import path20 from "path";
8625
- import os14 from "os";
8927
+ import path22 from "path";
8928
+ import os16 from "os";
8626
8929
 
8627
8930
  // src/undo.ts
8628
8931
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
8629
- import crypto2 from "crypto";
8630
- import fs17 from "fs";
8932
+ import crypto3 from "crypto";
8933
+ import fs19 from "fs";
8631
8934
  import net3 from "net";
8632
- import path19 from "path";
8633
- import os13 from "os";
8634
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path19.join(os13.tmpdir(), "node9-activity.sock");
8935
+ import path21 from "path";
8936
+ import os15 from "os";
8937
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path21.join(os15.tmpdir(), "node9-activity.sock");
8635
8938
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8636
8939
  try {
8637
8940
  const payload = JSON.stringify({
@@ -8651,22 +8954,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8651
8954
  } catch {
8652
8955
  }
8653
8956
  }
8654
- var SNAPSHOT_STACK_PATH = path19.join(os13.homedir(), ".node9", "snapshots.json");
8655
- var UNDO_LATEST_PATH = path19.join(os13.homedir(), ".node9", "undo_latest.txt");
8957
+ var SNAPSHOT_STACK_PATH = path21.join(os15.homedir(), ".node9", "snapshots.json");
8958
+ var UNDO_LATEST_PATH = path21.join(os15.homedir(), ".node9", "undo_latest.txt");
8656
8959
  var MAX_SNAPSHOTS = 10;
8657
8960
  var GIT_TIMEOUT = 15e3;
8658
8961
  function readStack() {
8659
8962
  try {
8660
- if (fs17.existsSync(SNAPSHOT_STACK_PATH))
8661
- return JSON.parse(fs17.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8963
+ if (fs19.existsSync(SNAPSHOT_STACK_PATH))
8964
+ return JSON.parse(fs19.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8662
8965
  } catch {
8663
8966
  }
8664
8967
  return [];
8665
8968
  }
8666
8969
  function writeStack(stack) {
8667
- const dir = path19.dirname(SNAPSHOT_STACK_PATH);
8668
- if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
8669
- fs17.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8970
+ const dir = path21.dirname(SNAPSHOT_STACK_PATH);
8971
+ if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
8972
+ fs19.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8670
8973
  }
8671
8974
  function extractFilePath(args) {
8672
8975
  if (!args || typeof args !== "object") return null;
@@ -8686,12 +8989,12 @@ function buildArgsSummary(tool, args) {
8686
8989
  return "";
8687
8990
  }
8688
8991
  function findProjectRoot(filePath) {
8689
- let dir = path19.dirname(filePath);
8992
+ let dir = path21.dirname(filePath);
8690
8993
  while (true) {
8691
- if (fs17.existsSync(path19.join(dir, ".git")) || fs17.existsSync(path19.join(dir, "package.json"))) {
8994
+ if (fs19.existsSync(path21.join(dir, ".git")) || fs19.existsSync(path21.join(dir, "package.json"))) {
8692
8995
  return dir;
8693
8996
  }
8694
- const parent = path19.dirname(dir);
8997
+ const parent = path21.dirname(dir);
8695
8998
  if (parent === dir) return process.cwd();
8696
8999
  dir = parent;
8697
9000
  }
@@ -8699,7 +9002,7 @@ function findProjectRoot(filePath) {
8699
9002
  function normalizeCwdForHash(cwd) {
8700
9003
  let normalized;
8701
9004
  try {
8702
- normalized = fs17.realpathSync(cwd);
9005
+ normalized = fs19.realpathSync(cwd);
8703
9006
  } catch {
8704
9007
  normalized = cwd;
8705
9008
  }
@@ -8708,17 +9011,17 @@ function normalizeCwdForHash(cwd) {
8708
9011
  return normalized;
8709
9012
  }
8710
9013
  function getShadowRepoDir(cwd) {
8711
- const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8712
- return path19.join(os13.homedir(), ".node9", "snapshots", hash);
9014
+ const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
9015
+ return path21.join(os15.homedir(), ".node9", "snapshots", hash);
8713
9016
  }
8714
9017
  function cleanOrphanedIndexFiles(shadowDir) {
8715
9018
  try {
8716
9019
  const cutoff = Date.now() - 6e4;
8717
- for (const f of fs17.readdirSync(shadowDir)) {
9020
+ for (const f of fs19.readdirSync(shadowDir)) {
8718
9021
  if (f.startsWith("index_")) {
8719
- const fp = path19.join(shadowDir, f);
9022
+ const fp = path21.join(shadowDir, f);
8720
9023
  try {
8721
- if (fs17.statSync(fp).mtimeMs < cutoff) fs17.unlinkSync(fp);
9024
+ if (fs19.statSync(fp).mtimeMs < cutoff) fs19.unlinkSync(fp);
8722
9025
  } catch {
8723
9026
  }
8724
9027
  }
@@ -8730,7 +9033,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8730
9033
  const hardcoded = [".git", ".node9"];
8731
9034
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8732
9035
  try {
8733
- fs17.writeFileSync(path19.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9036
+ fs19.writeFileSync(path21.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8734
9037
  } catch {
8735
9038
  }
8736
9039
  }
@@ -8743,25 +9046,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8743
9046
  timeout: 3e3
8744
9047
  });
8745
9048
  if (check.status === 0) {
8746
- const ptPath = path19.join(shadowDir, "project-path.txt");
9049
+ const ptPath = path21.join(shadowDir, "project-path.txt");
8747
9050
  try {
8748
- const stored = fs17.readFileSync(ptPath, "utf8").trim();
9051
+ const stored = fs19.readFileSync(ptPath, "utf8").trim();
8749
9052
  if (stored === normalizedCwd) return true;
8750
9053
  if (process.env.NODE9_DEBUG === "1")
8751
9054
  console.error(
8752
9055
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8753
9056
  );
8754
- fs17.rmSync(shadowDir, { recursive: true, force: true });
9057
+ fs19.rmSync(shadowDir, { recursive: true, force: true });
8755
9058
  } catch {
8756
9059
  try {
8757
- fs17.writeFileSync(ptPath, normalizedCwd, "utf8");
9060
+ fs19.writeFileSync(ptPath, normalizedCwd, "utf8");
8758
9061
  } catch {
8759
9062
  }
8760
9063
  return true;
8761
9064
  }
8762
9065
  }
8763
9066
  try {
8764
- fs17.mkdirSync(shadowDir, { recursive: true });
9067
+ fs19.mkdirSync(shadowDir, { recursive: true });
8765
9068
  } catch {
8766
9069
  }
8767
9070
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8770,7 +9073,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8770
9073
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8771
9074
  return false;
8772
9075
  }
8773
- const configFile = path19.join(shadowDir, "config");
9076
+ const configFile = path21.join(shadowDir, "config");
8774
9077
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8775
9078
  timeout: 3e3
8776
9079
  });
@@ -8778,7 +9081,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8778
9081
  timeout: 3e3
8779
9082
  });
8780
9083
  try {
8781
- fs17.writeFileSync(path19.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9084
+ fs19.writeFileSync(path21.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8782
9085
  } catch {
8783
9086
  }
8784
9087
  return true;
@@ -8798,12 +9101,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8798
9101
  let indexFile = null;
8799
9102
  try {
8800
9103
  const rawFilePath = extractFilePath(args);
8801
- const absFilePath = rawFilePath && path19.isAbsolute(rawFilePath) ? rawFilePath : null;
9104
+ const absFilePath = rawFilePath && path21.isAbsolute(rawFilePath) ? rawFilePath : null;
8802
9105
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8803
9106
  const shadowDir = getShadowRepoDir(cwd);
8804
9107
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8805
9108
  writeShadowExcludes(shadowDir, ignorePaths);
8806
- indexFile = path19.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9109
+ indexFile = path21.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8807
9110
  const shadowEnv = {
8808
9111
  ...process.env,
8809
9112
  GIT_DIR: shadowDir,
@@ -8875,7 +9178,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8875
9178
  writeStack(stack);
8876
9179
  const entry = stack[stack.length - 1];
8877
9180
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8878
- fs17.writeFileSync(UNDO_LATEST_PATH, commitHash);
9181
+ fs19.writeFileSync(UNDO_LATEST_PATH, commitHash);
8879
9182
  if (shouldGc) {
8880
9183
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8881
9184
  }
@@ -8886,7 +9189,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8886
9189
  } finally {
8887
9190
  if (indexFile) {
8888
9191
  try {
8889
- fs17.unlinkSync(indexFile);
9192
+ fs19.unlinkSync(indexFile);
8890
9193
  } catch {
8891
9194
  }
8892
9195
  }
@@ -8962,9 +9265,9 @@ function applyUndo(hash, cwd) {
8962
9265
  timeout: GIT_TIMEOUT
8963
9266
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8964
9267
  for (const file of [...tracked, ...untracked]) {
8965
- const fullPath = path19.join(dir, file);
8966
- if (!snapshotFiles.has(file) && fs17.existsSync(fullPath)) {
8967
- fs17.unlinkSync(fullPath);
9268
+ const fullPath = path21.join(dir, file);
9269
+ if (!snapshotFiles.has(file) && fs19.existsSync(fullPath)) {
9270
+ fs19.unlinkSync(fullPath);
8968
9271
  }
8969
9272
  }
8970
9273
  return true;
@@ -8988,9 +9291,9 @@ function registerCheckCommand(program2) {
8988
9291
  } catch (err2) {
8989
9292
  const tempConfig = getConfig();
8990
9293
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
8991
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9294
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
8992
9295
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
8993
- fs18.appendFileSync(
9296
+ fs20.appendFileSync(
8994
9297
  logPath,
8995
9298
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
8996
9299
  RAW: ${raw}
@@ -9003,10 +9306,10 @@ RAW: ${raw}
9003
9306
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9004
9307
  try {
9005
9308
  const scriptPath = process.argv[1];
9006
- if (typeof scriptPath !== "string" || !path20.isAbsolute(scriptPath))
9309
+ if (typeof scriptPath !== "string" || !path22.isAbsolute(scriptPath))
9007
9310
  throw new Error("node9: argv[1] is not an absolute path");
9008
- const resolvedScript = fs18.realpathSync(scriptPath);
9009
- const expectedCli = fs18.realpathSync(path20.resolve(__dirname, "../../cli.js"));
9311
+ const resolvedScript = fs20.realpathSync(scriptPath);
9312
+ const expectedCli = fs20.realpathSync(path22.resolve(__dirname, "../../cli.js"));
9010
9313
  if (resolvedScript !== expectedCli)
9011
9314
  throw new Error(
9012
9315
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9032,10 +9335,10 @@ RAW: ${raw}
9032
9335
  }
9033
9336
  }
9034
9337
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9035
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9036
- if (!fs18.existsSync(path20.dirname(logPath)))
9037
- fs18.mkdirSync(path20.dirname(logPath), { recursive: true });
9038
- fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9338
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9339
+ if (!fs20.existsSync(path22.dirname(logPath)))
9340
+ fs20.mkdirSync(path22.dirname(logPath), { recursive: true });
9341
+ fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9039
9342
  `);
9040
9343
  }
9041
9344
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9048,8 +9351,8 @@ RAW: ${raw}
9048
9351
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9049
9352
  let ttyFd = null;
9050
9353
  try {
9051
- ttyFd = fs18.openSync("/dev/tty", "w");
9052
- const writeTty = (line) => fs18.writeSync(ttyFd, line + "\n");
9354
+ ttyFd = fs20.openSync("/dev/tty", "w");
9355
+ const writeTty = (line) => fs20.writeSync(ttyFd, line + "\n");
9053
9356
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9054
9357
  writeTty(chalk5.bgRed.white.bold(`
9055
9358
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9068,7 +9371,7 @@ RAW: ${raw}
9068
9371
  } finally {
9069
9372
  if (ttyFd !== null)
9070
9373
  try {
9071
- fs18.closeSync(ttyFd);
9374
+ fs20.closeSync(ttyFd);
9072
9375
  } catch {
9073
9376
  }
9074
9377
  }
@@ -9100,7 +9403,7 @@ RAW: ${raw}
9100
9403
  if (shouldSnapshot(toolName, toolInput, config)) {
9101
9404
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9102
9405
  }
9103
- const safeCwdForAuth = typeof payload.cwd === "string" && path20.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9406
+ const safeCwdForAuth = typeof payload.cwd === "string" && path22.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9104
9407
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9105
9408
  cwd: safeCwdForAuth
9106
9409
  });
@@ -9112,12 +9415,12 @@ RAW: ${raw}
9112
9415
  }
9113
9416
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9114
9417
  try {
9115
- const tty = fs18.openSync("/dev/tty", "w");
9116
- fs18.writeSync(
9418
+ const tty = fs20.openSync("/dev/tty", "w");
9419
+ fs20.writeSync(
9117
9420
  tty,
9118
9421
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9119
9422
  );
9120
- fs18.closeSync(tty);
9423
+ fs20.closeSync(tty);
9121
9424
  } catch {
9122
9425
  }
9123
9426
  const daemonReady = await autoStartDaemonAndWait();
@@ -9144,9 +9447,9 @@ RAW: ${raw}
9144
9447
  });
9145
9448
  } catch (err2) {
9146
9449
  if (process.env.NODE9_DEBUG === "1") {
9147
- const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
9450
+ const logPath = path22.join(os16.homedir(), ".node9", "hook-debug.log");
9148
9451
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9149
- fs18.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9452
+ fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9150
9453
  `);
9151
9454
  }
9152
9455
  process.exit(0);
@@ -9183,9 +9486,9 @@ RAW: ${raw}
9183
9486
  init_audit();
9184
9487
  init_config();
9185
9488
  init_policy();
9186
- import fs19 from "fs";
9187
- import path21 from "path";
9188
- import os15 from "os";
9489
+ import fs21 from "fs";
9490
+ import path23 from "path";
9491
+ import os17 from "os";
9189
9492
  init_daemon();
9190
9493
 
9191
9494
  // src/utils/cp-mv-parser.ts
@@ -9226,9 +9529,9 @@ function containsShellMetachar(token) {
9226
9529
  }
9227
9530
 
9228
9531
  // src/cli/commands/log.ts
9229
- var TEST_COMMAND_RE = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
9532
+ var TEST_COMMAND_RE2 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
9230
9533
  function detectTestResult(command, output) {
9231
- if (!TEST_COMMAND_RE.test(command)) return null;
9534
+ if (!TEST_COMMAND_RE2.test(command)) return null;
9232
9535
  const out = output.toLowerCase();
9233
9536
  if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
9234
9537
  out
@@ -9258,10 +9561,10 @@ function registerLogCommand(program2) {
9258
9561
  decision: "allowed",
9259
9562
  source: "post-hook"
9260
9563
  };
9261
- const logPath = path21.join(os15.homedir(), ".node9", "audit.log");
9262
- if (!fs19.existsSync(path21.dirname(logPath)))
9263
- fs19.mkdirSync(path21.dirname(logPath), { recursive: true });
9264
- fs19.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9564
+ const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9565
+ if (!fs21.existsSync(path23.dirname(logPath)))
9566
+ fs21.mkdirSync(path23.dirname(logPath), { recursive: true });
9567
+ fs21.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9265
9568
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9266
9569
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9267
9570
  if (command) {
@@ -9277,16 +9580,24 @@ function registerLogCommand(program2) {
9277
9580
  if (bashCommand && output) {
9278
9581
  const testResult = detectTestResult(bashCommand, output);
9279
9582
  if (testResult) {
9280
- await notifyActivitySocket({
9281
- id: "test-result",
9282
- ts: Date.now(),
9583
+ appendToLog(LOCAL_AUDIT_LOG, {
9584
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9283
9585
  tool,
9284
- status: testResult === "pass" ? "test_pass" : "test_fail"
9586
+ testResult,
9587
+ source: "test-result"
9285
9588
  });
9589
+ if (isDaemonRunning()) {
9590
+ await notifyActivitySocket({
9591
+ id: "test-result",
9592
+ ts: Date.now(),
9593
+ tool,
9594
+ status: testResult === "pass" ? "test_pass" : "test_fail"
9595
+ });
9596
+ }
9286
9597
  }
9287
9598
  }
9288
9599
  }
9289
- const safeCwd = typeof payload.cwd === "string" && path21.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9600
+ const safeCwd = typeof payload.cwd === "string" && path23.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9290
9601
  const config = getConfig(safeCwd);
9291
9602
  if (shouldSnapshot(tool, {}, config)) {
9292
9603
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9295,9 +9606,9 @@ function registerLogCommand(program2) {
9295
9606
  const msg = err2 instanceof Error ? err2.message : String(err2);
9296
9607
  process.stderr.write(`[Node9] audit log error: ${msg}
9297
9608
  `);
9298
- const debugPath = path21.join(os15.homedir(), ".node9", "hook-debug.log");
9609
+ const debugPath = path23.join(os17.homedir(), ".node9", "hook-debug.log");
9299
9610
  try {
9300
- fs19.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9611
+ fs21.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9301
9612
  `);
9302
9613
  } catch {
9303
9614
  }
@@ -9698,13 +10009,13 @@ function registerConfigShowCommand(program2) {
9698
10009
  // src/cli/commands/doctor.ts
9699
10010
  init_daemon();
9700
10011
  import chalk7 from "chalk";
9701
- import fs20 from "fs";
9702
- import path22 from "path";
9703
- import os16 from "os";
10012
+ import fs22 from "fs";
10013
+ import path24 from "path";
10014
+ import os18 from "os";
9704
10015
  import { execSync as execSync2 } from "child_process";
9705
10016
  function registerDoctorCommand(program2, version2) {
9706
10017
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9707
- const homeDir2 = os16.homedir();
10018
+ const homeDir2 = os18.homedir();
9708
10019
  let failures = 0;
9709
10020
  function pass(msg) {
9710
10021
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -9753,10 +10064,10 @@ function registerDoctorCommand(program2, version2) {
9753
10064
  );
9754
10065
  }
9755
10066
  section("Configuration");
9756
- const globalConfigPath = path22.join(homeDir2, ".node9", "config.json");
9757
- if (fs20.existsSync(globalConfigPath)) {
10067
+ const globalConfigPath = path24.join(homeDir2, ".node9", "config.json");
10068
+ if (fs22.existsSync(globalConfigPath)) {
9758
10069
  try {
9759
- JSON.parse(fs20.readFileSync(globalConfigPath, "utf-8"));
10070
+ JSON.parse(fs22.readFileSync(globalConfigPath, "utf-8"));
9760
10071
  pass("~/.node9/config.json found and valid");
9761
10072
  } catch {
9762
10073
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9764,10 +10075,10 @@ function registerDoctorCommand(program2, version2) {
9764
10075
  } else {
9765
10076
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9766
10077
  }
9767
- const projectConfigPath = path22.join(process.cwd(), "node9.config.json");
9768
- if (fs20.existsSync(projectConfigPath)) {
10078
+ const projectConfigPath = path24.join(process.cwd(), "node9.config.json");
10079
+ if (fs22.existsSync(projectConfigPath)) {
9769
10080
  try {
9770
- JSON.parse(fs20.readFileSync(projectConfigPath, "utf-8"));
10081
+ JSON.parse(fs22.readFileSync(projectConfigPath, "utf-8"));
9771
10082
  pass("node9.config.json found and valid (project)");
9772
10083
  } catch {
9773
10084
  fail(
@@ -9776,8 +10087,8 @@ function registerDoctorCommand(program2, version2) {
9776
10087
  );
9777
10088
  }
9778
10089
  }
9779
- const credsPath = path22.join(homeDir2, ".node9", "credentials.json");
9780
- if (fs20.existsSync(credsPath)) {
10090
+ const credsPath = path24.join(homeDir2, ".node9", "credentials.json");
10091
+ if (fs22.existsSync(credsPath)) {
9781
10092
  pass("Cloud credentials found (~/.node9/credentials.json)");
9782
10093
  } else {
9783
10094
  warn(
@@ -9786,10 +10097,10 @@ function registerDoctorCommand(program2, version2) {
9786
10097
  );
9787
10098
  }
9788
10099
  section("Agent Hooks");
9789
- const claudeSettingsPath = path22.join(homeDir2, ".claude", "settings.json");
9790
- if (fs20.existsSync(claudeSettingsPath)) {
10100
+ const claudeSettingsPath = path24.join(homeDir2, ".claude", "settings.json");
10101
+ if (fs22.existsSync(claudeSettingsPath)) {
9791
10102
  try {
9792
- const cs = JSON.parse(fs20.readFileSync(claudeSettingsPath, "utf-8"));
10103
+ const cs = JSON.parse(fs22.readFileSync(claudeSettingsPath, "utf-8"));
9793
10104
  const hasHook = cs.hooks?.PreToolUse?.some(
9794
10105
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9795
10106
  );
@@ -9805,10 +10116,10 @@ function registerDoctorCommand(program2, version2) {
9805
10116
  } else {
9806
10117
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9807
10118
  }
9808
- const geminiSettingsPath = path22.join(homeDir2, ".gemini", "settings.json");
9809
- if (fs20.existsSync(geminiSettingsPath)) {
10119
+ const geminiSettingsPath = path24.join(homeDir2, ".gemini", "settings.json");
10120
+ if (fs22.existsSync(geminiSettingsPath)) {
9810
10121
  try {
9811
- const gs = JSON.parse(fs20.readFileSync(geminiSettingsPath, "utf-8"));
10122
+ const gs = JSON.parse(fs22.readFileSync(geminiSettingsPath, "utf-8"));
9812
10123
  const hasHook = gs.hooks?.BeforeTool?.some(
9813
10124
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9814
10125
  );
@@ -9824,10 +10135,10 @@ function registerDoctorCommand(program2, version2) {
9824
10135
  } else {
9825
10136
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9826
10137
  }
9827
- const cursorHooksPath = path22.join(homeDir2, ".cursor", "hooks.json");
9828
- if (fs20.existsSync(cursorHooksPath)) {
10138
+ const cursorHooksPath = path24.join(homeDir2, ".cursor", "hooks.json");
10139
+ if (fs22.existsSync(cursorHooksPath)) {
9829
10140
  try {
9830
- const cur = JSON.parse(fs20.readFileSync(cursorHooksPath, "utf-8"));
10141
+ const cur = JSON.parse(fs22.readFileSync(cursorHooksPath, "utf-8"));
9831
10142
  const hasHook = cur.hooks?.preToolUse?.some(
9832
10143
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9833
10144
  );
@@ -9865,9 +10176,9 @@ function registerDoctorCommand(program2, version2) {
9865
10176
 
9866
10177
  // src/cli/commands/audit.ts
9867
10178
  import chalk8 from "chalk";
9868
- import fs21 from "fs";
9869
- import path23 from "path";
9870
- import os17 from "os";
10179
+ import fs23 from "fs";
10180
+ import path25 from "path";
10181
+ import os19 from "os";
9871
10182
  function formatRelativeTime(timestamp) {
9872
10183
  const diff = Date.now() - new Date(timestamp).getTime();
9873
10184
  const sec = Math.floor(diff / 1e3);
@@ -9880,14 +10191,14 @@ function formatRelativeTime(timestamp) {
9880
10191
  }
9881
10192
  function registerAuditCommand(program2) {
9882
10193
  program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
9883
- const logPath = path23.join(os17.homedir(), ".node9", "audit.log");
9884
- if (!fs21.existsSync(logPath)) {
10194
+ const logPath = path25.join(os19.homedir(), ".node9", "audit.log");
10195
+ if (!fs23.existsSync(logPath)) {
9885
10196
  console.log(
9886
10197
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
9887
10198
  );
9888
10199
  return;
9889
10200
  }
9890
- const raw = fs21.readFileSync(logPath, "utf-8");
10201
+ const raw = fs23.readFileSync(logPath, "utf-8");
9891
10202
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
9892
10203
  let entries = lines.flatMap((line) => {
9893
10204
  try {
@@ -9939,10 +10250,445 @@ function registerAuditCommand(program2) {
9939
10250
  });
9940
10251
  }
9941
10252
 
10253
+ // src/cli/commands/report.ts
10254
+ import chalk9 from "chalk";
10255
+ import fs24 from "fs";
10256
+ import path26 from "path";
10257
+ import os20 from "os";
10258
+ 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;
10259
+ function buildTestTimestamps(allEntries) {
10260
+ const testTs = /* @__PURE__ */ new Set();
10261
+ for (const e of allEntries) {
10262
+ if (e.source !== "post-hook") continue;
10263
+ if (e.tool !== "Bash" && e.tool !== "bash") continue;
10264
+ const cmd = e.args?.command;
10265
+ if (typeof cmd === "string" && TEST_COMMAND_RE3.test(cmd)) {
10266
+ testTs.add(new Date(e.ts).getTime());
10267
+ }
10268
+ }
10269
+ return testTs;
10270
+ }
10271
+ function isTestEntry(entry, testTs) {
10272
+ if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
10273
+ if (entry.testRun === true) return true;
10274
+ const cmd = entry.args?.command;
10275
+ if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
10276
+ const t = new Date(entry.ts).getTime();
10277
+ for (const ts of testTs) {
10278
+ if (Math.abs(ts - t) <= 3e3) return true;
10279
+ }
10280
+ return false;
10281
+ }
10282
+ function getDateRange(period) {
10283
+ const now = /* @__PURE__ */ new Date();
10284
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
10285
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
10286
+ switch (period) {
10287
+ case "today":
10288
+ return { start: todayStart, end };
10289
+ case "7d": {
10290
+ const s = new Date(todayStart);
10291
+ s.setDate(s.getDate() - 6);
10292
+ return { start: s, end };
10293
+ }
10294
+ case "30d": {
10295
+ const s = new Date(todayStart);
10296
+ s.setDate(s.getDate() - 29);
10297
+ return { start: s, end };
10298
+ }
10299
+ case "month":
10300
+ return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
10301
+ }
10302
+ }
10303
+ function parseAuditLog(logPath) {
10304
+ if (!fs24.existsSync(logPath)) return [];
10305
+ const raw = fs24.readFileSync(logPath, "utf-8");
10306
+ return raw.split("\n").flatMap((line) => {
10307
+ if (!line.trim()) return [];
10308
+ try {
10309
+ return [JSON.parse(line)];
10310
+ } catch {
10311
+ return [];
10312
+ }
10313
+ });
10314
+ }
10315
+ function isAllow(decision) {
10316
+ return decision.startsWith("allow");
10317
+ }
10318
+ function isDlp(checkedBy) {
10319
+ return !!checkedBy?.includes("dlp");
10320
+ }
10321
+ function barStr(value, max, width) {
10322
+ if (max === 0 || width <= 0) return "\u2591".repeat(width);
10323
+ const filled = Math.max(1, Math.round(value / max * width));
10324
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
10325
+ }
10326
+ function colorBar(value, max, width) {
10327
+ const s = barStr(value, max, width);
10328
+ const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
10329
+ return chalk9.cyan(s.slice(0, filled)) + chalk9.dim(s.slice(filled));
10330
+ }
10331
+ function pct(num2, total) {
10332
+ if (total === 0) return "\u2013";
10333
+ return Math.round(num2 / total * 100) + "%";
10334
+ }
10335
+ function fmtDate(d) {
10336
+ const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
10337
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
10338
+ }
10339
+ function num(n) {
10340
+ return n.toLocaleString();
10341
+ }
10342
+ function fmtCost(usd) {
10343
+ if (usd < 1e-3) return "< $0.001";
10344
+ if (usd < 1) return "$" + usd.toFixed(4);
10345
+ return "$" + usd.toFixed(2);
10346
+ }
10347
+ var CLAUDE_PRICING = {
10348
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10349
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10350
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
10351
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10352
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10353
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10354
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10355
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10356
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
10357
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
10358
+ };
10359
+ function claudeModelPrice(model) {
10360
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10361
+ for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
10362
+ if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
10363
+ }
10364
+ return null;
10365
+ }
10366
+ function loadClaudeCost(start, end) {
10367
+ const empty = {
10368
+ total: 0,
10369
+ byDay: /* @__PURE__ */ new Map(),
10370
+ byModel: /* @__PURE__ */ new Map(),
10371
+ inputTokens: 0,
10372
+ cacheReadTokens: 0
10373
+ };
10374
+ const projectsDir = path26.join(os20.homedir(), ".claude", "projects");
10375
+ if (!fs24.existsSync(projectsDir)) return empty;
10376
+ let dirs;
10377
+ try {
10378
+ dirs = fs24.readdirSync(projectsDir);
10379
+ } catch {
10380
+ return empty;
10381
+ }
10382
+ let total = 0;
10383
+ let inputTokens = 0;
10384
+ let cacheReadTokens = 0;
10385
+ const byDay = /* @__PURE__ */ new Map();
10386
+ const byModel = /* @__PURE__ */ new Map();
10387
+ for (const proj of dirs) {
10388
+ const projPath = path26.join(projectsDir, proj);
10389
+ let files;
10390
+ try {
10391
+ const stat = fs24.statSync(projPath);
10392
+ if (!stat.isDirectory()) continue;
10393
+ files = fs24.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10394
+ } catch {
10395
+ continue;
10396
+ }
10397
+ for (const file of files) {
10398
+ try {
10399
+ const raw = fs24.readFileSync(path26.join(projPath, file), "utf-8");
10400
+ for (const line of raw.split("\n")) {
10401
+ if (!line.trim()) continue;
10402
+ let entry;
10403
+ try {
10404
+ entry = JSON.parse(line);
10405
+ } catch {
10406
+ continue;
10407
+ }
10408
+ if (entry.type !== "assistant") continue;
10409
+ if (!entry.timestamp) continue;
10410
+ const ts = new Date(entry.timestamp);
10411
+ if (ts < start || ts > end) continue;
10412
+ const usage = entry.message?.usage;
10413
+ const model = entry.message?.model;
10414
+ if (!usage || !model) continue;
10415
+ const p = claudeModelPrice(model);
10416
+ if (!p) continue;
10417
+ const inp = usage.input_tokens ?? 0;
10418
+ const out = usage.output_tokens ?? 0;
10419
+ const cw = usage.cache_creation_input_tokens ?? 0;
10420
+ const cr = usage.cache_read_input_tokens ?? 0;
10421
+ const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
10422
+ total += cost;
10423
+ inputTokens += inp;
10424
+ cacheReadTokens += cr;
10425
+ const dateKey = entry.timestamp.slice(0, 10);
10426
+ byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10427
+ const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10428
+ byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
10429
+ }
10430
+ } catch {
10431
+ continue;
10432
+ }
10433
+ }
10434
+ }
10435
+ return { total, byDay, byModel, inputTokens, cacheReadTokens };
10436
+ }
10437
+ function registerReportCommand(program2) {
10438
+ 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) => {
10439
+ const period = ["today", "7d", "30d", "month"].includes(
10440
+ options.period
10441
+ ) ? options.period : "7d";
10442
+ const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
10443
+ const allEntries = parseAuditLog(logPath);
10444
+ if (allEntries.length === 0) {
10445
+ console.log(
10446
+ chalk9.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
10447
+ );
10448
+ return;
10449
+ }
10450
+ const { start, end } = getDateRange(period);
10451
+ const {
10452
+ total: costUSD,
10453
+ byDay: costByDay,
10454
+ byModel: costByModel,
10455
+ inputTokens: costInputTokens,
10456
+ cacheReadTokens: costCacheRead
10457
+ } = loadClaudeCost(start, end);
10458
+ const periodMs = end.getTime() - start.getTime();
10459
+ const priorEnd = new Date(start.getTime() - 1);
10460
+ const priorStart = new Date(start.getTime() - periodMs);
10461
+ const priorEntries = allEntries.filter((e) => {
10462
+ if (e.source === "post-hook") return false;
10463
+ const ts = new Date(e.ts);
10464
+ return ts >= priorStart && ts <= priorEnd;
10465
+ });
10466
+ const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
10467
+ const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
10468
+ const excludeTests = options.tests === false;
10469
+ const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
10470
+ let filteredTestCount = 0;
10471
+ const entries = allEntries.filter((e) => {
10472
+ if (e.source === "post-hook") return false;
10473
+ const ts = new Date(e.ts);
10474
+ if (ts < start || ts > end) return false;
10475
+ if (excludeTests && isTestEntry(e, testTs)) {
10476
+ filteredTestCount++;
10477
+ return false;
10478
+ }
10479
+ return true;
10480
+ });
10481
+ if (entries.length === 0) {
10482
+ console.log(chalk9.yellow(`
10483
+ No activity for period "${period}".
10484
+ `));
10485
+ return;
10486
+ }
10487
+ let allowed = 0;
10488
+ let blocked = 0;
10489
+ let dlpHits = 0;
10490
+ let loopHits = 0;
10491
+ let testPasses = 0;
10492
+ let testFails = 0;
10493
+ const toolMap = /* @__PURE__ */ new Map();
10494
+ const blockMap = /* @__PURE__ */ new Map();
10495
+ const agentMap = /* @__PURE__ */ new Map();
10496
+ const mcpMap = /* @__PURE__ */ new Map();
10497
+ const dailyMap = /* @__PURE__ */ new Map();
10498
+ const hourMap = /* @__PURE__ */ new Map();
10499
+ for (const e of entries) {
10500
+ const allow = isAllow(e.decision);
10501
+ const dateKey = e.ts.slice(0, 10);
10502
+ if (allow) allowed++;
10503
+ else blocked++;
10504
+ if (isDlp(e.checkedBy)) dlpHits++;
10505
+ if (e.checkedBy === "loop-detected") loopHits++;
10506
+ const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
10507
+ t.calls++;
10508
+ if (!allow) t.blocked++;
10509
+ toolMap.set(e.tool, t);
10510
+ if (!allow && e.checkedBy) {
10511
+ blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
10512
+ }
10513
+ if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
10514
+ if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
10515
+ const hour = new Date(e.ts).getHours();
10516
+ hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
10517
+ const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
10518
+ d.calls++;
10519
+ if (!allow) d.blocked++;
10520
+ dailyMap.set(dateKey, d);
10521
+ }
10522
+ for (const e of allEntries) {
10523
+ if (e.source !== "test-result") continue;
10524
+ const ts = new Date(e.ts);
10525
+ if (ts < start || ts > end) continue;
10526
+ if (e.testResult === "pass") testPasses++;
10527
+ else if (e.testResult === "fail") testFails++;
10528
+ }
10529
+ const total = entries.length;
10530
+ const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
10531
+ const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
10532
+ const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
10533
+ const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
10534
+ const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
10535
+ const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
10536
+ const W = Math.min(process.stdout.columns || 80, 100);
10537
+ const INNER = W - 4;
10538
+ const COL = Math.floor(INNER / 2) - 1;
10539
+ const LABEL = 24;
10540
+ const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
10541
+ const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num(v.calls).length), 1);
10542
+ const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num(v).length), 1);
10543
+ const line = chalk9.dim("\u2500".repeat(W - 2));
10544
+ const periodLabel = {
10545
+ today: "Today",
10546
+ "7d": "Last 7 Days",
10547
+ "30d": "Last 30 Days",
10548
+ month: "This Month"
10549
+ };
10550
+ console.log("");
10551
+ console.log(
10552
+ " " + 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})`) : "")
10553
+ );
10554
+ console.log(" " + line);
10555
+ console.log("");
10556
+ const blockLabel = blocked > 0 ? chalk9.red(`\u{1F6D1} ${num(blocked)} blocked`) : chalk9.dim("\u{1F6D1} 0 blocked");
10557
+ const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
10558
+ const loopLabel = loopHits > 0 ? chalk9.yellow(`\u{1F504} ${loopHits} loops`) : chalk9.dim("\u{1F504} 0 loops");
10559
+ const currentRate = total > 0 ? blocked / total : 0;
10560
+ const trendLabel = (() => {
10561
+ if (priorBlockRate === null) return chalk9.dim(`${pct(blocked, total)} block rate`);
10562
+ const delta = Math.round((currentRate - priorBlockRate) * 100);
10563
+ const arrow = delta > 0 ? chalk9.red(`\u25B2${delta}%`) : delta < 0 ? chalk9.green(`\u25BC${Math.abs(delta)}%`) : chalk9.dim("\u2013");
10564
+ return chalk9.dim(`${pct(blocked, total)} block rate `) + arrow + chalk9.dim(" vs prior");
10565
+ })();
10566
+ const reads = toolMap.get("Read")?.calls ?? 0;
10567
+ const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
10568
+ const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
10569
+ const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
10570
+ console.log(
10571
+ " " + chalk9.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
10572
+ );
10573
+ console.log(" " + ratioLabel + " " + testLabel);
10574
+ console.log("");
10575
+ const toolHeaderRaw = "Top Tools";
10576
+ const blockHeaderRaw = "Top Blocks";
10577
+ console.log(
10578
+ " " + chalk9.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + chalk9.bold(blockHeaderRaw)
10579
+ );
10580
+ console.log(" " + chalk9.dim("\u2500".repeat(COL)) + " " + chalk9.dim("\u2500".repeat(COL)));
10581
+ const rows = Math.max(topTools.length, topBlocks.length, 1);
10582
+ for (let i = 0; i < rows; i++) {
10583
+ let leftStyled = " ".repeat(COL);
10584
+ if (i < topTools.length) {
10585
+ const [tool, { calls }] = topTools[i];
10586
+ const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
10587
+ const countStr = num(calls).padStart(TOOL_COUNT_W);
10588
+ const b = colorBar(calls, maxTool, BAR);
10589
+ const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
10590
+ const pad = Math.max(0, COL - rawLen);
10591
+ leftStyled = chalk9.white(label.padEnd(LABEL)) + b + " " + chalk9.white(countStr) + " ".repeat(pad);
10592
+ }
10593
+ let rightStyled = "";
10594
+ if (i < topBlocks.length) {
10595
+ const [reason, count] = topBlocks[i];
10596
+ const label = reason.length > LABEL - 1 ? reason.slice(0, LABEL - 2) + "\u2026" : reason;
10597
+ const countStr = num(count).padStart(BLOCK_COUNT_W);
10598
+ const b = colorBar(count, maxBlock, BAR);
10599
+ rightStyled = chalk9.white(label.padEnd(LABEL)) + b + " " + chalk9.red(countStr);
10600
+ }
10601
+ console.log(" " + leftStyled + " " + rightStyled);
10602
+ }
10603
+ if (topBlocks.length === 0) {
10604
+ console.log(" " + " ".repeat(COL) + " " + chalk9.dim("nothing blocked \u2713"));
10605
+ }
10606
+ if (agentMap.size > 1) {
10607
+ console.log("");
10608
+ console.log(" " + chalk9.bold("Agents"));
10609
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10610
+ const maxAgent = Math.max(...agentMap.values(), 1);
10611
+ for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
10612
+ const label = agent.slice(0, LABEL - 1);
10613
+ const b = colorBar(count, maxAgent, BAR);
10614
+ console.log(" " + chalk9.white(label.padEnd(LABEL)) + b + " " + chalk9.white(num(count)));
10615
+ }
10616
+ }
10617
+ if (mcpMap.size > 0) {
10618
+ console.log("");
10619
+ console.log(" " + chalk9.bold("MCP Servers"));
10620
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10621
+ const maxMcp = Math.max(...mcpMap.values(), 1);
10622
+ for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
10623
+ const label = server.slice(0, LABEL - 1).padEnd(LABEL);
10624
+ const b = colorBar(count, maxMcp, BAR);
10625
+ console.log(" " + chalk9.white(label) + b + " " + chalk9.white(num(count)));
10626
+ }
10627
+ }
10628
+ if (hourMap.size > 0) {
10629
+ const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
10630
+ const maxHour = Math.max(...hourMap.values(), 1);
10631
+ const bar = Array.from({ length: 24 }, (_, h) => {
10632
+ const v = hourMap.get(h) ?? 0;
10633
+ return BLOCKS[Math.round(v / maxHour * 8)];
10634
+ }).join("");
10635
+ console.log("");
10636
+ console.log(" " + chalk9.bold("Hour of Day") + chalk9.dim(" (local, 0h \u2013 23h)"));
10637
+ console.log(" " + chalk9.cyan(bar));
10638
+ console.log(" " + chalk9.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
10639
+ }
10640
+ if (dailyList.length > 1) {
10641
+ console.log("");
10642
+ console.log(" " + chalk9.bold("Daily Activity"));
10643
+ console.log(" " + chalk9.dim("\u2500".repeat(W - 2)));
10644
+ const DAY_BAR = Math.max(8, Math.min(30, W - 36));
10645
+ for (const [dateKey, { calls, blocked: db }] of dailyList) {
10646
+ const label = fmtDate(dateKey).padEnd(10);
10647
+ const b = colorBar(calls, maxDaily, DAY_BAR);
10648
+ const dayCost = costByDay.get(dateKey);
10649
+ const costNote = dayCost ? chalk9.magenta(` ${fmtCost(dayCost)}`) : "";
10650
+ const blockNote = db > 0 ? chalk9.red(` ${db} blocked`) : "";
10651
+ console.log(
10652
+ " " + chalk9.dim(label) + " " + b + " " + chalk9.white(num(calls)) + blockNote + costNote
10653
+ );
10654
+ }
10655
+ }
10656
+ if (costUSD > 0) {
10657
+ const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
10658
+ const avgPerDay = costUSD / periodDays;
10659
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
10660
+ const costHeaderRight = [
10661
+ chalk9.yellow(fmtCost(costUSD)),
10662
+ chalk9.dim(`avg ${fmtCost(avgPerDay)}/day`),
10663
+ cacheHitPct > 0 ? chalk9.dim(`${cacheHitPct}% cache hit`) : null
10664
+ ].filter(Boolean).join(chalk9.dim(" \xB7 "));
10665
+ console.log("");
10666
+ console.log(" " + chalk9.bold("Cost") + " " + costHeaderRight);
10667
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
10668
+ const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
10669
+ const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
10670
+ const MODEL_LABEL = 22;
10671
+ const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
10672
+ for (const [model, cost] of modelList) {
10673
+ const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
10674
+ const b = colorBar(cost, maxModelCost, MODEL_BAR);
10675
+ console.log(
10676
+ " " + chalk9.white(label.padEnd(MODEL_LABEL)) + b + " " + chalk9.yellow(fmtCost(cost))
10677
+ );
10678
+ }
10679
+ }
10680
+ console.log("");
10681
+ console.log(
10682
+ " " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
10683
+ );
10684
+ console.log("");
10685
+ });
10686
+ }
10687
+
9942
10688
  // src/cli/commands/daemon-cmd.ts
9943
10689
  init_daemon2();
9944
10690
  init_daemon();
9945
- import chalk9 from "chalk";
10691
+ import chalk10 from "chalk";
9946
10692
  import { spawn as spawn7 } from "child_process";
9947
10693
  function registerDaemonCommand(program2) {
9948
10694
  program2.command("daemon").description("Run the local approval server").argument("[action]", "start | stop | status (default: start)").option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
@@ -9955,7 +10701,7 @@ function registerDaemonCommand(program2) {
9955
10701
  if (cmd === "status") return daemonStatus();
9956
10702
  if (cmd !== "start" && action !== void 0) {
9957
10703
  console.error(
9958
- chalk9.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10704
+ chalk10.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
9959
10705
  );
9960
10706
  process.exit(1);
9961
10707
  }
@@ -9963,7 +10709,7 @@ function registerDaemonCommand(program2) {
9963
10709
  process.env.NODE9_WATCH_MODE = "1";
9964
10710
  setTimeout(() => {
9965
10711
  openBrowserLocal();
9966
- console.log(chalk9.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10712
+ console.log(chalk10.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9967
10713
  }, 600);
9968
10714
  startDaemon();
9969
10715
  return;
@@ -9971,7 +10717,7 @@ function registerDaemonCommand(program2) {
9971
10717
  if (options.openui) {
9972
10718
  if (isDaemonRunning()) {
9973
10719
  openBrowserLocal();
9974
- console.log(chalk9.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10720
+ console.log(chalk10.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9975
10721
  process.exit(0);
9976
10722
  }
9977
10723
  const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
@@ -9984,7 +10730,7 @@ function registerDaemonCommand(program2) {
9984
10730
  if (isDaemonRunning()) break;
9985
10731
  }
9986
10732
  openBrowserLocal();
9987
- console.log(chalk9.green(`
10733
+ console.log(chalk10.green(`
9988
10734
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
9989
10735
  process.exit(0);
9990
10736
  }
@@ -9994,7 +10740,7 @@ function registerDaemonCommand(program2) {
9994
10740
  stdio: "ignore"
9995
10741
  });
9996
10742
  child.unref();
9997
- console.log(chalk9.green(`
10743
+ console.log(chalk10.green(`
9998
10744
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
9999
10745
  process.exit(0);
10000
10746
  }
@@ -10006,13 +10752,13 @@ function registerDaemonCommand(program2) {
10006
10752
  // src/cli/commands/status.ts
10007
10753
  init_core();
10008
10754
  init_daemon();
10009
- import chalk10 from "chalk";
10010
- import fs22 from "fs";
10011
- import path24 from "path";
10012
- import os18 from "os";
10755
+ import chalk11 from "chalk";
10756
+ import fs25 from "fs";
10757
+ import path27 from "path";
10758
+ import os21 from "os";
10013
10759
  function readJson2(filePath) {
10014
10760
  try {
10015
- if (fs22.existsSync(filePath)) return JSON.parse(fs22.readFileSync(filePath, "utf-8"));
10761
+ if (fs25.existsSync(filePath)) return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
10016
10762
  } catch {
10017
10763
  }
10018
10764
  return null;
@@ -10026,21 +10772,21 @@ function wrappedMcpServers(servers) {
10026
10772
  return Object.entries(servers).filter(([, s]) => s.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
10027
10773
  }
10028
10774
  function printAgentSection(label, hookPairs, wrapped) {
10029
- console.log(chalk10.bold(` ${label}`));
10775
+ console.log(chalk11.bold(` ${label}`));
10030
10776
  for (const { name, present } of hookPairs) {
10031
10777
  if (present) {
10032
- console.log(chalk10.green(` \u2713 ${name}`));
10778
+ console.log(chalk11.green(` \u2713 ${name}`));
10033
10779
  } else {
10034
- console.log(chalk10.red(` \u2717 ${name}`) + chalk10.gray(" (not wired)"));
10780
+ console.log(chalk11.red(` \u2717 ${name}`) + chalk11.gray(" (not wired)"));
10035
10781
  }
10036
10782
  }
10037
10783
  if (wrapped.length > 0) {
10038
- console.log(chalk10.cyan(` MCP proxied:`));
10784
+ console.log(chalk11.cyan(` MCP proxied:`));
10039
10785
  for (const entry of wrapped) {
10040
- console.log(chalk10.gray(` \u2022 ${entry}`));
10786
+ console.log(chalk11.gray(` \u2022 ${entry}`));
10041
10787
  }
10042
10788
  } else {
10043
- console.log(chalk10.gray(` MCP proxied: none`));
10789
+ console.log(chalk11.gray(` MCP proxied: none`));
10044
10790
  }
10045
10791
  }
10046
10792
  function registerStatusCommand(program2) {
@@ -10051,58 +10797,58 @@ function registerStatusCommand(program2) {
10051
10797
  const settings = mergedConfig.settings;
10052
10798
  console.log("");
10053
10799
  if (creds && settings.approvers.cloud) {
10054
- console.log(chalk10.green(" \u25CF Agent mode") + chalk10.gray(" \u2014 cloud team policy enforced"));
10800
+ console.log(chalk11.green(" \u25CF Agent mode") + chalk11.gray(" \u2014 cloud team policy enforced"));
10055
10801
  } else if (creds && !settings.approvers.cloud) {
10056
10802
  console.log(
10057
- chalk10.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk10.gray(" \u2014 all decisions stay on this machine")
10803
+ chalk11.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk11.gray(" \u2014 all decisions stay on this machine")
10058
10804
  );
10059
10805
  } else {
10060
10806
  console.log(
10061
- chalk10.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk10.gray(" \u2014 no API key (Local rules only)")
10807
+ chalk11.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk11.gray(" \u2014 no API key (Local rules only)")
10062
10808
  );
10063
10809
  }
10064
10810
  console.log("");
10065
10811
  if (daemonRunning) {
10066
10812
  console.log(
10067
- chalk10.green(" \u25CF Daemon running") + chalk10.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10813
+ chalk11.green(" \u25CF Daemon running") + chalk11.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10068
10814
  );
10069
10815
  } else {
10070
- console.log(chalk10.gray(" \u25CB Daemon stopped"));
10816
+ console.log(chalk11.gray(" \u25CB Daemon stopped"));
10071
10817
  }
10072
10818
  if (settings.enableUndo) {
10073
10819
  console.log(
10074
- chalk10.magenta(" \u25CF Undo Engine") + chalk10.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10820
+ chalk11.magenta(" \u25CF Undo Engine") + chalk11.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10075
10821
  );
10076
10822
  }
10077
10823
  console.log("");
10078
- const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
10824
+ const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
10079
10825
  console.log(` Mode: ${modeLabel}`);
10080
- const projectConfig = path24.join(process.cwd(), "node9.config.json");
10081
- const globalConfig = path24.join(os18.homedir(), ".node9", "config.json");
10826
+ const projectConfig = path27.join(process.cwd(), "node9.config.json");
10827
+ const globalConfig = path27.join(os21.homedir(), ".node9", "config.json");
10082
10828
  console.log(
10083
- ` Local: ${fs22.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
10829
+ ` Local: ${fs25.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
10084
10830
  );
10085
10831
  console.log(
10086
- ` Global: ${fs22.existsSync(globalConfig) ? chalk10.green("Active (~/.node9/config.json)") : chalk10.gray("Not present")}`
10832
+ ` Global: ${fs25.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
10087
10833
  );
10088
10834
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10089
10835
  console.log(
10090
- ` Sandbox: ${chalk10.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10836
+ ` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10091
10837
  );
10092
10838
  }
10093
- const homeDir2 = os18.homedir();
10839
+ const homeDir2 = os21.homedir();
10094
10840
  const claudeSettings = readJson2(
10095
- path24.join(homeDir2, ".claude", "settings.json")
10841
+ path27.join(homeDir2, ".claude", "settings.json")
10096
10842
  );
10097
- const claudeConfig = readJson2(path24.join(homeDir2, ".claude.json"));
10843
+ const claudeConfig = readJson2(path27.join(homeDir2, ".claude.json"));
10098
10844
  const geminiSettings = readJson2(
10099
- path24.join(homeDir2, ".gemini", "settings.json")
10845
+ path27.join(homeDir2, ".gemini", "settings.json")
10100
10846
  );
10101
- const cursorConfig = readJson2(path24.join(homeDir2, ".cursor", "mcp.json"));
10847
+ const cursorConfig = readJson2(path27.join(homeDir2, ".cursor", "mcp.json"));
10102
10848
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10103
10849
  if (agentFound) {
10104
10850
  console.log("");
10105
- console.log(chalk10.bold(" Agent Wiring:"));
10851
+ console.log(chalk11.bold(" Agent Wiring:"));
10106
10852
  console.log("");
10107
10853
  if (claudeSettings || claudeConfig) {
10108
10854
  const preHook = claudeSettings?.hooks?.PreToolUse?.some(
@@ -10148,7 +10894,7 @@ function registerStatusCommand(program2) {
10148
10894
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
10149
10895
  console.log("");
10150
10896
  console.log(
10151
- chalk10.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk10.gray(" \u2014 all tool calls allowed")
10897
+ chalk11.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk11.gray(" \u2014 all tool calls allowed")
10152
10898
  );
10153
10899
  }
10154
10900
  console.log("");
@@ -10157,10 +10903,10 @@ function registerStatusCommand(program2) {
10157
10903
 
10158
10904
  // src/cli/commands/init.ts
10159
10905
  init_core();
10160
- import chalk11 from "chalk";
10161
- import fs23 from "fs";
10162
- import path25 from "path";
10163
- import os19 from "os";
10906
+ import chalk12 from "chalk";
10907
+ import fs26 from "fs";
10908
+ import path28 from "path";
10909
+ import os22 from "os";
10164
10910
  import https2 from "https";
10165
10911
  init_shields();
10166
10912
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
@@ -10195,7 +10941,7 @@ function fireTelemetryPing(agents) {
10195
10941
  }
10196
10942
  function registerInitCommand(program2) {
10197
10943
  program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
10198
- console.log(chalk11.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10944
+ console.log(chalk12.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10199
10945
  let chosenMode = options.mode.toLowerCase();
10200
10946
  if (!["standard", "strict", "audit"].includes(chosenMode)) {
10201
10947
  chosenMode = DEFAULT_CONFIG.settings.mode;
@@ -10214,37 +10960,37 @@ function registerInitCommand(program2) {
10214
10960
  const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
10215
10961
  if (hasNewShields) writeActiveShields(merged);
10216
10962
  } catch (err2) {
10217
- console.log(chalk11.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10963
+ console.log(chalk12.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10218
10964
  }
10219
10965
  }
10220
10966
  console.log("");
10221
10967
  }
10222
- const configPath = path25.join(os19.homedir(), ".node9", "config.json");
10223
- if (fs23.existsSync(configPath) && !options.force) {
10968
+ const configPath = path28.join(os22.homedir(), ".node9", "config.json");
10969
+ if (fs26.existsSync(configPath) && !options.force) {
10224
10970
  try {
10225
- const existing = JSON.parse(fs23.readFileSync(configPath, "utf-8"));
10971
+ const existing = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
10226
10972
  const settings = existing.settings ?? {};
10227
10973
  if (settings.mode !== chosenMode) {
10228
10974
  settings.mode = chosenMode;
10229
10975
  existing.settings = settings;
10230
- fs23.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10231
- console.log(chalk11.green(`\u2705 Mode updated: ${chosenMode}`));
10976
+ fs26.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10977
+ console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
10232
10978
  } else {
10233
- console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10979
+ console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10234
10980
  }
10235
10981
  } catch {
10236
- console.log(chalk11.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10982
+ console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10237
10983
  }
10238
10984
  } else {
10239
10985
  const configToSave = {
10240
10986
  ...DEFAULT_CONFIG,
10241
10987
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10242
10988
  };
10243
- const dir = path25.dirname(configPath);
10244
- if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
10245
- fs23.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10246
- console.log(chalk11.green(`\u2705 Config created: ${configPath}`));
10247
- console.log(chalk11.gray(` Mode: ${chosenMode}`));
10989
+ const dir = path28.dirname(configPath);
10990
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
10991
+ fs26.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10992
+ console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
10993
+ console.log(chalk12.gray(` Mode: ${chosenMode}`));
10248
10994
  }
10249
10995
  if (options.skipSetup) return;
10250
10996
  console.log("");
@@ -10254,18 +11000,18 @@ function registerInitCommand(program2) {
10254
11000
  );
10255
11001
  if (found.length === 0) {
10256
11002
  console.log(
10257
- chalk11.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
11003
+ chalk12.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
10258
11004
  );
10259
- console.log(chalk11.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
11005
+ console.log(chalk12.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
10260
11006
  return;
10261
11007
  }
10262
- console.log(chalk11.bold("Detected agents:"));
11008
+ console.log(chalk12.bold("Detected agents:"));
10263
11009
  for (const agent of found) {
10264
- console.log(chalk11.green(` \u2713 ${agent}`));
11010
+ console.log(chalk12.green(` \u2713 ${agent}`));
10265
11011
  }
10266
11012
  console.log("");
10267
11013
  for (const agent of found) {
10268
- console.log(chalk11.bold(`Wiring ${agent}...`));
11014
+ console.log(chalk12.bold(`Wiring ${agent}...`));
10269
11015
  if (agent === "claude") await setupClaude();
10270
11016
  else if (agent === "gemini") await setupGemini();
10271
11017
  else if (agent === "cursor") await setupCursor();
@@ -10282,26 +11028,26 @@ function registerInitCommand(program2) {
10282
11028
  console.log("");
10283
11029
  }
10284
11030
  const agentList = found.join(", ");
10285
- console.log(chalk11.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
11031
+ console.log(chalk12.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
10286
11032
  console.log("");
10287
- console.log(chalk11.white(" Watch live: ") + chalk11.cyan("node9 tail"));
10288
- console.log(chalk11.white(" Local UI: ") + chalk11.cyan("node9 daemon --openui"));
11033
+ console.log(chalk12.white(" Watch live: ") + chalk12.cyan("node9 tail"));
11034
+ console.log(chalk12.white(" Local UI: ") + chalk12.cyan("node9 daemon --openui"));
10289
11035
  console.log("");
10290
- console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11036
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10291
11037
  console.log(
10292
- chalk11.white(" Team dashboard + full audit trail \u2192 ") + chalk11.cyan.bold("https://node9.ai")
11038
+ chalk12.white(" Team dashboard + full audit trail \u2192 ") + chalk12.cyan.bold("https://node9.ai")
10293
11039
  );
10294
- console.log(chalk11.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11040
+ console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10295
11041
  });
10296
11042
  }
10297
11043
 
10298
11044
  // src/cli/commands/undo.ts
10299
- import path26 from "path";
10300
- import chalk13 from "chalk";
11045
+ import path29 from "path";
11046
+ import chalk14 from "chalk";
10301
11047
 
10302
11048
  // src/tui/undo-navigator.ts
10303
11049
  import readline2 from "readline";
10304
- import chalk12 from "chalk";
11050
+ import chalk13 from "chalk";
10305
11051
  var RESET = "\x1B[0m";
10306
11052
  var BOLD = "\x1B[1m";
10307
11053
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
@@ -10319,15 +11065,15 @@ function renderDiff(raw) {
10319
11065
  );
10320
11066
  for (const line of lines) {
10321
11067
  if (line.startsWith("+++") || line.startsWith("---")) {
10322
- process.stdout.write(chalk12.bold(line) + "\n");
11068
+ process.stdout.write(chalk13.bold(line) + "\n");
10323
11069
  } else if (line.startsWith("+")) {
10324
- process.stdout.write(chalk12.green(line) + "\n");
11070
+ process.stdout.write(chalk13.green(line) + "\n");
10325
11071
  } else if (line.startsWith("-")) {
10326
- process.stdout.write(chalk12.red(line) + "\n");
11072
+ process.stdout.write(chalk13.red(line) + "\n");
10327
11073
  } else if (line.startsWith("@@")) {
10328
- process.stdout.write(chalk12.cyan(line) + "\n");
11074
+ process.stdout.write(chalk13.cyan(line) + "\n");
10329
11075
  } else {
10330
- process.stdout.write(chalk12.gray(line) + "\n");
11076
+ process.stdout.write(chalk13.gray(line) + "\n");
10331
11077
  }
10332
11078
  }
10333
11079
  }
@@ -10346,23 +11092,23 @@ function render(entries, idx) {
10346
11092
  const step = idx + 1;
10347
11093
  process.stdout.write(CLEAR_SCREEN);
10348
11094
  process.stdout.write(
10349
- chalk12.magenta.bold(`\u23EA Node9 Undo`) + chalk12.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? chalk12.gray(
11095
+ chalk13.magenta.bold(`\u23EA Node9 Undo`) + chalk13.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? chalk13.gray(
10350
11096
  ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
10351
11097
  ) : "") + "\n\n"
10352
11098
  );
10353
11099
  process.stdout.write(
10354
- ` ${BOLD}Tool:${RESET} ${chalk12.cyan(entry.tool)}` + (entry.argsSummary ? chalk12.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
11100
+ ` ${BOLD}Tool:${RESET} ${chalk13.cyan(entry.tool)}` + (entry.argsSummary ? chalk13.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
10355
11101
  );
10356
- process.stdout.write(` ${BOLD}When:${RESET} ${chalk12.gray(formatAge(entry.timestamp))}
11102
+ process.stdout.write(` ${BOLD}When:${RESET} ${chalk13.gray(formatAge(entry.timestamp))}
10357
11103
  `);
10358
- process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk12.gray(entry.cwd)}
11104
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${chalk13.gray(entry.cwd)}
10359
11105
  `);
10360
11106
  if (entry.files && entry.files.length > 0) {
10361
- process.stdout.write(` ${BOLD}Files:${RESET} ${chalk12.gray(entry.files.join(", "))}
11107
+ process.stdout.write(` ${BOLD}Files:${RESET} ${chalk13.gray(entry.files.join(", "))}
10362
11108
  `);
10363
11109
  }
10364
11110
  if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
10365
- process.stdout.write(chalk12.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
11111
+ process.stdout.write(chalk13.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
10366
11112
  }
10367
11113
  process.stdout.write("\n");
10368
11114
  const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
@@ -10370,12 +11116,12 @@ function render(entries, idx) {
10370
11116
  renderDiff(diff);
10371
11117
  } else {
10372
11118
  process.stdout.write(
10373
- chalk12.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
11119
+ chalk13.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
10374
11120
  );
10375
11121
  }
10376
11122
  process.stdout.write("\n");
10377
11123
  process.stdout.write(
10378
- chalk12.gray(" ") + (idx < total - 1 ? chalk12.white("[\u2190] older") : chalk12.gray("[\u2190] older")) + chalk12.gray(" ") + (idx > 0 ? chalk12.white("[\u2192] newer") : chalk12.gray("[\u2192] newer")) + chalk12.gray(" ") + chalk12.green("[\u21B5] restore here") + chalk12.gray(" ") + chalk12.yellow("[s] session start") + chalk12.gray(" ") + chalk12.gray("[q] quit") + "\n"
11124
+ chalk13.gray(" ") + (idx < total - 1 ? chalk13.white("[\u2190] older") : chalk13.gray("[\u2190] older")) + chalk13.gray(" ") + (idx > 0 ? chalk13.white("[\u2192] newer") : chalk13.gray("[\u2192] newer")) + chalk13.gray(" ") + chalk13.green("[\u21B5] restore here") + chalk13.gray(" ") + chalk13.yellow("[s] session start") + chalk13.gray(" ") + chalk13.gray("[q] quit") + "\n"
10379
11125
  );
10380
11126
  }
10381
11127
  async function runUndoNavigator(entries) {
@@ -10429,19 +11175,19 @@ async function runUndoNavigator(entries) {
10429
11175
  cleanup();
10430
11176
  process.stdout.write(CLEAR_SCREEN);
10431
11177
  const entry = display[idx];
10432
- process.stdout.write(chalk12.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
11178
+ process.stdout.write(chalk13.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
10433
11179
  if (applyUndo(entry.hash, entry.cwd)) {
10434
- process.stdout.write(chalk12.green("\u2705 Reverted successfully.\n\n"));
11180
+ process.stdout.write(chalk13.green("\u2705 Reverted successfully.\n\n"));
10435
11181
  resolve({ restored: true });
10436
11182
  } else {
10437
- process.stdout.write(chalk12.red("\u274C Undo failed.\n\n"));
11183
+ process.stdout.write(chalk13.red("\u274C Undo failed.\n\n"));
10438
11184
  resolve({ restored: false });
10439
11185
  }
10440
11186
  } else if (name === "q" || key?.ctrl && name === "c") {
10441
11187
  done = true;
10442
11188
  cleanup();
10443
11189
  process.stdout.write(CLEAR_SCREEN);
10444
- process.stdout.write(chalk12.gray("\nCancelled.\n\n"));
11190
+ process.stdout.write(chalk13.gray("\nCancelled.\n\n"));
10445
11191
  resolve({ restored: false });
10446
11192
  }
10447
11193
  };
@@ -10455,7 +11201,7 @@ function findMatchingCwd(startDir, history) {
10455
11201
  let dir = startDir;
10456
11202
  while (true) {
10457
11203
  if (cwds.has(dir)) return dir;
10458
- const parent = path26.dirname(dir);
11204
+ const parent = path29.dirname(dir);
10459
11205
  if (parent === dir) return null;
10460
11206
  dir = parent;
10461
11207
  }
@@ -10477,39 +11223,39 @@ function registerUndoCommand(program2) {
10477
11223
  if (history.length === 0) {
10478
11224
  if (!options.all && allHistory.length > 0) {
10479
11225
  console.log(
10480
- chalk13.yellow(
11226
+ chalk14.yellow(
10481
11227
  `
10482
11228
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
10483
- Run ${chalk13.cyan("node9 undo --all")} to see snapshots from all projects.
11229
+ Run ${chalk14.cyan("node9 undo --all")} to see snapshots from all projects.
10484
11230
  `
10485
11231
  )
10486
11232
  );
10487
11233
  } else {
10488
- console.log(chalk13.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
11234
+ console.log(chalk14.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
10489
11235
  }
10490
11236
  return;
10491
11237
  }
10492
11238
  if (options.list) {
10493
- console.log(chalk13.magenta.bold("\n\u23EA Snapshot History\n"));
11239
+ console.log(chalk14.magenta.bold("\n\u23EA Snapshot History\n"));
10494
11240
  console.log(
10495
- chalk13.gray(
11241
+ chalk14.gray(
10496
11242
  ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
10497
11243
  )
10498
11244
  );
10499
- console.log(chalk13.gray(" " + "\u2500".repeat(80)));
11245
+ console.log(chalk14.gray(" " + "\u2500".repeat(80)));
10500
11246
  const display = [...history].reverse();
10501
11247
  let prevTs = null;
10502
11248
  for (let i = 0; i < display.length; i++) {
10503
11249
  const e = display[i];
10504
11250
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
10505
- if (isGap) console.log(chalk13.gray(" \u2500\u2500 earlier \u2500\u2500"));
11251
+ if (isGap) console.log(chalk14.gray(" \u2500\u2500 earlier \u2500\u2500"));
10506
11252
  const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
10507
11253
  const tool = e.tool.slice(0, 8).padEnd(8);
10508
11254
  const when = formatAge2(e.timestamp).padEnd(10);
10509
11255
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
10510
11256
  console.log(
10511
- chalk13.white(
10512
- ` ${String(i + 1).padEnd(3)} ${label} ${chalk13.cyan(tool)} ${chalk13.gray(when)} ${chalk13.gray(dir)}`
11257
+ chalk14.white(
11258
+ ` ${String(i + 1).padEnd(3)} ${label} ${chalk14.cyan(tool)} ${chalk14.gray(when)} ${chalk14.gray(dir)}`
10513
11259
  )
10514
11260
  );
10515
11261
  prevTs = e.timestamp;
@@ -10522,7 +11268,7 @@ function registerUndoCommand(program2) {
10522
11268
  const idx = history.length - steps;
10523
11269
  if (idx < 0) {
10524
11270
  console.log(
10525
- chalk13.yellow(
11271
+ chalk14.yellow(
10526
11272
  `
10527
11273
  \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10528
11274
  `
@@ -10533,47 +11279,47 @@ function registerUndoCommand(program2) {
10533
11279
  const snapshot = history[idx];
10534
11280
  const ageStr = formatAge2(snapshot.timestamp);
10535
11281
  console.log(
10536
- chalk13.magenta.bold(`
11282
+ chalk14.magenta.bold(`
10537
11283
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
10538
11284
  );
10539
11285
  console.log(
10540
- chalk13.white(
10541
- ` Tool: ${chalk13.cyan(snapshot.tool)}${snapshot.argsSummary ? chalk13.gray(" \u2192 " + snapshot.argsSummary) : ""}`
11286
+ chalk14.white(
11287
+ ` Tool: ${chalk14.cyan(snapshot.tool)}${snapshot.argsSummary ? chalk14.gray(" \u2192 " + snapshot.argsSummary) : ""}`
10542
11288
  )
10543
11289
  );
10544
- console.log(chalk13.white(` When: ${chalk13.gray(ageStr)}`));
10545
- console.log(chalk13.white(` Dir: ${chalk13.gray(snapshot.cwd)}`));
11290
+ console.log(chalk14.white(` When: ${chalk14.gray(ageStr)}`));
11291
+ console.log(chalk14.white(` Dir: ${chalk14.gray(snapshot.cwd)}`));
10546
11292
  if (steps > 1)
10547
11293
  console.log(
10548
- chalk13.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
11294
+ chalk14.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10549
11295
  );
10550
11296
  console.log("");
10551
11297
  const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10552
11298
  if (diff) {
10553
11299
  const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10554
11300
  for (const line of lines) {
10555
- if (line.startsWith("+++") || line.startsWith("---")) console.log(chalk13.bold(line));
10556
- else if (line.startsWith("+")) console.log(chalk13.green(line));
10557
- else if (line.startsWith("-")) console.log(chalk13.red(line));
10558
- else if (line.startsWith("@@")) console.log(chalk13.cyan(line));
10559
- else console.log(chalk13.gray(line));
11301
+ if (line.startsWith("+++") || line.startsWith("---")) console.log(chalk14.bold(line));
11302
+ else if (line.startsWith("+")) console.log(chalk14.green(line));
11303
+ else if (line.startsWith("-")) console.log(chalk14.red(line));
11304
+ else if (line.startsWith("@@")) console.log(chalk14.cyan(line));
11305
+ else console.log(chalk14.gray(line));
10560
11306
  }
10561
11307
  console.log("");
10562
11308
  } else {
10563
11309
  console.log(
10564
- chalk13.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
11310
+ chalk14.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
10565
11311
  );
10566
11312
  }
10567
11313
  const { confirm: confirm3 } = await import("@inquirer/prompts");
10568
11314
  const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10569
11315
  if (proceed) {
10570
11316
  if (applyUndo(snapshot.hash, snapshot.cwd)) {
10571
- console.log(chalk13.green("\n\u2705 Reverted successfully.\n"));
11317
+ console.log(chalk14.green("\n\u2705 Reverted successfully.\n"));
10572
11318
  } else {
10573
- console.error(chalk13.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
11319
+ console.error(chalk14.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10574
11320
  }
10575
11321
  } else {
10576
- console.log(chalk13.gray("\nCancelled.\n"));
11322
+ console.log(chalk14.gray("\nCancelled.\n"));
10577
11323
  }
10578
11324
  return;
10579
11325
  }
@@ -10583,7 +11329,7 @@ function registerUndoCommand(program2) {
10583
11329
 
10584
11330
  // src/cli/commands/watch.ts
10585
11331
  init_daemon();
10586
- import chalk14 from "chalk";
11332
+ import chalk15 from "chalk";
10587
11333
  import { spawn as spawn8, spawnSync as spawnSync5 } from "child_process";
10588
11334
  function registerWatchCommand(program2) {
10589
11335
  program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
@@ -10599,7 +11345,7 @@ function registerWatchCommand(program2) {
10599
11345
  throw new Error("not running");
10600
11346
  }
10601
11347
  } catch {
10602
- console.error(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
11348
+ console.error(chalk15.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10603
11349
  const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
10604
11350
  detached: true,
10605
11351
  stdio: "ignore",
@@ -10621,12 +11367,12 @@ function registerWatchCommand(program2) {
10621
11367
  }
10622
11368
  }
10623
11369
  if (!ready) {
10624
- console.error(chalk14.red("\u274C Daemon failed to start. Try: node9 daemon start"));
11370
+ console.error(chalk15.red("\u274C Daemon failed to start. Try: node9 daemon start"));
10625
11371
  process.exit(1);
10626
11372
  }
10627
11373
  }
10628
11374
  console.error(
10629
- chalk14.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk14.dim(` \u2192 localhost:${port}`) + chalk14.dim(
11375
+ chalk15.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk15.dim(` \u2192 localhost:${port}`) + chalk15.dim(
10630
11376
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
10631
11377
  )
10632
11378
  );
@@ -10635,7 +11381,7 @@ function registerWatchCommand(program2) {
10635
11381
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
10636
11382
  });
10637
11383
  if (result.error) {
10638
- console.error(chalk14.red(`\u274C Failed to run command: ${result.error.message}`));
11384
+ console.error(chalk15.red(`\u274C Failed to run command: ${result.error.message}`));
10639
11385
  process.exit(1);
10640
11386
  }
10641
11387
  process.exit(result.status ?? 0);
@@ -10645,18 +11391,18 @@ function registerWatchCommand(program2) {
10645
11391
  // src/mcp-gateway/index.ts
10646
11392
  init_orchestrator();
10647
11393
  import readline3 from "readline";
10648
- import chalk15 from "chalk";
11394
+ import chalk16 from "chalk";
10649
11395
  import { spawn as spawn9 } from "child_process";
10650
11396
  import { execa as execa2 } from "execa";
10651
11397
  init_provenance();
10652
11398
 
10653
11399
  // src/mcp-pin.ts
10654
- import fs24 from "fs";
10655
- import path27 from "path";
10656
- import os20 from "os";
10657
- import crypto3 from "crypto";
11400
+ import fs27 from "fs";
11401
+ import path30 from "path";
11402
+ import os23 from "os";
11403
+ import crypto4 from "crypto";
10658
11404
  function getPinsFilePath() {
10659
- return path27.join(os20.homedir(), ".node9", "mcp-pins.json");
11405
+ return path30.join(os23.homedir(), ".node9", "mcp-pins.json");
10660
11406
  }
10661
11407
  function hashToolDefinitions(tools) {
10662
11408
  const sorted = [...tools].sort((a, b) => {
@@ -10665,15 +11411,15 @@ function hashToolDefinitions(tools) {
10665
11411
  return nameA.localeCompare(nameB);
10666
11412
  });
10667
11413
  const canonical = JSON.stringify(sorted);
10668
- return crypto3.createHash("sha256").update(canonical).digest("hex");
11414
+ return crypto4.createHash("sha256").update(canonical).digest("hex");
10669
11415
  }
10670
11416
  function getServerKey(upstreamCommand) {
10671
- return crypto3.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
11417
+ return crypto4.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
10672
11418
  }
10673
11419
  function readMcpPinsSafe() {
10674
11420
  const filePath = getPinsFilePath();
10675
11421
  try {
10676
- const raw = fs24.readFileSync(filePath, "utf-8");
11422
+ const raw = fs27.readFileSync(filePath, "utf-8");
10677
11423
  if (!raw.trim()) {
10678
11424
  return { ok: false, reason: "corrupt", detail: "empty file" };
10679
11425
  }
@@ -10697,10 +11443,10 @@ function readMcpPins() {
10697
11443
  }
10698
11444
  function writeMcpPins(data) {
10699
11445
  const filePath = getPinsFilePath();
10700
- fs24.mkdirSync(path27.dirname(filePath), { recursive: true });
10701
- const tmp = `${filePath}.${crypto3.randomBytes(6).toString("hex")}.tmp`;
10702
- fs24.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10703
- fs24.renameSync(tmp, filePath);
11446
+ fs27.mkdirSync(path30.dirname(filePath), { recursive: true });
11447
+ const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
11448
+ fs27.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11449
+ fs27.renameSync(tmp, filePath);
10704
11450
  }
10705
11451
  function checkPin(serverKey, currentHash) {
10706
11452
  const result = readMcpPinsSafe();
@@ -10792,13 +11538,13 @@ async function runMcpGateway(upstreamCommand) {
10792
11538
  const prov = checkProvenance(executable);
10793
11539
  if (prov.trustLevel === "suspect") {
10794
11540
  console.error(
10795
- chalk15.red(
11541
+ chalk16.red(
10796
11542
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
10797
11543
  )
10798
11544
  );
10799
- console.error(chalk15.red(" Verify this binary is trusted before proceeding."));
11545
+ console.error(chalk16.red(" Verify this binary is trusted before proceeding."));
10800
11546
  }
10801
- console.error(chalk15.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
11547
+ console.error(chalk16.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10802
11548
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
10803
11549
  "NODE_OPTIONS",
10804
11550
  "NODE_PATH",
@@ -10901,10 +11647,10 @@ async function runMcpGateway(upstreamCommand) {
10901
11647
  mcpServer
10902
11648
  });
10903
11649
  if (!result.approved) {
10904
- console.error(chalk15.red(`
11650
+ console.error(chalk16.red(`
10905
11651
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
10906
- console.error(chalk15.gray(` Tool: ${toolName}`));
10907
- console.error(chalk15.gray(` Reason: ${result.reason ?? "Security Policy"}
11652
+ console.error(chalk16.gray(` Tool: ${toolName}`));
11653
+ console.error(chalk16.gray(` Reason: ${result.reason ?? "Security Policy"}
10908
11654
  `));
10909
11655
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
10910
11656
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -10983,7 +11729,7 @@ async function runMcpGateway(upstreamCommand) {
10983
11729
  updatePin(serverKey, upstreamCommand, currentHash, toolNames);
10984
11730
  pinState = "validated";
10985
11731
  console.error(
10986
- chalk15.green(
11732
+ chalk16.green(
10987
11733
  `\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
10988
11734
  )
10989
11735
  );
@@ -10996,11 +11742,11 @@ async function runMcpGateway(upstreamCommand) {
10996
11742
  } else if (pinStatus === "corrupt") {
10997
11743
  pinState = "quarantined";
10998
11744
  console.error(
10999
- chalk15.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11745
+ chalk16.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11000
11746
  );
11001
- console.error(chalk15.red(" Tool calls are blocked until the pin file is repaired."));
11747
+ console.error(chalk16.red(" Tool calls are blocked until the pin file is repaired."));
11002
11748
  console.error(
11003
- chalk15.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11749
+ chalk16.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11004
11750
  `)
11005
11751
  );
11006
11752
  const errorResponse = {
@@ -11017,13 +11763,13 @@ async function runMcpGateway(upstreamCommand) {
11017
11763
  } else {
11018
11764
  pinState = "quarantined";
11019
11765
  console.error(
11020
- chalk15.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11766
+ chalk16.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11021
11767
  );
11022
11768
  console.error(
11023
- chalk15.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11769
+ chalk16.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11024
11770
  );
11025
- console.error(chalk15.red(" Session quarantined \u2014 all tool calls blocked."));
11026
- console.error(chalk15.yellow(` Run: node9 mcp pin update ${serverKey}
11771
+ console.error(chalk16.red(" Session quarantined \u2014 all tool calls blocked."));
11772
+ console.error(chalk16.yellow(` Run: node9 mcp pin update ${serverKey}
11027
11773
  `));
11028
11774
  const errorResponse = {
11029
11775
  jsonrpc: "2.0",
@@ -11072,9 +11818,9 @@ function registerMcpGatewayCommand(program2) {
11072
11818
 
11073
11819
  // src/mcp-server/index.ts
11074
11820
  import readline4 from "readline";
11075
- import fs25 from "fs";
11076
- import os21 from "os";
11077
- import path28 from "path";
11821
+ import fs28 from "fs";
11822
+ import os24 from "os";
11823
+ import path31 from "path";
11078
11824
  init_core();
11079
11825
  init_daemon();
11080
11826
  init_shields();
@@ -11249,13 +11995,13 @@ function handleStatus() {
11249
11995
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11250
11996
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11251
11997
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11252
- const projectConfig = path28.join(process.cwd(), "node9.config.json");
11253
- const globalConfig = path28.join(os21.homedir(), ".node9", "config.json");
11998
+ const projectConfig = path31.join(process.cwd(), "node9.config.json");
11999
+ const globalConfig = path31.join(os24.homedir(), ".node9", "config.json");
11254
12000
  lines.push(
11255
- `Project config (node9.config.json): ${fs25.existsSync(projectConfig) ? "present" : "not found"}`
12001
+ `Project config (node9.config.json): ${fs28.existsSync(projectConfig) ? "present" : "not found"}`
11256
12002
  );
11257
12003
  lines.push(
11258
- `Global config (~/.node9/config.json): ${fs25.existsSync(globalConfig) ? "present" : "not found"}`
12004
+ `Global config (~/.node9/config.json): ${fs28.existsSync(globalConfig) ? "present" : "not found"}`
11259
12005
  );
11260
12006
  return lines.join("\n");
11261
12007
  }
@@ -11329,21 +12075,21 @@ function handleShieldDisable(args) {
11329
12075
  writeActiveShields(active.filter((s) => s !== name));
11330
12076
  return `Shield "${name}" disabled.`;
11331
12077
  }
11332
- var GLOBAL_CONFIG_PATH2 = path28.join(os21.homedir(), ".node9", "config.json");
12078
+ var GLOBAL_CONFIG_PATH2 = path31.join(os24.homedir(), ".node9", "config.json");
11333
12079
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11334
12080
  function readGlobalConfigRaw() {
11335
12081
  try {
11336
- if (fs25.existsSync(GLOBAL_CONFIG_PATH2)) {
11337
- return JSON.parse(fs25.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12082
+ if (fs28.existsSync(GLOBAL_CONFIG_PATH2)) {
12083
+ return JSON.parse(fs28.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11338
12084
  }
11339
12085
  } catch {
11340
12086
  }
11341
12087
  return {};
11342
12088
  }
11343
12089
  function writeGlobalConfigRaw(data) {
11344
- const dir = path28.dirname(GLOBAL_CONFIG_PATH2);
11345
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
11346
- fs25.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12090
+ const dir = path31.dirname(GLOBAL_CONFIG_PATH2);
12091
+ if (!fs28.existsSync(dir)) fs28.mkdirSync(dir, { recursive: true });
12092
+ fs28.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11347
12093
  }
11348
12094
  function handleApproverList() {
11349
12095
  const config = getConfig();
@@ -11386,9 +12132,9 @@ function handleApproverSet(args) {
11386
12132
  }
11387
12133
  function handleAuditGet(args) {
11388
12134
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11389
- const auditPath = path28.join(os21.homedir(), ".node9", "audit.log");
11390
- if (!fs25.existsSync(auditPath)) return "No audit log found.";
11391
- const lines = fs25.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
12135
+ const auditPath = path31.join(os24.homedir(), ".node9", "audit.log");
12136
+ if (!fs28.existsSync(auditPath)) return "No audit log found.";
12137
+ const lines = fs28.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11392
12138
  const recent = lines.slice(-limit);
11393
12139
  const entries = recent.map((line) => {
11394
12140
  try {
@@ -11576,7 +12322,7 @@ function registerMcpServerCommand(program2) {
11576
12322
 
11577
12323
  // src/cli/commands/trust.ts
11578
12324
  init_trusted_hosts();
11579
- import chalk16 from "chalk";
12325
+ import chalk17 from "chalk";
11580
12326
  function isValidHost(host) {
11581
12327
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
11582
12328
  }
@@ -11586,51 +12332,51 @@ function registerTrustCommand(program2) {
11586
12332
  const normalized = normalizeHost(host.trim());
11587
12333
  if (!isValidHost(normalized)) {
11588
12334
  console.error(
11589
- chalk16.red(`
12335
+ chalk17.red(`
11590
12336
  \u274C Invalid host: "${host}"
11591
- `) + chalk16.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
12337
+ `) + chalk17.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
11592
12338
  );
11593
12339
  process.exit(1);
11594
12340
  }
11595
12341
  addTrustedHost(normalized);
11596
- console.log(chalk16.green(`
12342
+ console.log(chalk17.green(`
11597
12343
  \u2705 ${normalized} added to trusted hosts.`));
11598
12344
  console.log(
11599
- chalk16.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
12345
+ chalk17.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
11600
12346
  );
11601
12347
  });
11602
12348
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
11603
12349
  const normalized = normalizeHost(host.trim());
11604
12350
  const removed = removeTrustedHost(normalized);
11605
12351
  if (!removed) {
11606
- console.error(chalk16.yellow(`
12352
+ console.error(chalk17.yellow(`
11607
12353
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
11608
12354
  `));
11609
12355
  process.exit(1);
11610
12356
  }
11611
- console.log(chalk16.green(`
12357
+ console.log(chalk17.green(`
11612
12358
  \u2705 ${normalized} removed from trusted hosts.
11613
12359
  `));
11614
12360
  });
11615
12361
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
11616
12362
  const hosts = readTrustedHosts();
11617
12363
  if (hosts.length === 0) {
11618
- console.log(chalk16.gray("\n No trusted hosts configured.\n"));
11619
- console.log(` Add one: ${chalk16.cyan("node9 trust add api.mycompany.com")}
12364
+ console.log(chalk17.gray("\n No trusted hosts configured.\n"));
12365
+ console.log(` Add one: ${chalk17.cyan("node9 trust add api.mycompany.com")}
11620
12366
  `);
11621
12367
  return;
11622
12368
  }
11623
- console.log(chalk16.bold("\n\u{1F513} Trusted Hosts\n"));
12369
+ console.log(chalk17.bold("\n\u{1F513} Trusted Hosts\n"));
11624
12370
  for (const entry of hosts) {
11625
12371
  const date = new Date(entry.addedAt).toLocaleDateString();
11626
- console.log(` ${chalk16.cyan(entry.host.padEnd(40))} ${chalk16.gray(`added ${date}`)}`);
12372
+ console.log(` ${chalk17.cyan(entry.host.padEnd(40))} ${chalk17.gray(`added ${date}`)}`);
11627
12373
  }
11628
12374
  console.log("");
11629
12375
  });
11630
12376
  }
11631
12377
 
11632
12378
  // src/cli/commands/mcp-pin.ts
11633
- import chalk17 from "chalk";
12379
+ import chalk18 from "chalk";
11634
12380
  function registerMcpPinCommand(program2) {
11635
12381
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
11636
12382
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -11638,31 +12384,31 @@ function registerMcpPinCommand(program2) {
11638
12384
  const result = readMcpPinsSafe();
11639
12385
  if (!result.ok) {
11640
12386
  if (result.reason === "missing") {
11641
- console.log(chalk17.gray("\nNo MCP servers are pinned yet."));
12387
+ console.log(chalk18.gray("\nNo MCP servers are pinned yet."));
11642
12388
  console.log(
11643
- chalk17.gray("Pins are created automatically when the MCP gateway first connects.\n")
12389
+ chalk18.gray("Pins are created automatically when the MCP gateway first connects.\n")
11644
12390
  );
11645
12391
  return;
11646
12392
  }
11647
- console.error(chalk17.red(`
12393
+ console.error(chalk18.red(`
11648
12394
  \u274C Pin file is corrupt: ${result.detail}`));
11649
- console.error(chalk17.yellow(" Run: node9 mcp pin reset\n"));
12395
+ console.error(chalk18.yellow(" Run: node9 mcp pin reset\n"));
11650
12396
  process.exit(1);
11651
12397
  }
11652
12398
  const entries = Object.entries(result.pins.servers);
11653
12399
  if (entries.length === 0) {
11654
- console.log(chalk17.gray("\nNo MCP servers are pinned yet."));
12400
+ console.log(chalk18.gray("\nNo MCP servers are pinned yet."));
11655
12401
  console.log(
11656
- chalk17.gray("Pins are created automatically when the MCP gateway first connects.\n")
12402
+ chalk18.gray("Pins are created automatically when the MCP gateway first connects.\n")
11657
12403
  );
11658
12404
  return;
11659
12405
  }
11660
- console.log(chalk17.bold("\n\u{1F512} Pinned MCP Servers\n"));
12406
+ console.log(chalk18.bold("\n\u{1F512} Pinned MCP Servers\n"));
11661
12407
  for (const [key, entry] of entries) {
11662
- console.log(` ${chalk17.cyan(key)} ${chalk17.gray(entry.label)}`);
11663
- console.log(` Tools (${entry.toolCount}): ${chalk17.white(entry.toolNames.join(", "))}`);
11664
- console.log(` Hash: ${chalk17.gray(entry.toolsHash.slice(0, 16))}...`);
11665
- console.log(` Pinned: ${chalk17.gray(entry.pinnedAt)}`);
12408
+ console.log(` ${chalk18.cyan(key)} ${chalk18.gray(entry.label)}`);
12409
+ console.log(` Tools (${entry.toolCount}): ${chalk18.white(entry.toolNames.join(", "))}`);
12410
+ console.log(` Hash: ${chalk18.gray(entry.toolsHash.slice(0, 16))}...`);
12411
+ console.log(` Pinned: ${chalk18.gray(entry.pinnedAt)}`);
11666
12412
  console.log("");
11667
12413
  }
11668
12414
  });
@@ -11673,55 +12419,55 @@ function registerMcpPinCommand(program2) {
11673
12419
  try {
11674
12420
  pins = readMcpPins();
11675
12421
  } catch {
11676
- console.error(chalk17.red("\n\u274C Pin file is corrupt."));
11677
- console.error(chalk17.yellow(" Run: node9 mcp pin reset\n"));
12422
+ console.error(chalk18.red("\n\u274C Pin file is corrupt."));
12423
+ console.error(chalk18.yellow(" Run: node9 mcp pin reset\n"));
11678
12424
  process.exit(1);
11679
12425
  }
11680
12426
  if (!pins.servers[serverKey]) {
11681
- console.error(chalk17.red(`
12427
+ console.error(chalk18.red(`
11682
12428
  \u274C No pin found for server key "${serverKey}"
11683
12429
  `));
11684
- console.error(`Run ${chalk17.cyan("node9 mcp pin list")} to see pinned servers.
12430
+ console.error(`Run ${chalk18.cyan("node9 mcp pin list")} to see pinned servers.
11685
12431
  `);
11686
12432
  process.exit(1);
11687
12433
  }
11688
12434
  const label = pins.servers[serverKey].label;
11689
12435
  removePin(serverKey);
11690
- console.log(chalk17.green(`
11691
- \u{1F513} Pin removed for ${chalk17.cyan(serverKey)}`));
11692
- console.log(chalk17.gray(` Server: ${label}`));
11693
- console.log(chalk17.gray(" Next connection will re-pin with current tool definitions.\n"));
12436
+ console.log(chalk18.green(`
12437
+ \u{1F513} Pin removed for ${chalk18.cyan(serverKey)}`));
12438
+ console.log(chalk18.gray(` Server: ${label}`));
12439
+ console.log(chalk18.gray(" Next connection will re-pin with current tool definitions.\n"));
11694
12440
  });
11695
12441
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
11696
12442
  const result = readMcpPinsSafe();
11697
12443
  if (!result.ok && result.reason === "missing") {
11698
- console.log(chalk17.gray("\nNo pins to clear.\n"));
12444
+ console.log(chalk18.gray("\nNo pins to clear.\n"));
11699
12445
  return;
11700
12446
  }
11701
12447
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
11702
12448
  clearAllPins();
11703
- console.log(chalk17.green(`
12449
+ console.log(chalk18.green(`
11704
12450
  \u{1F513} Cleared ${count} MCP pin(s).`));
11705
- console.log(chalk17.gray(" Next connection to each server will re-pin.\n"));
12451
+ console.log(chalk18.gray(" Next connection to each server will re-pin.\n"));
11706
12452
  });
11707
12453
  }
11708
12454
 
11709
12455
  // src/cli.ts
11710
12456
  var { version } = JSON.parse(
11711
- fs28.readFileSync(path31.join(__dirname, "../package.json"), "utf-8")
12457
+ fs31.readFileSync(path34.join(__dirname, "../package.json"), "utf-8")
11712
12458
  );
11713
12459
  var program = new Command();
11714
12460
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
11715
12461
  program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
11716
12462
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
11717
- const credPath = path31.join(os24.homedir(), ".node9", "credentials.json");
11718
- if (!fs28.existsSync(path31.dirname(credPath)))
11719
- fs28.mkdirSync(path31.dirname(credPath), { recursive: true });
12463
+ const credPath = path34.join(os27.homedir(), ".node9", "credentials.json");
12464
+ if (!fs31.existsSync(path34.dirname(credPath)))
12465
+ fs31.mkdirSync(path34.dirname(credPath), { recursive: true });
11720
12466
  const profileName = options.profile || "default";
11721
12467
  let existingCreds = {};
11722
12468
  try {
11723
- if (fs28.existsSync(credPath)) {
11724
- const raw = JSON.parse(fs28.readFileSync(credPath, "utf-8"));
12469
+ if (fs31.existsSync(credPath)) {
12470
+ const raw = JSON.parse(fs31.readFileSync(credPath, "utf-8"));
11725
12471
  if (raw.apiKey) {
11726
12472
  existingCreds = {
11727
12473
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -11733,13 +12479,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11733
12479
  } catch {
11734
12480
  }
11735
12481
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
11736
- fs28.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12482
+ fs31.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
11737
12483
  if (profileName === "default") {
11738
- const configPath = path31.join(os24.homedir(), ".node9", "config.json");
12484
+ const configPath = path34.join(os27.homedir(), ".node9", "config.json");
11739
12485
  let config = {};
11740
12486
  try {
11741
- if (fs28.existsSync(configPath))
11742
- config = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
12487
+ if (fs31.existsSync(configPath))
12488
+ config = JSON.parse(fs31.readFileSync(configPath, "utf-8"));
11743
12489
  } catch {
11744
12490
  }
11745
12491
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -11754,19 +12500,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11754
12500
  approvers.cloud = false;
11755
12501
  }
11756
12502
  s.approvers = approvers;
11757
- if (!fs28.existsSync(path31.dirname(configPath)))
11758
- fs28.mkdirSync(path31.dirname(configPath), { recursive: true });
11759
- fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12503
+ if (!fs31.existsSync(path34.dirname(configPath)))
12504
+ fs31.mkdirSync(path34.dirname(configPath), { recursive: true });
12505
+ fs31.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
11760
12506
  }
11761
12507
  if (options.profile && profileName !== "default") {
11762
- console.log(chalk19.green(`\u2705 Profile "${profileName}" saved`));
11763
- console.log(chalk19.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
12508
+ console.log(chalk20.green(`\u2705 Profile "${profileName}" saved`));
12509
+ console.log(chalk20.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
11764
12510
  } else if (options.local) {
11765
- console.log(chalk19.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
11766
- console.log(chalk19.gray(` All decisions stay on this machine.`));
12511
+ console.log(chalk20.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
12512
+ console.log(chalk20.gray(` All decisions stay on this machine.`));
11767
12513
  } else {
11768
- console.log(chalk19.green(`\u2705 Logged in \u2014 agent mode`));
11769
- console.log(chalk19.gray(` Team policy enforced for all calls via Node9 cloud.`));
12514
+ console.log(chalk20.green(`\u2705 Logged in \u2014 agent mode`));
12515
+ console.log(chalk20.gray(` Team policy enforced for all calls via Node9 cloud.`));
11770
12516
  }
11771
12517
  });
11772
12518
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
@@ -11774,19 +12520,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
11774
12520
  if (target === "claude") return await setupClaude();
11775
12521
  if (target === "cursor") return await setupCursor();
11776
12522
  if (target === "hud") return setupHud();
11777
- console.error(chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12523
+ console.error(chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11778
12524
  process.exit(1);
11779
12525
  });
11780
12526
  program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
11781
12527
  if (!target) {
11782
- console.log(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
11783
- console.log(" Usage: " + chalk19.white("node9 setup <target>") + "\n");
12528
+ console.log(chalk20.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
12529
+ console.log(" Usage: " + chalk20.white("node9 setup <target>") + "\n");
11784
12530
  console.log(" Targets:");
11785
- console.log(" " + chalk19.green("claude") + " \u2014 Claude Code (hook mode)");
11786
- console.log(" " + chalk19.green("gemini") + " \u2014 Gemini CLI (hook mode)");
11787
- console.log(" " + chalk19.green("cursor") + " \u2014 Cursor (hook mode)");
12531
+ console.log(" " + chalk20.green("claude") + " \u2014 Claude Code (hook mode)");
12532
+ console.log(" " + chalk20.green("gemini") + " \u2014 Gemini CLI (hook mode)");
12533
+ console.log(" " + chalk20.green("cursor") + " \u2014 Cursor (hook mode)");
11788
12534
  process.stdout.write(
11789
- " " + chalk19.green("hud") + " \u2014 Claude Code security statusline\n"
12535
+ " " + chalk20.green("hud") + " \u2014 Claude Code security statusline\n"
11790
12536
  );
11791
12537
  console.log("");
11792
12538
  return;
@@ -11796,7 +12542,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
11796
12542
  if (t === "claude") return await setupClaude();
11797
12543
  if (t === "cursor") return await setupCursor();
11798
12544
  if (t === "hud") return setupHud();
11799
- console.error(chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12545
+ console.error(chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11800
12546
  process.exit(1);
11801
12547
  });
11802
12548
  program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
@@ -11807,31 +12553,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
11807
12553
  else if (target === "hud") fn = teardownHud;
11808
12554
  else {
11809
12555
  console.error(
11810
- chalk19.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
12556
+ chalk20.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
11811
12557
  );
11812
12558
  process.exit(1);
11813
12559
  }
11814
- console.log(chalk19.cyan(`
12560
+ console.log(chalk20.cyan(`
11815
12561
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
11816
12562
  `));
11817
12563
  try {
11818
12564
  fn();
11819
12565
  } catch (err2) {
11820
- console.error(chalk19.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
12566
+ console.error(chalk20.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
11821
12567
  process.exit(1);
11822
12568
  }
11823
- console.log(chalk19.gray("\n Restart the agent for changes to take effect."));
12569
+ console.log(chalk20.gray("\n Restart the agent for changes to take effect."));
11824
12570
  });
11825
12571
  program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
11826
- console.log(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
11827
- console.log(chalk19.bold("Stopping daemon..."));
12572
+ console.log(chalk20.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
12573
+ console.log(chalk20.bold("Stopping daemon..."));
11828
12574
  try {
11829
12575
  stopDaemon();
11830
- console.log(chalk19.green(" \u2705 Daemon stopped"));
12576
+ console.log(chalk20.green(" \u2705 Daemon stopped"));
11831
12577
  } catch {
11832
- console.log(chalk19.blue(" \u2139\uFE0F Daemon was not running"));
12578
+ console.log(chalk20.blue(" \u2139\uFE0F Daemon was not running"));
11833
12579
  }
11834
- console.log(chalk19.bold("\nRemoving hooks..."));
12580
+ console.log(chalk20.bold("\nRemoving hooks..."));
11835
12581
  let teardownFailed = false;
11836
12582
  for (const [label, fn] of [
11837
12583
  ["Claude", teardownClaude],
@@ -11843,45 +12589,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
11843
12589
  } catch (err2) {
11844
12590
  teardownFailed = true;
11845
12591
  console.error(
11846
- chalk19.red(
12592
+ chalk20.red(
11847
12593
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
11848
12594
  )
11849
12595
  );
11850
12596
  }
11851
12597
  }
11852
12598
  if (options.purge) {
11853
- const node9Dir = path31.join(os24.homedir(), ".node9");
11854
- if (fs28.existsSync(node9Dir)) {
12599
+ const node9Dir = path34.join(os27.homedir(), ".node9");
12600
+ if (fs31.existsSync(node9Dir)) {
11855
12601
  const confirmed = await confirm2({
11856
12602
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
11857
12603
  default: false
11858
12604
  });
11859
12605
  if (confirmed) {
11860
- fs28.rmSync(node9Dir, { recursive: true });
11861
- if (fs28.existsSync(node9Dir)) {
12606
+ fs31.rmSync(node9Dir, { recursive: true });
12607
+ if (fs31.existsSync(node9Dir)) {
11862
12608
  console.error(
11863
- chalk19.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12609
+ chalk20.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
11864
12610
  );
11865
12611
  } else {
11866
- console.log(chalk19.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
12612
+ console.log(chalk20.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
11867
12613
  }
11868
12614
  } else {
11869
- console.log(chalk19.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12615
+ console.log(chalk20.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
11870
12616
  }
11871
12617
  } else {
11872
- console.log(chalk19.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
12618
+ console.log(chalk20.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
11873
12619
  }
11874
12620
  } else {
11875
12621
  console.log(
11876
- chalk19.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
12622
+ chalk20.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
11877
12623
  );
11878
12624
  }
11879
12625
  if (teardownFailed) {
11880
- console.error(chalk19.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
12626
+ console.error(chalk20.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
11881
12627
  process.exit(1);
11882
12628
  }
11883
- console.log(chalk19.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
11884
- console.log(chalk19.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
12629
+ console.log(chalk20.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
12630
+ console.log(chalk20.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
11885
12631
  });
11886
12632
  registerDoctorCommand(program, version);
11887
12633
  program.command("explain").description(
@@ -11894,7 +12640,7 @@ program.command("explain").description(
11894
12640
  try {
11895
12641
  args = JSON.parse(trimmed);
11896
12642
  } catch {
11897
- console.error(chalk19.red(`
12643
+ console.error(chalk20.red(`
11898
12644
  \u274C Invalid JSON: ${trimmed}
11899
12645
  `));
11900
12646
  process.exit(1);
@@ -11905,60 +12651,61 @@ program.command("explain").description(
11905
12651
  }
11906
12652
  const result = await explainPolicy(tool, args);
11907
12653
  console.log("");
11908
- console.log(chalk19.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12654
+ console.log(chalk20.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
11909
12655
  console.log("");
11910
- console.log(` ${chalk19.bold("Tool:")} ${chalk19.white(result.tool)}`);
12656
+ console.log(` ${chalk20.bold("Tool:")} ${chalk20.white(result.tool)}`);
11911
12657
  if (argsRaw) {
11912
12658
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
11913
- console.log(` ${chalk19.bold("Input:")} ${chalk19.gray(preview)}`);
12659
+ console.log(` ${chalk20.bold("Input:")} ${chalk20.gray(preview)}`);
11914
12660
  }
11915
12661
  console.log("");
11916
- console.log(chalk19.bold("Config Sources (Waterfall):"));
12662
+ console.log(chalk20.bold("Config Sources (Waterfall):"));
11917
12663
  for (const tier of result.waterfall) {
11918
- const num = chalk19.gray(` ${tier.tier}.`);
12664
+ const num2 = chalk20.gray(` ${tier.tier}.`);
11919
12665
  const label = tier.label.padEnd(16);
11920
12666
  let statusStr;
11921
12667
  if (tier.tier === 1) {
11922
- statusStr = chalk19.gray(tier.note ?? "");
12668
+ statusStr = chalk20.gray(tier.note ?? "");
11923
12669
  } else if (tier.status === "active") {
11924
- const loc = tier.path ? chalk19.gray(tier.path) : "";
11925
- const note = tier.note ? chalk19.gray(`(${tier.note})`) : "";
11926
- statusStr = chalk19.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
12670
+ const loc = tier.path ? chalk20.gray(tier.path) : "";
12671
+ const note = tier.note ? chalk20.gray(`(${tier.note})`) : "";
12672
+ statusStr = chalk20.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
11927
12673
  } else {
11928
- statusStr = chalk19.gray("\u25CB " + (tier.note ?? "not found"));
12674
+ statusStr = chalk20.gray("\u25CB " + (tier.note ?? "not found"));
11929
12675
  }
11930
- console.log(`${num} ${chalk19.white(label)} ${statusStr}`);
12676
+ console.log(`${num2} ${chalk20.white(label)} ${statusStr}`);
11931
12677
  }
11932
12678
  console.log("");
11933
- console.log(chalk19.bold("Policy Evaluation:"));
12679
+ console.log(chalk20.bold("Policy Evaluation:"));
11934
12680
  for (const step of result.steps) {
11935
12681
  const isFinal = step.isFinal;
11936
12682
  let icon;
11937
- if (step.outcome === "allow") icon = chalk19.green(" \u2705");
11938
- else if (step.outcome === "review") icon = chalk19.red(" \u{1F534}");
11939
- else if (step.outcome === "skip") icon = chalk19.gray(" \u2500 ");
11940
- else icon = chalk19.gray(" \u25CB ");
12683
+ if (step.outcome === "allow") icon = chalk20.green(" \u2705");
12684
+ else if (step.outcome === "review") icon = chalk20.red(" \u{1F534}");
12685
+ else if (step.outcome === "skip") icon = chalk20.gray(" \u2500 ");
12686
+ else icon = chalk20.gray(" \u25CB ");
11941
12687
  const name = step.name.padEnd(18);
11942
- const nameStr = isFinal ? chalk19.white.bold(name) : chalk19.white(name);
11943
- const detail = isFinal ? chalk19.white(step.detail) : chalk19.gray(step.detail);
11944
- const arrow = isFinal ? chalk19.yellow(" \u2190 STOP") : "";
12688
+ const nameStr = isFinal ? chalk20.white.bold(name) : chalk20.white(name);
12689
+ const detail = isFinal ? chalk20.white(step.detail) : chalk20.gray(step.detail);
12690
+ const arrow = isFinal ? chalk20.yellow(" \u2190 STOP") : "";
11945
12691
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
11946
12692
  }
11947
12693
  console.log("");
11948
12694
  if (result.decision === "allow") {
11949
- console.log(chalk19.green.bold(" Decision: \u2705 ALLOW") + chalk19.gray(" \u2014 no approval needed"));
12695
+ console.log(chalk20.green.bold(" Decision: \u2705 ALLOW") + chalk20.gray(" \u2014 no approval needed"));
11950
12696
  } else {
11951
12697
  console.log(
11952
- chalk19.red.bold(" Decision: \u{1F534} REVIEW") + chalk19.gray(" \u2014 human approval required")
12698
+ chalk20.red.bold(" Decision: \u{1F534} REVIEW") + chalk20.gray(" \u2014 human approval required")
11953
12699
  );
11954
12700
  if (result.blockedByLabel) {
11955
- console.log(chalk19.gray(` Reason: ${result.blockedByLabel}`));
12701
+ console.log(chalk20.gray(` Reason: ${result.blockedByLabel}`));
11956
12702
  }
11957
12703
  }
11958
12704
  console.log("");
11959
12705
  });
11960
12706
  registerInitCommand(program);
11961
12707
  registerAuditCommand(program);
12708
+ registerReportCommand(program);
11962
12709
  registerStatusCommand(program);
11963
12710
  registerDaemonCommand(program);
11964
12711
  program.command("tail").description("Stream live agent activity to the terminal").option("--history", "Replay recent history then continue live", false).option("--clear", "Clear the history buffer and exit (does not stream)", false).action(async (options) => {
@@ -11966,7 +12713,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
11966
12713
  try {
11967
12714
  await startTail2(options);
11968
12715
  } catch (err2) {
11969
- console.error(chalk19.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
12716
+ console.error(chalk20.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
11970
12717
  process.exit(1);
11971
12718
  }
11972
12719
  });
@@ -11998,14 +12745,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
11998
12745
  Run "node9 addto claude" to register it as the statusLine.`
11999
12746
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12000
12747
  if (subcommand === "debug") {
12001
- const flagFile = path31.join(os24.homedir(), ".node9", "hud-debug");
12748
+ const flagFile = path34.join(os27.homedir(), ".node9", "hud-debug");
12002
12749
  if (state === "on") {
12003
- fs28.mkdirSync(path31.dirname(flagFile), { recursive: true });
12004
- fs28.writeFileSync(flagFile, "");
12750
+ fs31.mkdirSync(path34.dirname(flagFile), { recursive: true });
12751
+ fs31.writeFileSync(flagFile, "");
12005
12752
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12006
12753
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12007
12754
  } else if (state === "off") {
12008
- if (fs28.existsSync(flagFile)) fs28.unlinkSync(flagFile);
12755
+ if (fs31.existsSync(flagFile)) fs31.unlinkSync(flagFile);
12009
12756
  console.log("HUD debug logging disabled.");
12010
12757
  } else {
12011
12758
  console.error("Usage: node9 hud debug on|off");
@@ -12020,7 +12767,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12020
12767
  const ms = parseDuration(options.duration);
12021
12768
  if (ms === null) {
12022
12769
  console.error(
12023
- chalk19.red(`
12770
+ chalk20.red(`
12024
12771
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12025
12772
  `)
12026
12773
  );
@@ -12028,20 +12775,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12028
12775
  }
12029
12776
  pauseNode9(ms, options.duration);
12030
12777
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12031
- console.log(chalk19.yellow(`
12778
+ console.log(chalk20.yellow(`
12032
12779
  \u23F8 Node9 paused until ${expiresAt}`));
12033
- console.log(chalk19.gray(` All tool calls will be allowed without review.`));
12034
- console.log(chalk19.gray(` Run "node9 resume" to re-enable early.
12780
+ console.log(chalk20.gray(` All tool calls will be allowed without review.`));
12781
+ console.log(chalk20.gray(` Run "node9 resume" to re-enable early.
12035
12782
  `));
12036
12783
  });
12037
12784
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12038
12785
  const { paused } = checkPause();
12039
12786
  if (!paused) {
12040
- console.log(chalk19.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12787
+ console.log(chalk20.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12041
12788
  return;
12042
12789
  }
12043
12790
  resumeNode9();
12044
- console.log(chalk19.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12791
+ console.log(chalk20.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12045
12792
  });
12046
12793
  var HOOK_BASED_AGENTS = {
12047
12794
  claude: "claude",
@@ -12054,15 +12801,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12054
12801
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12055
12802
  const target = HOOK_BASED_AGENTS[firstArg2];
12056
12803
  console.error(
12057
- chalk19.yellow(`
12804
+ chalk20.yellow(`
12058
12805
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12059
12806
  );
12060
- console.error(chalk19.white(`
12807
+ console.error(chalk20.white(`
12061
12808
  "${target}" uses its own hook system. Use:`));
12062
12809
  console.error(
12063
- chalk19.green(` node9 addto ${target} `) + chalk19.gray("# one-time setup")
12810
+ chalk20.green(` node9 addto ${target} `) + chalk20.gray("# one-time setup")
12064
12811
  );
12065
- console.error(chalk19.green(` ${target} `) + chalk19.gray("# run normally"));
12812
+ console.error(chalk20.green(` ${target} `) + chalk20.gray("# run normally"));
12066
12813
  process.exit(1);
12067
12814
  }
12068
12815
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12079,7 +12826,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12079
12826
  }
12080
12827
  );
12081
12828
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12082
- console.error(chalk19.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12829
+ console.error(chalk20.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12083
12830
  const daemonReady = await autoStartDaemonAndWait();
12084
12831
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12085
12832
  }
@@ -12092,12 +12839,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12092
12839
  }
12093
12840
  if (!result.approved) {
12094
12841
  console.error(
12095
- chalk19.red(`
12842
+ chalk20.red(`
12096
12843
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12097
12844
  );
12098
12845
  process.exit(1);
12099
12846
  }
12100
- console.error(chalk19.green("\n\u2705 Approved \u2014 running command...\n"));
12847
+ console.error(chalk20.green("\n\u2705 Approved \u2014 running command...\n"));
12101
12848
  await runProxy(fullCommand);
12102
12849
  } else {
12103
12850
  program.help();
@@ -12112,9 +12859,9 @@ if (process.argv[2] !== "daemon") {
12112
12859
  const isCheckHook = process.argv[2] === "check";
12113
12860
  if (isCheckHook) {
12114
12861
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12115
- const logPath = path31.join(os24.homedir(), ".node9", "hook-debug.log");
12862
+ const logPath = path34.join(os27.homedir(), ".node9", "hook-debug.log");
12116
12863
  const msg = reason instanceof Error ? reason.message : String(reason);
12117
- fs28.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12864
+ fs31.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12118
12865
  `);
12119
12866
  }
12120
12867
  process.exit(0);