@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.js CHANGED
@@ -76,6 +76,11 @@ __export(audit_exports, {
76
76
  appendToLog: () => appendToLog,
77
77
  redactSecrets: () => redactSecrets
78
78
  });
79
+ function isTestCall(toolName, args) {
80
+ if (toolName !== "Bash" && toolName !== "bash") return false;
81
+ const cmd = args?.command;
82
+ return typeof cmd === "string" && TEST_COMMAND_RE.test(cmd);
83
+ }
79
84
  function redactSecrets(text) {
80
85
  if (!text) return text;
81
86
  let redacted = text;
@@ -111,12 +116,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
111
116
  }
112
117
  function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
113
118
  const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
119
+ const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
114
120
  appendToLog(LOCAL_AUDIT_LOG, {
115
121
  ts: (/* @__PURE__ */ new Date()).toISOString(),
116
122
  tool: toolName,
117
123
  ...argsField,
118
124
  decision,
119
125
  checkedBy,
126
+ ...testRun,
120
127
  agent: meta?.agent,
121
128
  mcpServer: meta?.mcpServer,
122
129
  hostname: import_os.default.hostname()
@@ -129,7 +136,7 @@ function appendConfigAudit(entry) {
129
136
  hostname: import_os.default.hostname()
130
137
  });
131
138
  }
132
- var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
139
+ var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, TEST_COMMAND_RE;
133
140
  var init_audit = __esm({
134
141
  "src/audit/index.ts"() {
135
142
  "use strict";
@@ -139,6 +146,7 @@ var init_audit = __esm({
139
146
  init_hasher();
140
147
  LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
141
148
  HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
149
+ 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;
142
150
  }
143
151
  });
144
152
 
@@ -160,8 +168,8 @@ function sanitizeConfig(raw) {
160
168
  }
161
169
  }
162
170
  const lines = result.error.issues.map((issue) => {
163
- const path32 = issue.path.length > 0 ? issue.path.join(".") : "root";
164
- return ` \u2022 ${path32}: ${issue.message}`;
171
+ const path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path35}: ${issue.message}`;
165
173
  });
166
174
  return {
167
175
  sanitized,
@@ -245,7 +253,8 @@ var init_config_schema = __esm({
245
253
  slackEnabled: import_zod.z.boolean().optional(),
246
254
  enableTrustSessions: import_zod.z.boolean().optional(),
247
255
  allowGlobalPause: import_zod.z.boolean().optional(),
248
- auditHashArgs: import_zod.z.boolean().optional()
256
+ auditHashArgs: import_zod.z.boolean().optional(),
257
+ agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
249
258
  }).optional(),
250
259
  policy: import_zod.z.object({
251
260
  sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
@@ -261,6 +270,11 @@ var init_config_schema = __esm({
261
270
  dlp: import_zod.z.object({
262
271
  enabled: import_zod.z.boolean().optional(),
263
272
  scanIgnoredTools: import_zod.z.boolean().optional()
273
+ }).optional(),
274
+ loopDetection: import_zod.z.object({
275
+ enabled: import_zod.z.boolean().optional(),
276
+ threshold: import_zod.z.number().min(2).optional(),
277
+ windowSeconds: import_zod.z.number().min(10).optional()
264
278
  }).optional()
265
279
  }).optional(),
266
280
  environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
@@ -555,7 +569,8 @@ function getConfig(cwd) {
555
569
  onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
556
570
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
557
571
  },
558
- dlp: { ...DEFAULT_CONFIG.policy.dlp }
572
+ dlp: { ...DEFAULT_CONFIG.policy.dlp },
573
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
559
574
  };
560
575
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
561
576
  const applyLayer = (source) => {
@@ -594,6 +609,13 @@ function getConfig(cwd) {
594
609
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
595
610
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
596
611
  }
612
+ if (p.loopDetection) {
613
+ const ld = p.loopDetection;
614
+ if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
615
+ if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
616
+ if (ld.windowSeconds !== void 0)
617
+ mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
618
+ }
597
619
  const envs = source.environments || {};
598
620
  for (const [envName, envConfig] of Object.entries(envs)) {
599
621
  if (envConfig && typeof envConfig === "object") {
@@ -900,7 +922,8 @@ var init_config = __esm({
900
922
  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."
901
923
  }
902
924
  ],
903
- dlp: { enabled: true, scanIgnoredTools: true }
925
+ dlp: { enabled: true, scanIgnoredTools: true },
926
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
904
927
  },
905
928
  environments: {}
906
929
  };
@@ -1704,9 +1727,9 @@ function matchesPattern(text, patterns) {
1704
1727
  const withoutDotSlash = text.replace(/^\.\//, "");
1705
1728
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1706
1729
  }
1707
- function getNestedValue(obj, path32) {
1730
+ function getNestedValue(obj, path35) {
1708
1731
  if (!obj || typeof obj !== "object") return null;
1709
- return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
1732
+ return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
1710
1733
  }
1711
1734
  function shouldSnapshot(toolName, args, config) {
1712
1735
  if (!config.settings.enableUndo) return false;
@@ -2789,11 +2812,12 @@ ${smartTruncate(str, 500)}`
2789
2812
  function escapePango(text) {
2790
2813
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2791
2814
  }
2792
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2815
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2793
2816
  const lines = [];
2794
2817
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2795
2818
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2796
2819
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2820
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2797
2821
  lines.push("");
2798
2822
  lines.push(formattedArgs);
2799
2823
  if (allowCount >= 3) {
@@ -2806,7 +2830,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2806
2830
  }
2807
2831
  return lines.join("\n");
2808
2832
  }
2809
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2833
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2810
2834
  const lines = [];
2811
2835
  if (locked) {
2812
2836
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2816,6 +2840,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2816
2840
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2817
2841
  );
2818
2842
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2843
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2819
2844
  lines.push("");
2820
2845
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2821
2846
  if (allowCount >= 3) {
@@ -2832,7 +2857,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2832
2857
  }
2833
2858
  return lines.join("\n");
2834
2859
  }
2835
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2860
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2836
2861
  if (isTestEnv()) return "deny";
2837
2862
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2838
2863
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2843,7 +2868,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2843
2868
  agent,
2844
2869
  explainableLabel,
2845
2870
  locked,
2846
- allowCount
2871
+ allowCount,
2872
+ ruleDescription
2847
2873
  );
2848
2874
  return new Promise((resolve) => {
2849
2875
  let childProcess = null;
@@ -2877,7 +2903,8 @@ end run`;
2877
2903
  agent,
2878
2904
  explainableLabel,
2879
2905
  locked,
2880
- allowCount
2906
+ allowCount,
2907
+ ruleDescription
2881
2908
  );
2882
2909
  const argsList = [
2883
2910
  locked ? "--info" : "--question",
@@ -2950,7 +2977,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2950
2977
  }).catch(() => {
2951
2978
  });
2952
2979
  }
2953
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2980
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2954
2981
  const controller = new AbortController();
2955
2982
  const timeout = setTimeout(() => controller.abort(), 1e4);
2956
2983
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -2995,7 +3022,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2995
3022
  platform: import_os9.default.platform()
2996
3023
  },
2997
3024
  ...riskMetadata && { riskMetadata },
2998
- ...ciContext && { ciContext }
3025
+ ...ciContext && { ciContext },
3026
+ ...agentPolicy && { policy: agentPolicy },
3027
+ ...forceReview && { forceReview: true }
2999
3028
  }),
3000
3029
  signal: controller.signal
3001
3030
  });
@@ -3079,6 +3108,58 @@ var init_cloud = __esm({
3079
3108
  }
3080
3109
  });
3081
3110
 
3111
+ // src/loop-detector.ts
3112
+ function loopStateFile() {
3113
+ return import_path14.default.join(import_os10.default.homedir(), ".node9", "loop-state.json");
3114
+ }
3115
+ function computeArgsHash(args) {
3116
+ const str = JSON.stringify(args ?? "");
3117
+ return import_crypto3.default.createHash("sha256").update(str).digest("hex").slice(0, 16);
3118
+ }
3119
+ function readState() {
3120
+ try {
3121
+ if (!import_fs11.default.existsSync(loopStateFile())) return [];
3122
+ const raw = import_fs11.default.readFileSync(loopStateFile(), "utf-8");
3123
+ const parsed = JSON.parse(raw);
3124
+ if (!Array.isArray(parsed)) return [];
3125
+ return parsed;
3126
+ } catch {
3127
+ return [];
3128
+ }
3129
+ }
3130
+ function writeState(records) {
3131
+ const dir = import_path14.default.dirname(loopStateFile());
3132
+ if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
3133
+ const tmpPath = `${loopStateFile()}.${import_os10.default.hostname()}.${process.pid}.tmp`;
3134
+ import_fs11.default.writeFileSync(tmpPath, JSON.stringify(records));
3135
+ import_fs11.default.renameSync(tmpPath, loopStateFile());
3136
+ }
3137
+ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
3138
+ try {
3139
+ const hash = computeArgsHash(args);
3140
+ const now = Date.now();
3141
+ const cutoff = now - windowMs;
3142
+ const records = readState().filter((r) => r.ts >= cutoff);
3143
+ records.push({ t: tool, h: hash, ts: now });
3144
+ const count = records.filter((r) => r.t === tool && r.h === hash).length;
3145
+ writeState(records.slice(-MAX_RECORDS));
3146
+ return { looping: count >= threshold, count };
3147
+ } catch {
3148
+ return { looping: false, count: 0 };
3149
+ }
3150
+ }
3151
+ var import_fs11, import_path14, import_os10, import_crypto3, MAX_RECORDS;
3152
+ var init_loop_detector = __esm({
3153
+ "src/loop-detector.ts"() {
3154
+ "use strict";
3155
+ import_fs11 = __toESM(require("fs"));
3156
+ import_path14 = __toESM(require("path"));
3157
+ import_os10 = __toESM(require("os"));
3158
+ import_crypto3 = __toESM(require("crypto"));
3159
+ MAX_RECORDS = 500;
3160
+ }
3161
+ });
3162
+
3082
3163
  // src/auth/orchestrator.ts
3083
3164
  function isWriteTool(toolName) {
3084
3165
  const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
@@ -3119,7 +3200,7 @@ function notifyActivity(data) {
3119
3200
  }
3120
3201
  async function authorizeHeadless(toolName, args, meta, options) {
3121
3202
  if (!options?.calledFromDaemon) {
3122
- const actId = (0, import_crypto3.randomUUID)();
3203
+ const actId = (0, import_crypto4.randomUUID)();
3123
3204
  const actTs = Date.now();
3124
3205
  await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
3125
3206
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
@@ -3166,6 +3247,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3166
3247
  let explainableLabel = "Local Config";
3167
3248
  let policyMatchedField;
3168
3249
  let policyMatchedWord;
3250
+ let policyRuleDescription;
3169
3251
  let riskMetadata;
3170
3252
  let statefulRecoveryCommand;
3171
3253
  let localSmartRuleMatched = false;
@@ -3259,6 +3341,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3259
3341
  return { approved: true, checkedBy: "audit" };
3260
3342
  }
3261
3343
  if (!taintWarning && !isIgnoredTool(toolName)) {
3344
+ const ld = config.policy.loopDetection;
3345
+ if (ld.enabled) {
3346
+ const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
3347
+ if (loopResult.looping) {
3348
+ 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?`;
3349
+ if (!isManual)
3350
+ appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
3351
+ return {
3352
+ approved: false,
3353
+ reason,
3354
+ blockedBy: "loop-detection",
3355
+ blockedByLabel: "\u{1F504} Loop Detected"
3356
+ };
3357
+ }
3358
+ }
3262
3359
  if (getActiveTrustSession(toolName)) {
3263
3360
  if (approvers.cloud && creds?.apiKey)
3264
3361
  await auditLocalAllow(toolName, args, "trust", creds, meta);
@@ -3314,6 +3411,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3314
3411
  policyMatchedField = policyResult.matchedField;
3315
3412
  policyMatchedWord = policyResult.matchedWord;
3316
3413
  if (policyResult.ruleName) localSmartRuleMatched = true;
3414
+ if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
3415
+ else if (policyResult.reason) policyRuleDescription = policyResult.reason;
3317
3416
  riskMetadata = computeRiskMetadata(
3318
3417
  args,
3319
3418
  policyResult.tier ?? 6,
@@ -3322,6 +3421,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3322
3421
  policyMatchedWord,
3323
3422
  policyResult.ruleName
3324
3423
  );
3424
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
3325
3425
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
3326
3426
  if (persistent === "allow") {
3327
3427
  if (approvers.cloud && creds?.apiKey)
@@ -3356,9 +3456,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3356
3456
  }
3357
3457
  let cloudRequestId = null;
3358
3458
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
3359
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3459
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
3460
+ if (cloudEnforced) {
3360
3461
  try {
3361
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
3462
+ const initResult = await initNode9SaaS(
3463
+ toolName,
3464
+ args,
3465
+ creds,
3466
+ meta,
3467
+ riskMetadata,
3468
+ config.settings.agentPolicy,
3469
+ forceReview
3470
+ );
3362
3471
  if (!initResult.pending) {
3363
3472
  if (initResult.shadowMode) {
3364
3473
  return { approved: true, checkedBy: "cloud" };
@@ -3373,9 +3482,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3373
3482
  };
3374
3483
  }
3375
3484
  }
3376
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
3377
- cloudRequestId = initResult.requestId || null;
3378
- }
3485
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
3379
3486
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
3380
3487
  } catch {
3381
3488
  }
@@ -3432,7 +3539,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3432
3539
  }
3433
3540
  }
3434
3541
  }
3435
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3542
+ if (cloudEnforced && cloudRequestId) {
3436
3543
  racePromises.push(
3437
3544
  (async () => {
3438
3545
  try {
@@ -3464,7 +3571,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3464
3571
  signal,
3465
3572
  policyMatchedField,
3466
3573
  policyMatchedWord,
3467
- daemonAllowCount
3574
+ daemonAllowCount,
3575
+ riskMetadata?.ruleDescription
3468
3576
  );
3469
3577
  if (decision === "always_allow") {
3470
3578
  writeTrustSession(toolName, 36e5);
@@ -3577,13 +3685,14 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
3577
3685
  hashAuditArgs
3578
3686
  );
3579
3687
  }
3580
- return finalResult;
3688
+ const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
3689
+ return enrichedResult;
3581
3690
  }
3582
- var import_crypto3, WRITE_TOOLS;
3691
+ var import_crypto4, WRITE_TOOLS;
3583
3692
  var init_orchestrator = __esm({
3584
3693
  "src/auth/orchestrator.ts"() {
3585
3694
  "use strict";
3586
- import_crypto3 = require("crypto");
3695
+ import_crypto4 = require("crypto");
3587
3696
  init_native();
3588
3697
  init_context_sniper();
3589
3698
  init_dlp();
@@ -3593,6 +3702,7 @@ var init_orchestrator = __esm({
3593
3702
  init_state();
3594
3703
  init_daemon();
3595
3704
  init_cloud();
3705
+ init_loop_detector();
3596
3706
  WRITE_TOOLS = /* @__PURE__ */ new Set([
3597
3707
  "write",
3598
3708
  "write_file",
@@ -5266,11 +5376,11 @@ function commonPathPrefix(paths) {
5266
5376
  const prefix = common.join("/").replace(/\/?$/, "/");
5267
5377
  return prefix.length > 1 ? prefix : null;
5268
5378
  }
5269
- var import_crypto4, SuggestionTracker;
5379
+ var import_crypto5, SuggestionTracker;
5270
5380
  var init_suggestion_tracker = __esm({
5271
5381
  "src/daemon/suggestion-tracker.ts"() {
5272
5382
  "use strict";
5273
- import_crypto4 = require("crypto");
5383
+ import_crypto5 = require("crypto");
5274
5384
  SuggestionTracker = class {
5275
5385
  events = /* @__PURE__ */ new Map();
5276
5386
  threshold;
@@ -5316,7 +5426,7 @@ var init_suggestion_tracker = __esm({
5316
5426
  }
5317
5427
  } : { type: "ignoredTool", toolName };
5318
5428
  return {
5319
- id: (0, import_crypto4.randomUUID)(),
5429
+ id: (0, import_crypto5.randomUUID)(),
5320
5430
  toolName,
5321
5431
  allowCount: events.length,
5322
5432
  suggestedRule,
@@ -5330,12 +5440,12 @@ var init_suggestion_tracker = __esm({
5330
5440
  });
5331
5441
 
5332
5442
  // src/daemon/taint-store.ts
5333
- var import_fs12, import_path15, DEFAULT_TTL_MS, TaintStore;
5443
+ var import_fs13, import_path16, DEFAULT_TTL_MS, TaintStore;
5334
5444
  var init_taint_store = __esm({
5335
5445
  "src/daemon/taint-store.ts"() {
5336
5446
  "use strict";
5337
- import_fs12 = __toESM(require("fs"));
5338
- import_path15 = __toESM(require("path"));
5447
+ import_fs13 = __toESM(require("fs"));
5448
+ import_path16 = __toESM(require("path"));
5339
5449
  DEFAULT_TTL_MS = 60 * 60 * 1e3;
5340
5450
  TaintStore = class {
5341
5451
  records = /* @__PURE__ */ new Map();
@@ -5400,9 +5510,9 @@ var init_taint_store = __esm({
5400
5510
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5401
5511
  _resolve(filePath) {
5402
5512
  try {
5403
- return import_fs12.default.realpathSync.native(import_path15.default.resolve(filePath));
5513
+ return import_fs13.default.realpathSync.native(import_path16.default.resolve(filePath));
5404
5514
  } catch {
5405
- return import_path15.default.resolve(filePath);
5515
+ return import_path16.default.resolve(filePath);
5406
5516
  }
5407
5517
  }
5408
5518
  };
@@ -5520,8 +5630,8 @@ var init_session_history = __esm({
5520
5630
  // src/daemon/state.ts
5521
5631
  function loadInsightCounts() {
5522
5632
  try {
5523
- if (!import_fs13.default.existsSync(INSIGHT_COUNTS_FILE)) return;
5524
- const data = JSON.parse(import_fs13.default.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5633
+ if (!import_fs14.default.existsSync(INSIGHT_COUNTS_FILE)) return;
5634
+ const data = JSON.parse(import_fs14.default.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5525
5635
  for (const [tool, count] of Object.entries(data)) {
5526
5636
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
5527
5637
  }
@@ -5560,23 +5670,23 @@ function markRejectionHandlerRegistered() {
5560
5670
  daemonRejectionHandlerRegistered = true;
5561
5671
  }
5562
5672
  function atomicWriteSync2(filePath, data, options) {
5563
- const dir = import_path16.default.dirname(filePath);
5564
- if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
5565
- const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
5673
+ const dir = import_path17.default.dirname(filePath);
5674
+ if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
5675
+ const tmpPath = `${filePath}.${(0, import_crypto6.randomUUID)()}.tmp`;
5566
5676
  try {
5567
- import_fs13.default.writeFileSync(tmpPath, data, options);
5677
+ import_fs14.default.writeFileSync(tmpPath, data, options);
5568
5678
  } catch (err2) {
5569
5679
  try {
5570
- import_fs13.default.unlinkSync(tmpPath);
5680
+ import_fs14.default.unlinkSync(tmpPath);
5571
5681
  } catch {
5572
5682
  }
5573
5683
  throw err2;
5574
5684
  }
5575
5685
  try {
5576
- import_fs13.default.renameSync(tmpPath, filePath);
5686
+ import_fs14.default.renameSync(tmpPath, filePath);
5577
5687
  } catch (err2) {
5578
5688
  try {
5579
- import_fs13.default.unlinkSync(tmpPath);
5689
+ import_fs14.default.unlinkSync(tmpPath);
5580
5690
  } catch {
5581
5691
  }
5582
5692
  throw err2;
@@ -5600,16 +5710,16 @@ function appendAuditLog(data) {
5600
5710
  decision: data.decision,
5601
5711
  source: "daemon"
5602
5712
  };
5603
- const dir = import_path16.default.dirname(AUDIT_LOG_FILE);
5604
- if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
5605
- import_fs13.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5713
+ const dir = import_path17.default.dirname(AUDIT_LOG_FILE);
5714
+ if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
5715
+ import_fs14.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5606
5716
  } catch {
5607
5717
  }
5608
5718
  }
5609
5719
  function getAuditHistory(limit = 20) {
5610
5720
  try {
5611
- if (!import_fs13.default.existsSync(AUDIT_LOG_FILE)) return [];
5612
- const lines = import_fs13.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5721
+ if (!import_fs14.default.existsSync(AUDIT_LOG_FILE)) return [];
5722
+ const lines = import_fs14.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5613
5723
  if (lines.length === 1 && lines[0] === "") return [];
5614
5724
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
5615
5725
  } catch {
@@ -5618,19 +5728,19 @@ function getAuditHistory(limit = 20) {
5618
5728
  }
5619
5729
  function getOrgName() {
5620
5730
  try {
5621
- if (import_fs13.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5731
+ if (import_fs14.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5622
5732
  } catch {
5623
5733
  }
5624
5734
  return null;
5625
5735
  }
5626
5736
  function hasStoredSlackKey() {
5627
- return import_fs13.default.existsSync(CREDENTIALS_FILE);
5737
+ return import_fs14.default.existsSync(CREDENTIALS_FILE);
5628
5738
  }
5629
5739
  function writeGlobalSetting(key, value) {
5630
5740
  let config = {};
5631
5741
  try {
5632
- if (import_fs13.default.existsSync(GLOBAL_CONFIG_FILE)) {
5633
- config = JSON.parse(import_fs13.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5742
+ if (import_fs14.default.existsSync(GLOBAL_CONFIG_FILE)) {
5743
+ config = JSON.parse(import_fs14.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5634
5744
  }
5635
5745
  } catch {
5636
5746
  }
@@ -5642,8 +5752,8 @@ function writeTrustEntry(toolName, durationMs) {
5642
5752
  try {
5643
5753
  let trust = { entries: [] };
5644
5754
  try {
5645
- if (import_fs13.default.existsSync(TRUST_FILE2))
5646
- trust = JSON.parse(import_fs13.default.readFileSync(TRUST_FILE2, "utf-8"));
5755
+ if (import_fs14.default.existsSync(TRUST_FILE2))
5756
+ trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
5647
5757
  } catch {
5648
5758
  }
5649
5759
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -5654,8 +5764,8 @@ function writeTrustEntry(toolName, durationMs) {
5654
5764
  }
5655
5765
  function readPersistentDecisions() {
5656
5766
  try {
5657
- if (import_fs13.default.existsSync(DECISIONS_FILE)) {
5658
- return JSON.parse(import_fs13.default.readFileSync(DECISIONS_FILE, "utf-8"));
5767
+ if (import_fs14.default.existsSync(DECISIONS_FILE)) {
5768
+ return JSON.parse(import_fs14.default.readFileSync(DECISIONS_FILE, "utf-8"));
5659
5769
  }
5660
5770
  } catch {
5661
5771
  }
@@ -5692,7 +5802,7 @@ function estimateToolCost(tool, args) {
5692
5802
  const filePath = a.file_path ?? a.path;
5693
5803
  if (filePath) {
5694
5804
  try {
5695
- const bytes = import_fs13.default.statSync(filePath).size;
5805
+ const bytes = import_fs14.default.statSync(filePath).size;
5696
5806
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
5697
5807
  } catch {
5698
5808
  }
@@ -5750,7 +5860,7 @@ function abandonPending() {
5750
5860
  });
5751
5861
  if (autoStarted) {
5752
5862
  try {
5753
- import_fs13.default.unlinkSync(DAEMON_PID_FILE);
5863
+ import_fs14.default.unlinkSync(DAEMON_PID_FILE);
5754
5864
  } catch {
5755
5865
  }
5756
5866
  setTimeout(() => {
@@ -5761,7 +5871,7 @@ function abandonPending() {
5761
5871
  }
5762
5872
  function startActivitySocket() {
5763
5873
  try {
5764
- import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5874
+ import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5765
5875
  } catch {
5766
5876
  }
5767
5877
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -5842,34 +5952,34 @@ function startActivitySocket() {
5842
5952
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
5843
5953
  process.on("exit", () => {
5844
5954
  try {
5845
- import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5955
+ import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5846
5956
  } catch {
5847
5957
  }
5848
5958
  });
5849
5959
  }
5850
- var import_net2, import_fs13, import_path16, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
5960
+ var import_net2, import_fs14, import_path17, import_os12, import_child_process3, import_crypto6, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
5851
5961
  var init_state2 = __esm({
5852
5962
  "src/daemon/state.ts"() {
5853
5963
  "use strict";
5854
5964
  import_net2 = __toESM(require("net"));
5855
- import_fs13 = __toESM(require("fs"));
5856
- import_path16 = __toESM(require("path"));
5857
- import_os11 = __toESM(require("os"));
5965
+ import_fs14 = __toESM(require("fs"));
5966
+ import_path17 = __toESM(require("path"));
5967
+ import_os12 = __toESM(require("os"));
5858
5968
  import_child_process3 = require("child_process");
5859
- import_crypto5 = require("crypto");
5969
+ import_crypto6 = require("crypto");
5860
5970
  init_daemon();
5861
5971
  init_suggestion_tracker();
5862
5972
  init_taint_store();
5863
5973
  init_session_counters();
5864
5974
  init_session_history();
5865
- homeDir = import_os11.default.homedir();
5866
- DAEMON_PID_FILE = import_path16.default.join(homeDir, ".node9", "daemon.pid");
5867
- DECISIONS_FILE = import_path16.default.join(homeDir, ".node9", "decisions.json");
5868
- AUDIT_LOG_FILE = import_path16.default.join(homeDir, ".node9", "audit.log");
5869
- TRUST_FILE2 = import_path16.default.join(homeDir, ".node9", "trust.json");
5870
- GLOBAL_CONFIG_FILE = import_path16.default.join(homeDir, ".node9", "config.json");
5871
- CREDENTIALS_FILE = import_path16.default.join(homeDir, ".node9", "credentials.json");
5872
- INSIGHT_COUNTS_FILE = import_path16.default.join(homeDir, ".node9", "insight-counts.json");
5975
+ homeDir = import_os12.default.homedir();
5976
+ DAEMON_PID_FILE = import_path17.default.join(homeDir, ".node9", "daemon.pid");
5977
+ DECISIONS_FILE = import_path17.default.join(homeDir, ".node9", "decisions.json");
5978
+ AUDIT_LOG_FILE = import_path17.default.join(homeDir, ".node9", "audit.log");
5979
+ TRUST_FILE2 = import_path17.default.join(homeDir, ".node9", "trust.json");
5980
+ GLOBAL_CONFIG_FILE = import_path17.default.join(homeDir, ".node9", "config.json");
5981
+ CREDENTIALS_FILE = import_path17.default.join(homeDir, ".node9", "credentials.json");
5982
+ INSIGHT_COUNTS_FILE = import_path17.default.join(homeDir, ".node9", "insight-counts.json");
5873
5983
  pending = /* @__PURE__ */ new Map();
5874
5984
  sseClients = /* @__PURE__ */ new Set();
5875
5985
  suggestionTracker = new SuggestionTracker(3);
@@ -5887,7 +5997,7 @@ var init_state2 = __esm({
5887
5997
  "2h": 2 * 60 * 6e4
5888
5998
  };
5889
5999
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5890
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path16.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
6000
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path17.default.join(import_os12.default.tmpdir(), "node9-activity.sock");
5891
6001
  ACTIVITY_RING_SIZE = 100;
5892
6002
  activityRing = [];
5893
6003
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5912,8 +6022,8 @@ var init_state2 = __esm({
5912
6022
  function patchConfig(configPath, patch) {
5913
6023
  let config = {};
5914
6024
  try {
5915
- if (import_fs14.default.existsSync(configPath)) {
5916
- config = JSON.parse(import_fs14.default.readFileSync(configPath, "utf8"));
6025
+ if (import_fs15.default.existsSync(configPath)) {
6026
+ config = JSON.parse(import_fs15.default.readFileSync(configPath, "utf8"));
5917
6027
  }
5918
6028
  } catch {
5919
6029
  throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
@@ -5932,44 +6042,211 @@ function patchConfig(configPath, patch) {
5932
6042
  ignored.push(patch.toolName);
5933
6043
  }
5934
6044
  }
5935
- const dir = import_path17.default.dirname(configPath);
5936
- import_fs14.default.mkdirSync(dir, { recursive: true });
6045
+ const dir = import_path18.default.dirname(configPath);
6046
+ import_fs15.default.mkdirSync(dir, { recursive: true });
5937
6047
  const tmp = configPath + ".node9-tmp";
5938
6048
  try {
5939
- import_fs14.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
6049
+ import_fs15.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
5940
6050
  } catch (err2) {
5941
6051
  try {
5942
- import_fs14.default.unlinkSync(tmp);
6052
+ import_fs15.default.unlinkSync(tmp);
5943
6053
  } catch {
5944
6054
  }
5945
6055
  throw err2;
5946
6056
  }
5947
6057
  try {
5948
- import_fs14.default.renameSync(tmp, configPath);
6058
+ import_fs15.default.renameSync(tmp, configPath);
5949
6059
  } catch (err2) {
5950
6060
  try {
5951
- import_fs14.default.unlinkSync(tmp);
6061
+ import_fs15.default.unlinkSync(tmp);
5952
6062
  } catch {
5953
6063
  }
5954
6064
  throw err2;
5955
6065
  }
5956
6066
  }
5957
- var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
6067
+ var import_fs15, import_path18, import_os13, GLOBAL_CONFIG_PATH;
5958
6068
  var init_patch = __esm({
5959
6069
  "src/config/patch.ts"() {
5960
6070
  "use strict";
5961
- import_fs14 = __toESM(require("fs"));
5962
- import_path17 = __toESM(require("path"));
5963
- import_os12 = __toESM(require("os"));
5964
- GLOBAL_CONFIG_PATH = import_path17.default.join(import_os12.default.homedir(), ".node9", "config.json");
6071
+ import_fs15 = __toESM(require("fs"));
6072
+ import_path18 = __toESM(require("path"));
6073
+ import_os13 = __toESM(require("os"));
6074
+ GLOBAL_CONFIG_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "config.json");
6075
+ }
6076
+ });
6077
+
6078
+ // src/costSync.ts
6079
+ function normalizeModel(raw) {
6080
+ return raw.replace(/-\d{8}$/, "");
6081
+ }
6082
+ function pricingFor(model) {
6083
+ const norm = normalizeModel(model);
6084
+ if (PRICING[norm]) return PRICING[norm];
6085
+ let best = null;
6086
+ for (const key of Object.keys(PRICING)) {
6087
+ if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
6088
+ }
6089
+ return best ? PRICING[best] : null;
6090
+ }
6091
+ function parseJSONLFile(filePath) {
6092
+ let content;
6093
+ try {
6094
+ content = import_fs16.default.readFileSync(filePath, "utf8");
6095
+ } catch {
6096
+ return /* @__PURE__ */ new Map();
6097
+ }
6098
+ const daily = /* @__PURE__ */ new Map();
6099
+ for (const line of content.split("\n")) {
6100
+ if (!line.trim()) continue;
6101
+ let row;
6102
+ try {
6103
+ row = JSON.parse(line);
6104
+ } catch {
6105
+ continue;
6106
+ }
6107
+ if (row["type"] !== "assistant") continue;
6108
+ const msg = row["message"];
6109
+ if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
6110
+ const usage = msg["usage"];
6111
+ const model = msg["model"];
6112
+ const timestamp = row["timestamp"];
6113
+ if (typeof timestamp !== "string" || timestamp.length < 10) continue;
6114
+ const date = timestamp.slice(0, 10);
6115
+ const p = pricingFor(model);
6116
+ if (!p) continue;
6117
+ const inp = Number(usage["input_tokens"] ?? 0);
6118
+ const out = Number(usage["output_tokens"] ?? 0);
6119
+ const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
6120
+ const cr = Number(usage["cache_read_input_tokens"] ?? 0);
6121
+ const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
6122
+ const norm = normalizeModel(model);
6123
+ const key = `${date}::${norm}`;
6124
+ const prev = daily.get(key);
6125
+ if (prev) {
6126
+ prev.costUSD += cost;
6127
+ prev.inputTokens += inp;
6128
+ prev.outputTokens += out;
6129
+ prev.cacheWriteTokens += cw;
6130
+ prev.cacheReadTokens += cr;
6131
+ } else {
6132
+ daily.set(key, {
6133
+ date,
6134
+ model: norm,
6135
+ costUSD: cost,
6136
+ inputTokens: inp,
6137
+ outputTokens: out,
6138
+ cacheWriteTokens: cw,
6139
+ cacheReadTokens: cr
6140
+ });
6141
+ }
6142
+ }
6143
+ return daily;
6144
+ }
6145
+ function collectEntries() {
6146
+ const projectsDir = import_path19.default.join(import_os14.default.homedir(), ".claude", "projects");
6147
+ if (!import_fs16.default.existsSync(projectsDir)) return [];
6148
+ const combined = /* @__PURE__ */ new Map();
6149
+ let dirs;
6150
+ try {
6151
+ dirs = import_fs16.default.readdirSync(projectsDir);
6152
+ } catch {
6153
+ return [];
6154
+ }
6155
+ for (const dir of dirs) {
6156
+ const dirPath = import_path19.default.join(projectsDir, dir);
6157
+ try {
6158
+ if (!import_fs16.default.statSync(dirPath).isDirectory()) continue;
6159
+ } catch {
6160
+ continue;
6161
+ }
6162
+ let files;
6163
+ try {
6164
+ files = import_fs16.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
6165
+ } catch {
6166
+ continue;
6167
+ }
6168
+ for (const file of files) {
6169
+ const entries = parseJSONLFile(import_path19.default.join(dirPath, file));
6170
+ for (const [key, e] of entries) {
6171
+ const prev = combined.get(key);
6172
+ if (prev) {
6173
+ prev.costUSD += e.costUSD;
6174
+ prev.inputTokens += e.inputTokens;
6175
+ prev.outputTokens += e.outputTokens;
6176
+ prev.cacheWriteTokens += e.cacheWriteTokens;
6177
+ prev.cacheReadTokens += e.cacheReadTokens;
6178
+ } else {
6179
+ combined.set(key, { ...e });
6180
+ }
6181
+ }
6182
+ }
6183
+ }
6184
+ return [...combined.values()];
6185
+ }
6186
+ async function syncCost() {
6187
+ const creds = getCredentials();
6188
+ if (!creds?.apiKey || !creds?.apiUrl) return;
6189
+ const entries = collectEntries();
6190
+ if (entries.length === 0) return;
6191
+ let username = "unknown";
6192
+ try {
6193
+ username = import_os14.default.userInfo().username;
6194
+ } catch {
6195
+ }
6196
+ const machineId = `${import_os14.default.hostname()}:${username}`;
6197
+ try {
6198
+ const res = await fetch(`${creds.apiUrl}/cost-sync`, {
6199
+ method: "POST",
6200
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
6201
+ body: JSON.stringify({ machineId, entries }),
6202
+ signal: AbortSignal.timeout(15e3)
6203
+ });
6204
+ if (!res.ok) {
6205
+ import_fs16.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
6206
+ `);
6207
+ }
6208
+ } catch (err2) {
6209
+ import_fs16.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
6210
+ `);
6211
+ }
6212
+ }
6213
+ function startCostSync() {
6214
+ syncCost().catch(() => {
6215
+ });
6216
+ const timer = setInterval(() => {
6217
+ syncCost().catch(() => {
6218
+ });
6219
+ }, SYNC_INTERVAL_MS);
6220
+ timer.unref();
6221
+ }
6222
+ var import_fs16, import_path19, import_os14, SYNC_INTERVAL_MS, PRICING;
6223
+ var init_costSync = __esm({
6224
+ "src/costSync.ts"() {
6225
+ "use strict";
6226
+ import_fs16 = __toESM(require("fs"));
6227
+ import_path19 = __toESM(require("path"));
6228
+ import_os14 = __toESM(require("os"));
6229
+ init_config();
6230
+ init_audit();
6231
+ SYNC_INTERVAL_MS = 10 * 60 * 1e3;
6232
+ PRICING = {
6233
+ "claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
6234
+ "claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
6235
+ "claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
6236
+ "claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6237
+ "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
6238
+ "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
6239
+ "claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
6240
+ };
5965
6241
  }
5966
6242
  });
5967
6243
 
5968
6244
  // src/daemon/server.ts
5969
6245
  function startDaemon() {
6246
+ startCostSync();
5970
6247
  loadInsightCounts();
5971
- const csrfToken = (0, import_crypto6.randomUUID)();
5972
- const internalToken = (0, import_crypto6.randomUUID)();
6248
+ const csrfToken = (0, import_crypto7.randomUUID)();
6249
+ const internalToken = (0, import_crypto7.randomUUID)();
5973
6250
  const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
5974
6251
  const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
5975
6252
  const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
@@ -5982,7 +6259,7 @@ function startDaemon() {
5982
6259
  idleTimer = setTimeout(() => {
5983
6260
  if (autoStarted) {
5984
6261
  try {
5985
- import_fs15.default.unlinkSync(DAEMON_PID_FILE);
6262
+ import_fs17.default.unlinkSync(DAEMON_PID_FILE);
5986
6263
  } catch {
5987
6264
  }
5988
6265
  }
@@ -6103,7 +6380,7 @@ data: ${JSON.stringify(item.data)}
6103
6380
  cwd,
6104
6381
  localSmartRuleMatched = false
6105
6382
  } = JSON.parse(body);
6106
- const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto6.randomUUID)();
6383
+ const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto7.randomUUID)();
6107
6384
  const entry = {
6108
6385
  id,
6109
6386
  toolName,
@@ -6145,7 +6422,7 @@ data: ${JSON.stringify(item.data)}
6145
6422
  status: "pending"
6146
6423
  });
6147
6424
  }
6148
- const projectCwd = typeof cwd === "string" && import_path18.default.isAbsolute(cwd) ? cwd : void 0;
6425
+ const projectCwd = typeof cwd === "string" && import_path20.default.isAbsolute(cwd) ? cwd : void 0;
6149
6426
  const projectConfig = getConfig(projectCwd);
6150
6427
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6151
6428
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6535,8 +6812,8 @@ data: ${JSON.stringify(item.data)}
6535
6812
  const body = await readBody(req);
6536
6813
  const data = body ? JSON.parse(body) : {};
6537
6814
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6538
- const node9Dir = import_path18.default.dirname(GLOBAL_CONFIG_PATH);
6539
- if (!import_path18.default.resolve(configPath).startsWith(node9Dir + import_path18.default.sep)) {
6815
+ const node9Dir = import_path20.default.dirname(GLOBAL_CONFIG_PATH);
6816
+ if (!import_path20.default.resolve(configPath).startsWith(node9Dir + import_path20.default.sep)) {
6540
6817
  res.writeHead(400, { "Content-Type": "application/json" });
6541
6818
  return res.end(
6542
6819
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6647,14 +6924,14 @@ data: ${JSON.stringify(item.data)}
6647
6924
  server.on("error", (e) => {
6648
6925
  if (e.code === "EADDRINUSE") {
6649
6926
  try {
6650
- if (import_fs15.default.existsSync(DAEMON_PID_FILE)) {
6651
- const { pid } = JSON.parse(import_fs15.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6927
+ if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
6928
+ const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6652
6929
  process.kill(pid, 0);
6653
6930
  return process.exit(0);
6654
6931
  }
6655
6932
  } catch {
6656
6933
  try {
6657
- import_fs15.default.unlinkSync(DAEMON_PID_FILE);
6934
+ import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6658
6935
  } catch {
6659
6936
  }
6660
6937
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6713,14 +6990,14 @@ data: ${JSON.stringify(item.data)}
6713
6990
  }
6714
6991
  startActivitySocket();
6715
6992
  }
6716
- var import_http, import_fs15, import_path18, import_crypto6, import_child_process4, import_chalk2;
6993
+ var import_http, import_fs17, import_path20, import_crypto7, import_child_process4, import_chalk2;
6717
6994
  var init_server = __esm({
6718
6995
  "src/daemon/server.ts"() {
6719
6996
  "use strict";
6720
6997
  import_http = __toESM(require("http"));
6721
- import_fs15 = __toESM(require("fs"));
6722
- import_path18 = __toESM(require("path"));
6723
- import_crypto6 = require("crypto");
6998
+ import_fs17 = __toESM(require("fs"));
6999
+ import_path20 = __toESM(require("path"));
7000
+ import_crypto7 = require("crypto");
6724
7001
  import_child_process4 = require("child_process");
6725
7002
  import_chalk2 = __toESM(require("chalk"));
6726
7003
  init_core();
@@ -6729,29 +7006,30 @@ var init_server = __esm({
6729
7006
  init_state2();
6730
7007
  init_patch();
6731
7008
  init_config_schema();
7009
+ init_costSync();
6732
7010
  }
6733
7011
  });
6734
7012
 
6735
7013
  // src/daemon/index.ts
6736
7014
  function stopDaemon() {
6737
- if (!import_fs16.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7015
+ if (!import_fs18.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
6738
7016
  try {
6739
- const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7017
+ const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6740
7018
  process.kill(pid, "SIGTERM");
6741
7019
  console.log(import_chalk3.default.green("\u2705 Stopped."));
6742
7020
  } catch {
6743
7021
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
6744
7022
  } finally {
6745
7023
  try {
6746
- import_fs16.default.unlinkSync(DAEMON_PID_FILE);
7024
+ import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6747
7025
  } catch {
6748
7026
  }
6749
7027
  }
6750
7028
  }
6751
7029
  function daemonStatus() {
6752
- if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
7030
+ if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
6753
7031
  try {
6754
- const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7032
+ const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6755
7033
  process.kill(pid, 0);
6756
7034
  console.log(import_chalk3.default.green("Node9 daemon: running"));
6757
7035
  return;
@@ -6770,11 +7048,11 @@ function daemonStatus() {
6770
7048
  console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
6771
7049
  }
6772
7050
  }
6773
- var import_fs16, import_chalk3, import_child_process5;
7051
+ var import_fs18, import_chalk3, import_child_process5;
6774
7052
  var init_daemon2 = __esm({
6775
7053
  "src/daemon/index.ts"() {
6776
7054
  "use strict";
6777
- import_fs16 = __toESM(require("fs"));
7055
+ import_fs18 = __toESM(require("fs"));
6778
7056
  import_chalk3 = __toESM(require("chalk"));
6779
7057
  import_child_process5 = require("child_process");
6780
7058
  init_server();
@@ -6795,44 +7073,64 @@ function getIcon(tool) {
6795
7073
  }
6796
7074
  return "\u{1F6E0}\uFE0F";
6797
7075
  }
7076
+ function visibleLength(s) {
7077
+ return s.replace(/\x1B\[[0-9;]*m/g, "").length;
7078
+ }
7079
+ function wrappedLineCount(text) {
7080
+ const cols = process.stdout.columns;
7081
+ if (!cols) return 1;
7082
+ const len = visibleLength(text);
7083
+ return Math.max(1, Math.ceil(len / cols));
7084
+ }
6798
7085
  function formatBase(activity) {
6799
7086
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6800
7087
  const icon = getIcon(activity.tool);
6801
7088
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6802
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os22.default.homedir(), "~");
7089
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os25.default.homedir(), "~");
6803
7090
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6804
- return `${import_chalk18.default.gray(time)} ${icon} ${import_chalk18.default.white.bold(toolName)} ${import_chalk18.default.dim(argsPreview)}`;
7091
+ return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
6805
7092
  }
6806
7093
  function renderResult(activity, result) {
6807
7094
  const base = formatBase(activity);
6808
7095
  let status;
6809
7096
  if (result.status === "allow") {
6810
- status = import_chalk18.default.green("\u2713 ALLOW");
7097
+ status = import_chalk19.default.green("\u2713 ALLOW");
6811
7098
  } else if (result.status === "dlp") {
6812
- status = import_chalk18.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7099
+ status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6813
7100
  } else {
6814
- status = import_chalk18.default.red("\u2717 BLOCK");
7101
+ status = import_chalk19.default.red("\u2717 BLOCK");
6815
7102
  }
6816
7103
  const cost = result.costEstimate ?? activity.costEstimate;
6817
- const costSuffix = cost == null ? "" : import_chalk18.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7104
+ const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
6818
7105
  if (process.stdout.isTTY) {
6819
- import_readline5.default.clearLine(process.stdout, 0);
6820
- import_readline5.default.cursorTo(process.stdout, 0);
7106
+ if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7107
+ import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
7108
+ import_readline5.default.cursorTo(process.stdout, 0);
7109
+ process.stdout.write(ERASE_DOWN);
7110
+ } else {
7111
+ import_readline5.default.clearLine(process.stdout, 0);
7112
+ import_readline5.default.cursorTo(process.stdout, 0);
7113
+ }
7114
+ pendingShownForId = null;
7115
+ pendingWrappedLines = 0;
6821
7116
  }
6822
7117
  console.log(`${base} ${status}${costSuffix}`);
6823
7118
  }
6824
7119
  function renderPending(activity) {
6825
7120
  if (!process.stdout.isTTY) return;
6826
- process.stdout.write(`${formatBase(activity)} ${import_chalk18.default.yellow("\u25CF \u2026")}\r`);
7121
+ const line = `${formatBase(activity)} ${import_chalk19.default.yellow("\u25CF \u2026")}`;
7122
+ pendingShownForId = activity.id;
7123
+ pendingWrappedLines = wrappedLineCount(line);
7124
+ process.stdout.write(`${line}\r`);
6827
7125
  }
6828
7126
  async function ensureDaemon() {
6829
7127
  let pidPort = null;
6830
- if (import_fs26.default.existsSync(PID_FILE)) {
7128
+ if (import_fs29.default.existsSync(PID_FILE)) {
6831
7129
  try {
6832
- const { port } = JSON.parse(import_fs26.default.readFileSync(PID_FILE, "utf-8"));
7130
+ const { port } = JSON.parse(import_fs29.default.readFileSync(PID_FILE, "utf-8"));
6833
7131
  pidPort = port;
6834
7132
  } catch {
6835
- console.error(import_chalk18.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7133
+ console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6836
7134
  }
6837
7135
  }
6838
7136
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6843,7 +7141,7 @@ async function ensureDaemon() {
6843
7141
  if (res.ok) return checkPort;
6844
7142
  } catch {
6845
7143
  }
6846
- console.log(import_chalk18.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7144
+ console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6847
7145
  const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
6848
7146
  detached: true,
6849
7147
  stdio: "ignore",
@@ -6860,7 +7158,7 @@ async function ensureDaemon() {
6860
7158
  } catch {
6861
7159
  }
6862
7160
  }
6863
- console.error(import_chalk18.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7161
+ console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6864
7162
  process.exit(1);
6865
7163
  }
6866
7164
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6906,6 +7204,9 @@ function buildCardLines(req, localCount = 0) {
6906
7204
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
6907
7205
  `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
6908
7206
  ];
7207
+ if (req.riskMetadata?.ruleDescription) {
7208
+ lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7209
+ }
6909
7210
  if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
6910
7211
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
6911
7212
  }
@@ -6949,9 +7250,9 @@ function buildRecoveryCardLines(req) {
6949
7250
  ];
6950
7251
  }
6951
7252
  function readApproversFromDisk() {
6952
- const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
7253
+ const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
6953
7254
  try {
6954
- const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
7255
+ const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
6955
7256
  const settings = raw.settings ?? {};
6956
7257
  return settings.approvers ?? {};
6957
7258
  } catch {
@@ -6962,20 +7263,20 @@ function approverStatusLine() {
6962
7263
  const a = readApproversFromDisk();
6963
7264
  const fmt = (label, key) => {
6964
7265
  const on = a[key] !== false;
6965
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk18.default.green("\u2713") : import_chalk18.default.dim("\u2717")}`;
7266
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk19.default.green("\u2713") : import_chalk19.default.dim("\u2717")}`;
6966
7267
  };
6967
7268
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
6968
7269
  }
6969
7270
  function toggleApprover(channel) {
6970
- const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
7271
+ const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
6971
7272
  try {
6972
- const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
7273
+ const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
6973
7274
  const settings = raw.settings ?? {};
6974
7275
  const approvers = settings.approvers ?? {};
6975
7276
  approvers[channel] = approvers[channel] === false;
6976
7277
  settings.approvers = approvers;
6977
7278
  raw.settings = settings;
6978
- import_fs26.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7279
+ import_fs29.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
6979
7280
  } catch (err2) {
6980
7281
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
6981
7282
  `);
@@ -7007,7 +7308,7 @@ async function startTail(options = {}) {
7007
7308
  req2.end();
7008
7309
  });
7009
7310
  if (result.ok) {
7010
- console.log(import_chalk18.default.green("\u2713 Flight Recorder buffer cleared."));
7311
+ console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
7011
7312
  } else if (result.code === "ECONNREFUSED") {
7012
7313
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7013
7314
  } else if (result.code === "ETIMEDOUT") {
@@ -7051,7 +7352,7 @@ async function startTail(options = {}) {
7051
7352
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7052
7353
  if (channel) {
7053
7354
  toggleApprover(channel);
7054
- console.log(import_chalk18.default.dim(` Approvers: ${approverStatusLine()}`));
7355
+ console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
7055
7356
  }
7056
7357
  };
7057
7358
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7117,7 +7418,7 @@ async function startTail(options = {}) {
7117
7418
  localAllowCounts.get(req2.toolName) ?? 0
7118
7419
  )
7119
7420
  );
7120
- const decisionStamp = action === "always-allow" ? import_chalk18.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk18.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk18.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk18.default.yellow("\u21A9 REDIRECT AI") : import_chalk18.default.red("\u2717 DENIED");
7421
+ const decisionStamp = action === "always-allow" ? import_chalk19.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk19.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk19.default.yellow("\u21A9 REDIRECT AI") : import_chalk19.default.red("\u2717 DENIED");
7121
7422
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7122
7423
  for (const line of stampedLines) process.stdout.write(line + "\n");
7123
7424
  process.stdout.write(SHOW_CURSOR);
@@ -7145,8 +7446,8 @@ async function startTail(options = {}) {
7145
7446
  }
7146
7447
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7147
7448
  try {
7148
- import_fs26.default.appendFileSync(
7149
- import_path29.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log"),
7449
+ import_fs29.default.appendFileSync(
7450
+ import_path32.default.join(import_os25.default.homedir(), ".node9", "hook-debug.log"),
7150
7451
  `[tail] POST /decision failed: ${String(err2)}
7151
7452
  `
7152
7453
  );
@@ -7168,7 +7469,7 @@ async function startTail(options = {}) {
7168
7469
  );
7169
7470
  const stampedLines = buildCardLines(req2, priorCount);
7170
7471
  if (externalDecision) {
7171
- const source = externalDecision === "allow" ? import_chalk18.default.green("\u2713 ALLOWED") : import_chalk18.default.red("\u2717 DENIED");
7472
+ const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
7172
7473
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7173
7474
  }
7174
7475
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7227,16 +7528,16 @@ async function startTail(options = {}) {
7227
7528
  }
7228
7529
  } catch {
7229
7530
  }
7230
- console.log(import_chalk18.default.cyan.bold(`
7231
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk18.default.dim(`\u2192 ${dashboardUrl}`));
7531
+ console.log(import_chalk19.default.cyan.bold(`
7532
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk19.default.dim(`\u2192 ${dashboardUrl}`));
7232
7533
  if (canApprove) {
7233
- console.log(import_chalk18.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7234
- console.log(import_chalk18.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7534
+ console.log(import_chalk19.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7535
+ console.log(import_chalk19.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7235
7536
  }
7236
7537
  if (options.history) {
7237
- console.log(import_chalk18.default.dim("Showing history + live events.\n"));
7538
+ console.log(import_chalk19.default.dim("Showing history + live events.\n"));
7238
7539
  } else {
7239
- console.log(import_chalk18.default.dim("Showing live events only. Use --history to include past.\n"));
7540
+ console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
7240
7541
  }
7241
7542
  process.on("SIGINT", () => {
7242
7543
  exitIdleMode();
@@ -7246,13 +7547,13 @@ async function startTail(options = {}) {
7246
7547
  import_readline5.default.clearLine(process.stdout, 0);
7247
7548
  import_readline5.default.cursorTo(process.stdout, 0);
7248
7549
  }
7249
- console.log(import_chalk18.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7550
+ console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7250
7551
  process.exit(0);
7251
7552
  });
7252
7553
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7253
7554
  const req = import_http2.default.get(sseUrl, (res) => {
7254
7555
  if (res.statusCode !== 200) {
7255
- console.error(import_chalk18.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7556
+ console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7256
7557
  process.exit(1);
7257
7558
  }
7258
7559
  if (canApprove) enterIdleMode();
@@ -7283,7 +7584,7 @@ async function startTail(options = {}) {
7283
7584
  import_readline5.default.clearLine(process.stdout, 0);
7284
7585
  import_readline5.default.cursorTo(process.stdout, 0);
7285
7586
  }
7286
- console.log(import_chalk18.default.red("\n\u274C Daemon disconnected."));
7587
+ console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
7287
7588
  process.exit(1);
7288
7589
  });
7289
7590
  });
@@ -7375,9 +7676,9 @@ async function startTail(options = {}) {
7375
7676
  const hash = data.hash ?? "";
7376
7677
  const summary = data.argsSummary ?? data.tool;
7377
7678
  const fileCount = data.fileCount ?? 0;
7378
- const files = fileCount > 0 ? import_chalk18.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7679
+ const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7379
7680
  process.stdout.write(
7380
- `${import_chalk18.default.dim(time)} ${import_chalk18.default.cyan("\u{1F4F8} snapshot")} ${import_chalk18.default.dim(hash)} ${summary}${files}
7681
+ `${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
7381
7682
  `
7382
7683
  );
7383
7684
  return;
@@ -7394,26 +7695,26 @@ async function startTail(options = {}) {
7394
7695
  }
7395
7696
  req.on("error", (err2) => {
7396
7697
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7397
- console.error(import_chalk18.default.red(`
7698
+ console.error(import_chalk19.default.red(`
7398
7699
  \u274C ${msg}`));
7399
7700
  process.exit(1);
7400
7701
  });
7401
7702
  }
7402
- var import_http2, import_chalk18, import_fs26, import_os22, import_path29, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
7703
+ var import_http2, import_chalk19, import_fs29, import_os25, import_path32, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7403
7704
  var init_tail = __esm({
7404
7705
  "src/tui/tail.ts"() {
7405
7706
  "use strict";
7406
7707
  import_http2 = __toESM(require("http"));
7407
- import_chalk18 = __toESM(require("chalk"));
7408
- import_fs26 = __toESM(require("fs"));
7409
- import_os22 = __toESM(require("os"));
7410
- import_path29 = __toESM(require("path"));
7708
+ import_chalk19 = __toESM(require("chalk"));
7709
+ import_fs29 = __toESM(require("fs"));
7710
+ import_os25 = __toESM(require("os"));
7711
+ import_path32 = __toESM(require("path"));
7411
7712
  import_readline5 = __toESM(require("readline"));
7412
7713
  import_child_process14 = require("child_process");
7413
7714
  init_daemon2();
7414
7715
  init_daemon();
7415
7716
  init_core();
7416
- PID_FILE = import_path29.default.join(import_os22.default.homedir(), ".node9", "daemon.pid");
7717
+ PID_FILE = import_path32.default.join(import_os25.default.homedir(), ".node9", "daemon.pid");
7417
7718
  ICONS = {
7418
7719
  bash: "\u{1F4BB}",
7419
7720
  shell: "\u{1F4BB}",
@@ -7441,6 +7742,8 @@ var init_tail = __esm({
7441
7742
  HIDE_CURSOR = "\x1B[?25l";
7442
7743
  SHOW_CURSOR = "\x1B[?25h";
7443
7744
  ERASE_DOWN = "\x1B[J";
7745
+ pendingShownForId = null;
7746
+ pendingWrappedLines = 0;
7444
7747
  DIVIDER = "\u2500".repeat(60);
7445
7748
  }
7446
7749
  });
@@ -7509,10 +7812,10 @@ function bold(s) {
7509
7812
  function color(c, s) {
7510
7813
  return `${c}${s}${RESET3}`;
7511
7814
  }
7512
- function progressBar(pct, warnAt = 70, critAt = 85) {
7513
- const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
7815
+ function progressBar(pct2, warnAt = 70, critAt = 85) {
7816
+ const filled = Math.round(Math.min(pct2, 100) / 100 * BAR_WIDTH);
7514
7817
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7515
- const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7818
+ const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
7516
7819
  return `${c}${bar}${RESET3}`;
7517
7820
  }
7518
7821
  function formatTimeLeft(resetsAt) {
@@ -7526,9 +7829,9 @@ function formatTimeLeft(resetsAt) {
7526
7829
  return ` (${m}m left)`;
7527
7830
  }
7528
7831
  function safeReadJson(filePath) {
7529
- if (!import_fs27.default.existsSync(filePath)) return null;
7832
+ if (!import_fs30.default.existsSync(filePath)) return null;
7530
7833
  try {
7531
- return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
7834
+ return JSON.parse(import_fs30.default.readFileSync(filePath, "utf-8"));
7532
7835
  } catch {
7533
7836
  return null;
7534
7837
  }
@@ -7549,12 +7852,12 @@ function countHooksInFile(filePath) {
7549
7852
  return Object.keys(cfg.hooks).length;
7550
7853
  }
7551
7854
  function countRulesInDir(rulesDir) {
7552
- if (!import_fs27.default.existsSync(rulesDir)) return 0;
7855
+ if (!import_fs30.default.existsSync(rulesDir)) return 0;
7553
7856
  let count = 0;
7554
7857
  try {
7555
- for (const entry of import_fs27.default.readdirSync(rulesDir, { withFileTypes: true })) {
7858
+ for (const entry of import_fs30.default.readdirSync(rulesDir, { withFileTypes: true })) {
7556
7859
  if (entry.isDirectory()) {
7557
- count += countRulesInDir(import_path30.default.join(rulesDir, entry.name));
7860
+ count += countRulesInDir(import_path33.default.join(rulesDir, entry.name));
7558
7861
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7559
7862
  count++;
7560
7863
  }
@@ -7565,46 +7868,46 @@ function countRulesInDir(rulesDir) {
7565
7868
  }
7566
7869
  function isSamePath(a, b) {
7567
7870
  try {
7568
- return import_path30.default.resolve(a) === import_path30.default.resolve(b);
7871
+ return import_path33.default.resolve(a) === import_path33.default.resolve(b);
7569
7872
  } catch {
7570
7873
  return false;
7571
7874
  }
7572
7875
  }
7573
7876
  function countConfigs(cwd) {
7574
- const homeDir2 = import_os23.default.homedir();
7575
- const claudeDir = import_path30.default.join(homeDir2, ".claude");
7877
+ const homeDir2 = import_os26.default.homedir();
7878
+ const claudeDir = import_path33.default.join(homeDir2, ".claude");
7576
7879
  let claudeMdCount = 0;
7577
7880
  let rulesCount = 0;
7578
7881
  let hooksCount = 0;
7579
7882
  const userMcpServers = /* @__PURE__ */ new Set();
7580
7883
  const projectMcpServers = /* @__PURE__ */ new Set();
7581
- if (import_fs27.default.existsSync(import_path30.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7582
- rulesCount += countRulesInDir(import_path30.default.join(claudeDir, "rules"));
7583
- const userSettings = import_path30.default.join(claudeDir, "settings.json");
7884
+ if (import_fs30.default.existsSync(import_path33.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7885
+ rulesCount += countRulesInDir(import_path33.default.join(claudeDir, "rules"));
7886
+ const userSettings = import_path33.default.join(claudeDir, "settings.json");
7584
7887
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7585
7888
  hooksCount += countHooksInFile(userSettings);
7586
- const userClaudeJson = import_path30.default.join(homeDir2, ".claude.json");
7889
+ const userClaudeJson = import_path33.default.join(homeDir2, ".claude.json");
7587
7890
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7588
7891
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7589
7892
  userMcpServers.delete(name);
7590
7893
  }
7591
7894
  if (cwd) {
7592
- if (import_fs27.default.existsSync(import_path30.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7593
- if (import_fs27.default.existsSync(import_path30.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7594
- const projectClaudeDir = import_path30.default.join(cwd, ".claude");
7895
+ if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7896
+ if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7897
+ const projectClaudeDir = import_path33.default.join(cwd, ".claude");
7595
7898
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7596
7899
  if (!overlapsUserScope) {
7597
- if (import_fs27.default.existsSync(import_path30.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7598
- rulesCount += countRulesInDir(import_path30.default.join(projectClaudeDir, "rules"));
7599
- const projSettings = import_path30.default.join(projectClaudeDir, "settings.json");
7900
+ if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7901
+ rulesCount += countRulesInDir(import_path33.default.join(projectClaudeDir, "rules"));
7902
+ const projSettings = import_path33.default.join(projectClaudeDir, "settings.json");
7600
7903
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7601
7904
  hooksCount += countHooksInFile(projSettings);
7602
7905
  }
7603
- if (import_fs27.default.existsSync(import_path30.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7604
- const localSettings = import_path30.default.join(projectClaudeDir, "settings.local.json");
7906
+ if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7907
+ const localSettings = import_path33.default.join(projectClaudeDir, "settings.local.json");
7605
7908
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7606
7909
  hooksCount += countHooksInFile(localSettings);
7607
- const mcpJsonServers = getMcpServerNames(import_path30.default.join(cwd, ".mcp.json"));
7910
+ const mcpJsonServers = getMcpServerNames(import_path33.default.join(cwd, ".mcp.json"));
7608
7911
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7609
7912
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7610
7913
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7637,12 +7940,12 @@ function readActiveShieldsHud() {
7637
7940
  return shieldsCache.value;
7638
7941
  }
7639
7942
  try {
7640
- const shieldsPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "shields.json");
7641
- if (!import_fs27.default.existsSync(shieldsPath)) {
7943
+ const shieldsPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "shields.json");
7944
+ if (!import_fs30.default.existsSync(shieldsPath)) {
7642
7945
  shieldsCache = { value: [], ts: now };
7643
7946
  return [];
7644
7947
  }
7645
- const parsed = JSON.parse(import_fs27.default.readFileSync(shieldsPath, "utf-8"));
7948
+ const parsed = JSON.parse(import_fs30.default.readFileSync(shieldsPath, "utf-8"));
7646
7949
  if (!Array.isArray(parsed.active)) {
7647
7950
  shieldsCache = { value: [], ts: now };
7648
7951
  return [];
@@ -7728,15 +8031,15 @@ function renderContextLine(stdin) {
7728
8031
  }
7729
8032
  const rl = stdin.rate_limits;
7730
8033
  if (rl?.five_hour?.used_percentage !== void 0) {
7731
- const pct = Math.round(rl.five_hour.used_percentage);
7732
- const bar = progressBar(pct, 60, 80);
8034
+ const pct2 = Math.round(rl.five_hour.used_percentage);
8035
+ const bar = progressBar(pct2, 60, 80);
7733
8036
  const left = formatTimeLeft(rl.five_hour.resets_at);
7734
- parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
8037
+ parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
7735
8038
  }
7736
8039
  if (rl?.seven_day?.used_percentage !== void 0) {
7737
- const pct = Math.round(rl.seven_day.used_percentage);
7738
- const bar = progressBar(pct, 60, 80);
7739
- parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
8040
+ const pct2 = Math.round(rl.seven_day.used_percentage);
8041
+ const bar = progressBar(pct2, 60, 80);
8042
+ parts.push(`${dim("\u2502")} 7d ${bar} ${pct2}%`);
7740
8043
  }
7741
8044
  if (parts.length === 0) return null;
7742
8045
  return parts.join(" ");
@@ -7744,17 +8047,17 @@ function renderContextLine(stdin) {
7744
8047
  async function main() {
7745
8048
  try {
7746
8049
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7747
- if (import_fs27.default.existsSync(import_path30.default.join(import_os23.default.homedir(), ".node9", "hud-debug"))) {
8050
+ if (import_fs30.default.existsSync(import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug"))) {
7748
8051
  try {
7749
- const logPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "hud-debug.log");
8052
+ const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug.log");
7750
8053
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7751
8054
  let size = 0;
7752
8055
  try {
7753
- size = import_fs27.default.statSync(logPath).size;
8056
+ size = import_fs30.default.statSync(logPath).size;
7754
8057
  } catch {
7755
8058
  }
7756
8059
  if (size < MAX_LOG_SIZE) {
7757
- import_fs27.default.appendFileSync(
8060
+ import_fs30.default.appendFileSync(
7758
8061
  logPath,
7759
8062
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7760
8063
  );
@@ -7775,11 +8078,11 @@ async function main() {
7775
8078
  try {
7776
8079
  const cwd = stdin.cwd ?? process.cwd();
7777
8080
  for (const configPath of [
7778
- import_path30.default.join(cwd, "node9.config.json"),
7779
- import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json")
8081
+ import_path33.default.join(cwd, "node9.config.json"),
8082
+ import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json")
7780
8083
  ]) {
7781
- if (!import_fs27.default.existsSync(configPath)) continue;
7782
- const cfg = JSON.parse(import_fs27.default.readFileSync(configPath, "utf-8"));
8084
+ if (!import_fs30.default.existsSync(configPath)) continue;
8085
+ const cfg = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
7783
8086
  const hud = cfg.settings?.hud;
7784
8087
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7785
8088
  }
@@ -7797,13 +8100,13 @@ async function main() {
7797
8100
  renderOffline();
7798
8101
  }
7799
8102
  }
7800
- var import_fs27, import_path30, import_os23, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8103
+ var import_fs30, import_path33, import_os26, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
7801
8104
  var init_hud = __esm({
7802
8105
  "src/cli/hud.ts"() {
7803
8106
  "use strict";
7804
- import_fs27 = __toESM(require("fs"));
7805
- import_path30 = __toESM(require("path"));
7806
- import_os23 = __toESM(require("os"));
8107
+ import_fs30 = __toESM(require("fs"));
8108
+ import_path33 = __toESM(require("path"));
8109
+ import_os26 = __toESM(require("os"));
7807
8110
  import_http3 = __toESM(require("http"));
7808
8111
  init_daemon();
7809
8112
  RESET3 = "\x1B[0m";
@@ -7829,9 +8132,9 @@ var import_commander = require("commander");
7829
8132
  init_core();
7830
8133
 
7831
8134
  // src/setup.ts
7832
- var import_fs11 = __toESM(require("fs"));
7833
- var import_path14 = __toESM(require("path"));
7834
- var import_os10 = __toESM(require("os"));
8135
+ var import_fs12 = __toESM(require("fs"));
8136
+ var import_path15 = __toESM(require("path"));
8137
+ var import_os11 = __toESM(require("os"));
7835
8138
  var import_chalk = __toESM(require("chalk"));
7836
8139
  var import_prompts = require("@inquirer/prompts");
7837
8140
  var import_smol_toml = require("smol-toml");
@@ -7859,26 +8162,26 @@ function fullPathCommand(subcommand) {
7859
8162
  }
7860
8163
  function readJson(filePath) {
7861
8164
  try {
7862
- if (import_fs11.default.existsSync(filePath)) {
7863
- return JSON.parse(import_fs11.default.readFileSync(filePath, "utf-8"));
8165
+ if (import_fs12.default.existsSync(filePath)) {
8166
+ return JSON.parse(import_fs12.default.readFileSync(filePath, "utf-8"));
7864
8167
  }
7865
8168
  } catch {
7866
8169
  }
7867
8170
  return null;
7868
8171
  }
7869
8172
  function writeJson(filePath, data) {
7870
- const dir = import_path14.default.dirname(filePath);
7871
- if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
7872
- import_fs11.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
8173
+ const dir = import_path15.default.dirname(filePath);
8174
+ if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
8175
+ import_fs12.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7873
8176
  }
7874
8177
  function isNode9Hook(cmd) {
7875
8178
  if (!cmd) return false;
7876
8179
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
7877
8180
  }
7878
8181
  function teardownClaude() {
7879
- const homeDir2 = import_os10.default.homedir();
7880
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
7881
- const mcpPath = import_path14.default.join(homeDir2, ".claude", ".mcp.json");
8182
+ const homeDir2 = import_os11.default.homedir();
8183
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8184
+ const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
7882
8185
  let changed = false;
7883
8186
  const settings = readJson(hooksPath);
7884
8187
  if (settings?.hooks) {
@@ -7926,8 +8229,8 @@ function teardownClaude() {
7926
8229
  }
7927
8230
  }
7928
8231
  function teardownGemini() {
7929
- const homeDir2 = import_os10.default.homedir();
7930
- const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
8232
+ const homeDir2 = import_os11.default.homedir();
8233
+ const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
7931
8234
  const settings = readJson(settingsPath);
7932
8235
  if (!settings) {
7933
8236
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7970,8 +8273,8 @@ function teardownGemini() {
7970
8273
  }
7971
8274
  }
7972
8275
  function teardownCursor() {
7973
- const homeDir2 = import_os10.default.homedir();
7974
- const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
8276
+ const homeDir2 = import_os11.default.homedir();
8277
+ const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
7975
8278
  const mcpConfig = readJson(mcpPath);
7976
8279
  if (!mcpConfig?.mcpServers) {
7977
8280
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -8002,9 +8305,9 @@ function teardownCursor() {
8002
8305
  }
8003
8306
  }
8004
8307
  async function setupClaude() {
8005
- const homeDir2 = import_os10.default.homedir();
8006
- const mcpPath = import_path14.default.join(homeDir2, ".claude", ".mcp.json");
8007
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8308
+ const homeDir2 = import_os11.default.homedir();
8309
+ const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
8310
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8008
8311
  const claudeConfig = readJson(mcpPath) ?? {};
8009
8312
  const settings = readJson(hooksPath) ?? {};
8010
8313
  const servers = claudeConfig.mcpServers ?? {};
@@ -8101,8 +8404,8 @@ async function setupClaude() {
8101
8404
  }
8102
8405
  }
8103
8406
  async function setupGemini() {
8104
- const homeDir2 = import_os10.default.homedir();
8105
- const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
8407
+ const homeDir2 = import_os11.default.homedir();
8408
+ const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
8106
8409
  const settings = readJson(settingsPath) ?? {};
8107
8410
  const servers = settings.mcpServers ?? {};
8108
8411
  let hooksChanged = false;
@@ -8197,10 +8500,10 @@ async function setupGemini() {
8197
8500
  printDaemonTip();
8198
8501
  }
8199
8502
  }
8200
- function detectAgents(homeDir2 = import_os10.default.homedir()) {
8503
+ function detectAgents(homeDir2 = import_os11.default.homedir()) {
8201
8504
  const exists = (p) => {
8202
8505
  try {
8203
- return import_fs11.default.existsSync(p);
8506
+ return import_fs12.default.existsSync(p);
8204
8507
  } catch (err2) {
8205
8508
  const code = err2.code;
8206
8509
  if (code !== "ENOENT") {
@@ -8211,15 +8514,15 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
8211
8514
  }
8212
8515
  };
8213
8516
  return {
8214
- claude: exists(import_path14.default.join(homeDir2, ".claude")) || exists(import_path14.default.join(homeDir2, ".claude.json")),
8215
- gemini: exists(import_path14.default.join(homeDir2, ".gemini")),
8216
- cursor: exists(import_path14.default.join(homeDir2, ".cursor")),
8217
- codex: exists(import_path14.default.join(homeDir2, ".codex"))
8517
+ claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
8518
+ gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
8519
+ cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
8520
+ codex: exists(import_path15.default.join(homeDir2, ".codex"))
8218
8521
  };
8219
8522
  }
8220
8523
  async function setupCursor() {
8221
- const homeDir2 = import_os10.default.homedir();
8222
- const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
8524
+ const homeDir2 = import_os11.default.homedir();
8525
+ const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
8223
8526
  const mcpConfig = readJson(mcpPath) ?? {};
8224
8527
  const servers = mcpConfig.mcpServers ?? {};
8225
8528
  let anythingChanged = false;
@@ -8285,21 +8588,21 @@ async function setupCursor() {
8285
8588
  }
8286
8589
  function readToml(filePath) {
8287
8590
  try {
8288
- if (import_fs11.default.existsSync(filePath)) {
8289
- return (0, import_smol_toml.parse)(import_fs11.default.readFileSync(filePath, "utf-8"));
8591
+ if (import_fs12.default.existsSync(filePath)) {
8592
+ return (0, import_smol_toml.parse)(import_fs12.default.readFileSync(filePath, "utf-8"));
8290
8593
  }
8291
8594
  } catch {
8292
8595
  }
8293
8596
  return null;
8294
8597
  }
8295
8598
  function writeToml(filePath, data) {
8296
- const dir = import_path14.default.dirname(filePath);
8297
- if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
8298
- import_fs11.default.writeFileSync(filePath, (0, import_smol_toml.stringify)(data));
8599
+ const dir = import_path15.default.dirname(filePath);
8600
+ if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
8601
+ import_fs12.default.writeFileSync(filePath, (0, import_smol_toml.stringify)(data));
8299
8602
  }
8300
8603
  async function setupCodex() {
8301
- const homeDir2 = import_os10.default.homedir();
8302
- const configPath = import_path14.default.join(homeDir2, ".codex", "config.toml");
8604
+ const homeDir2 = import_os11.default.homedir();
8605
+ const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
8303
8606
  const config = readToml(configPath) ?? {};
8304
8607
  const servers = config.mcp_servers ?? {};
8305
8608
  let anythingChanged = false;
@@ -8364,8 +8667,8 @@ async function setupCodex() {
8364
8667
  }
8365
8668
  }
8366
8669
  function setupHud() {
8367
- const homeDir2 = import_os10.default.homedir();
8368
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8670
+ const homeDir2 = import_os11.default.homedir();
8671
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8369
8672
  const settings = readJson(hooksPath) ?? {};
8370
8673
  const hudCommand = fullPathCommand("hud");
8371
8674
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8391,8 +8694,8 @@ function setupHud() {
8391
8694
  console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
8392
8695
  }
8393
8696
  function teardownHud() {
8394
- const homeDir2 = import_os10.default.homedir();
8395
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8697
+ const homeDir2 = import_os11.default.homedir();
8698
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8396
8699
  const settings = readJson(hooksPath);
8397
8700
  if (!settings) {
8398
8701
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8412,10 +8715,10 @@ function teardownHud() {
8412
8715
 
8413
8716
  // src/cli.ts
8414
8717
  init_daemon2();
8415
- var import_chalk19 = __toESM(require("chalk"));
8416
- var import_fs28 = __toESM(require("fs"));
8417
- var import_path31 = __toESM(require("path"));
8418
- var import_os24 = __toESM(require("os"));
8718
+ var import_chalk20 = __toESM(require("chalk"));
8719
+ var import_fs31 = __toESM(require("fs"));
8720
+ var import_path34 = __toESM(require("path"));
8721
+ var import_os27 = __toESM(require("os"));
8419
8722
  var import_prompts2 = require("@inquirer/prompts");
8420
8723
 
8421
8724
  // src/utils/duration.ts
@@ -8640,10 +8943,10 @@ async function autoStartDaemonAndWait() {
8640
8943
 
8641
8944
  // src/cli/commands/check.ts
8642
8945
  var import_chalk5 = __toESM(require("chalk"));
8643
- var import_fs18 = __toESM(require("fs"));
8946
+ var import_fs20 = __toESM(require("fs"));
8644
8947
  var import_child_process9 = require("child_process");
8645
- var import_path20 = __toESM(require("path"));
8646
- var import_os14 = __toESM(require("os"));
8948
+ var import_path22 = __toESM(require("path"));
8949
+ var import_os16 = __toESM(require("os"));
8647
8950
  init_orchestrator();
8648
8951
  init_daemon();
8649
8952
  init_config();
@@ -8651,12 +8954,12 @@ init_policy();
8651
8954
 
8652
8955
  // src/undo.ts
8653
8956
  var import_child_process8 = require("child_process");
8654
- var import_crypto7 = __toESM(require("crypto"));
8655
- var import_fs17 = __toESM(require("fs"));
8957
+ var import_crypto8 = __toESM(require("crypto"));
8958
+ var import_fs19 = __toESM(require("fs"));
8656
8959
  var import_net3 = __toESM(require("net"));
8657
- var import_path19 = __toESM(require("path"));
8658
- var import_os13 = __toESM(require("os"));
8659
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path19.default.join(import_os13.default.tmpdir(), "node9-activity.sock");
8960
+ var import_path21 = __toESM(require("path"));
8961
+ var import_os15 = __toESM(require("os"));
8962
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path21.default.join(import_os15.default.tmpdir(), "node9-activity.sock");
8660
8963
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8661
8964
  try {
8662
8965
  const payload = JSON.stringify({
@@ -8676,22 +8979,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8676
8979
  } catch {
8677
8980
  }
8678
8981
  }
8679
- var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
8680
- var UNDO_LATEST_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
8982
+ var SNAPSHOT_STACK_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots.json");
8983
+ var UNDO_LATEST_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "undo_latest.txt");
8681
8984
  var MAX_SNAPSHOTS = 10;
8682
8985
  var GIT_TIMEOUT = 15e3;
8683
8986
  function readStack() {
8684
8987
  try {
8685
- if (import_fs17.default.existsSync(SNAPSHOT_STACK_PATH))
8686
- return JSON.parse(import_fs17.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8988
+ if (import_fs19.default.existsSync(SNAPSHOT_STACK_PATH))
8989
+ return JSON.parse(import_fs19.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8687
8990
  } catch {
8688
8991
  }
8689
8992
  return [];
8690
8993
  }
8691
8994
  function writeStack(stack) {
8692
- const dir = import_path19.default.dirname(SNAPSHOT_STACK_PATH);
8693
- if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
8694
- import_fs17.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8995
+ const dir = import_path21.default.dirname(SNAPSHOT_STACK_PATH);
8996
+ if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
8997
+ import_fs19.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8695
8998
  }
8696
8999
  function extractFilePath(args) {
8697
9000
  if (!args || typeof args !== "object") return null;
@@ -8711,12 +9014,12 @@ function buildArgsSummary(tool, args) {
8711
9014
  return "";
8712
9015
  }
8713
9016
  function findProjectRoot(filePath) {
8714
- let dir = import_path19.default.dirname(filePath);
9017
+ let dir = import_path21.default.dirname(filePath);
8715
9018
  while (true) {
8716
- if (import_fs17.default.existsSync(import_path19.default.join(dir, ".git")) || import_fs17.default.existsSync(import_path19.default.join(dir, "package.json"))) {
9019
+ if (import_fs19.default.existsSync(import_path21.default.join(dir, ".git")) || import_fs19.default.existsSync(import_path21.default.join(dir, "package.json"))) {
8717
9020
  return dir;
8718
9021
  }
8719
- const parent = import_path19.default.dirname(dir);
9022
+ const parent = import_path21.default.dirname(dir);
8720
9023
  if (parent === dir) return process.cwd();
8721
9024
  dir = parent;
8722
9025
  }
@@ -8724,7 +9027,7 @@ function findProjectRoot(filePath) {
8724
9027
  function normalizeCwdForHash(cwd) {
8725
9028
  let normalized;
8726
9029
  try {
8727
- normalized = import_fs17.default.realpathSync(cwd);
9030
+ normalized = import_fs19.default.realpathSync(cwd);
8728
9031
  } catch {
8729
9032
  normalized = cwd;
8730
9033
  }
@@ -8733,17 +9036,17 @@ function normalizeCwdForHash(cwd) {
8733
9036
  return normalized;
8734
9037
  }
8735
9038
  function getShadowRepoDir(cwd) {
8736
- const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8737
- return import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
9039
+ const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
9040
+ return import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots", hash);
8738
9041
  }
8739
9042
  function cleanOrphanedIndexFiles(shadowDir) {
8740
9043
  try {
8741
9044
  const cutoff = Date.now() - 6e4;
8742
- for (const f of import_fs17.default.readdirSync(shadowDir)) {
9045
+ for (const f of import_fs19.default.readdirSync(shadowDir)) {
8743
9046
  if (f.startsWith("index_")) {
8744
- const fp = import_path19.default.join(shadowDir, f);
9047
+ const fp = import_path21.default.join(shadowDir, f);
8745
9048
  try {
8746
- if (import_fs17.default.statSync(fp).mtimeMs < cutoff) import_fs17.default.unlinkSync(fp);
9049
+ if (import_fs19.default.statSync(fp).mtimeMs < cutoff) import_fs19.default.unlinkSync(fp);
8747
9050
  } catch {
8748
9051
  }
8749
9052
  }
@@ -8755,7 +9058,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8755
9058
  const hardcoded = [".git", ".node9"];
8756
9059
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8757
9060
  try {
8758
- import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9061
+ import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8759
9062
  } catch {
8760
9063
  }
8761
9064
  }
@@ -8768,25 +9071,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8768
9071
  timeout: 3e3
8769
9072
  });
8770
9073
  if (check.status === 0) {
8771
- const ptPath = import_path19.default.join(shadowDir, "project-path.txt");
9074
+ const ptPath = import_path21.default.join(shadowDir, "project-path.txt");
8772
9075
  try {
8773
- const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
9076
+ const stored = import_fs19.default.readFileSync(ptPath, "utf8").trim();
8774
9077
  if (stored === normalizedCwd) return true;
8775
9078
  if (process.env.NODE9_DEBUG === "1")
8776
9079
  console.error(
8777
9080
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8778
9081
  );
8779
- import_fs17.default.rmSync(shadowDir, { recursive: true, force: true });
9082
+ import_fs19.default.rmSync(shadowDir, { recursive: true, force: true });
8780
9083
  } catch {
8781
9084
  try {
8782
- import_fs17.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9085
+ import_fs19.default.writeFileSync(ptPath, normalizedCwd, "utf8");
8783
9086
  } catch {
8784
9087
  }
8785
9088
  return true;
8786
9089
  }
8787
9090
  }
8788
9091
  try {
8789
- import_fs17.default.mkdirSync(shadowDir, { recursive: true });
9092
+ import_fs19.default.mkdirSync(shadowDir, { recursive: true });
8790
9093
  } catch {
8791
9094
  }
8792
9095
  const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8795,7 +9098,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8795
9098
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8796
9099
  return false;
8797
9100
  }
8798
- const configFile = import_path19.default.join(shadowDir, "config");
9101
+ const configFile = import_path21.default.join(shadowDir, "config");
8799
9102
  (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8800
9103
  timeout: 3e3
8801
9104
  });
@@ -8803,7 +9106,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8803
9106
  timeout: 3e3
8804
9107
  });
8805
9108
  try {
8806
- import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9109
+ import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8807
9110
  } catch {
8808
9111
  }
8809
9112
  return true;
@@ -8823,12 +9126,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8823
9126
  let indexFile = null;
8824
9127
  try {
8825
9128
  const rawFilePath = extractFilePath(args);
8826
- const absFilePath = rawFilePath && import_path19.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9129
+ const absFilePath = rawFilePath && import_path21.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8827
9130
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8828
9131
  const shadowDir = getShadowRepoDir(cwd);
8829
9132
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8830
9133
  writeShadowExcludes(shadowDir, ignorePaths);
8831
- indexFile = import_path19.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9134
+ indexFile = import_path21.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8832
9135
  const shadowEnv = {
8833
9136
  ...process.env,
8834
9137
  GIT_DIR: shadowDir,
@@ -8900,7 +9203,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8900
9203
  writeStack(stack);
8901
9204
  const entry = stack[stack.length - 1];
8902
9205
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8903
- import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9206
+ import_fs19.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
8904
9207
  if (shouldGc) {
8905
9208
  (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8906
9209
  }
@@ -8911,7 +9214,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8911
9214
  } finally {
8912
9215
  if (indexFile) {
8913
9216
  try {
8914
- import_fs17.default.unlinkSync(indexFile);
9217
+ import_fs19.default.unlinkSync(indexFile);
8915
9218
  } catch {
8916
9219
  }
8917
9220
  }
@@ -8987,9 +9290,9 @@ function applyUndo(hash, cwd) {
8987
9290
  timeout: GIT_TIMEOUT
8988
9291
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8989
9292
  for (const file of [...tracked, ...untracked]) {
8990
- const fullPath = import_path19.default.join(dir, file);
8991
- if (!snapshotFiles.has(file) && import_fs17.default.existsSync(fullPath)) {
8992
- import_fs17.default.unlinkSync(fullPath);
9293
+ const fullPath = import_path21.default.join(dir, file);
9294
+ if (!snapshotFiles.has(file) && import_fs19.default.existsSync(fullPath)) {
9295
+ import_fs19.default.unlinkSync(fullPath);
8993
9296
  }
8994
9297
  }
8995
9298
  return true;
@@ -9013,9 +9316,9 @@ function registerCheckCommand(program2) {
9013
9316
  } catch (err2) {
9014
9317
  const tempConfig = getConfig();
9015
9318
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9016
- const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
9319
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9017
9320
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9018
- import_fs18.default.appendFileSync(
9321
+ import_fs20.default.appendFileSync(
9019
9322
  logPath,
9020
9323
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9021
9324
  RAW: ${raw}
@@ -9028,10 +9331,10 @@ RAW: ${raw}
9028
9331
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9029
9332
  try {
9030
9333
  const scriptPath = process.argv[1];
9031
- if (typeof scriptPath !== "string" || !import_path20.default.isAbsolute(scriptPath))
9334
+ if (typeof scriptPath !== "string" || !import_path22.default.isAbsolute(scriptPath))
9032
9335
  throw new Error("node9: argv[1] is not an absolute path");
9033
- const resolvedScript = import_fs18.default.realpathSync(scriptPath);
9034
- const expectedCli = import_fs18.default.realpathSync(import_path20.default.resolve(__dirname, "../../cli.js"));
9336
+ const resolvedScript = import_fs20.default.realpathSync(scriptPath);
9337
+ const expectedCli = import_fs20.default.realpathSync(import_path22.default.resolve(__dirname, "../../cli.js"));
9035
9338
  if (resolvedScript !== expectedCli)
9036
9339
  throw new Error(
9037
9340
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9057,10 +9360,10 @@ RAW: ${raw}
9057
9360
  }
9058
9361
  }
9059
9362
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9060
- const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
9061
- if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
9062
- import_fs18.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
9063
- import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9363
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9364
+ if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
9365
+ import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
9366
+ import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9064
9367
  `);
9065
9368
  }
9066
9369
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9073,8 +9376,8 @@ RAW: ${raw}
9073
9376
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9074
9377
  let ttyFd = null;
9075
9378
  try {
9076
- ttyFd = import_fs18.default.openSync("/dev/tty", "w");
9077
- const writeTty = (line) => import_fs18.default.writeSync(ttyFd, line + "\n");
9379
+ ttyFd = import_fs20.default.openSync("/dev/tty", "w");
9380
+ const writeTty = (line) => import_fs20.default.writeSync(ttyFd, line + "\n");
9078
9381
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9079
9382
  writeTty(import_chalk5.default.bgRed.white.bold(`
9080
9383
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9093,7 +9396,7 @@ RAW: ${raw}
9093
9396
  } finally {
9094
9397
  if (ttyFd !== null)
9095
9398
  try {
9096
- import_fs18.default.closeSync(ttyFd);
9399
+ import_fs20.default.closeSync(ttyFd);
9097
9400
  } catch {
9098
9401
  }
9099
9402
  }
@@ -9125,7 +9428,7 @@ RAW: ${raw}
9125
9428
  if (shouldSnapshot(toolName, toolInput, config)) {
9126
9429
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9127
9430
  }
9128
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9431
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9129
9432
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9130
9433
  cwd: safeCwdForAuth
9131
9434
  });
@@ -9137,12 +9440,12 @@ RAW: ${raw}
9137
9440
  }
9138
9441
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9139
9442
  try {
9140
- const tty = import_fs18.default.openSync("/dev/tty", "w");
9141
- import_fs18.default.writeSync(
9443
+ const tty = import_fs20.default.openSync("/dev/tty", "w");
9444
+ import_fs20.default.writeSync(
9142
9445
  tty,
9143
9446
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9144
9447
  );
9145
- import_fs18.default.closeSync(tty);
9448
+ import_fs20.default.closeSync(tty);
9146
9449
  } catch {
9147
9450
  }
9148
9451
  const daemonReady = await autoStartDaemonAndWait();
@@ -9169,9 +9472,9 @@ RAW: ${raw}
9169
9472
  });
9170
9473
  } catch (err2) {
9171
9474
  if (process.env.NODE9_DEBUG === "1") {
9172
- const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
9475
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9173
9476
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9174
- import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9477
+ import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9175
9478
  `);
9176
9479
  }
9177
9480
  process.exit(0);
@@ -9205,9 +9508,9 @@ RAW: ${raw}
9205
9508
  }
9206
9509
 
9207
9510
  // src/cli/commands/log.ts
9208
- var import_fs19 = __toESM(require("fs"));
9209
- var import_path21 = __toESM(require("path"));
9210
- var import_os15 = __toESM(require("os"));
9511
+ var import_fs21 = __toESM(require("fs"));
9512
+ var import_path23 = __toESM(require("path"));
9513
+ var import_os17 = __toESM(require("os"));
9211
9514
  init_audit();
9212
9515
  init_config();
9213
9516
  init_policy();
@@ -9251,9 +9554,9 @@ function containsShellMetachar(token) {
9251
9554
  }
9252
9555
 
9253
9556
  // src/cli/commands/log.ts
9254
- 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;
9557
+ 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;
9255
9558
  function detectTestResult(command, output) {
9256
- if (!TEST_COMMAND_RE.test(command)) return null;
9559
+ if (!TEST_COMMAND_RE2.test(command)) return null;
9257
9560
  const out = output.toLowerCase();
9258
9561
  if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
9259
9562
  out
@@ -9283,10 +9586,10 @@ function registerLogCommand(program2) {
9283
9586
  decision: "allowed",
9284
9587
  source: "post-hook"
9285
9588
  };
9286
- const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "audit.log");
9287
- if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
9288
- import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
9289
- import_fs19.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9589
+ const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9590
+ if (!import_fs21.default.existsSync(import_path23.default.dirname(logPath)))
9591
+ import_fs21.default.mkdirSync(import_path23.default.dirname(logPath), { recursive: true });
9592
+ import_fs21.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9290
9593
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9291
9594
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9292
9595
  if (command) {
@@ -9302,16 +9605,24 @@ function registerLogCommand(program2) {
9302
9605
  if (bashCommand && output) {
9303
9606
  const testResult = detectTestResult(bashCommand, output);
9304
9607
  if (testResult) {
9305
- await notifyActivitySocket({
9306
- id: "test-result",
9307
- ts: Date.now(),
9608
+ appendToLog(LOCAL_AUDIT_LOG, {
9609
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9308
9610
  tool,
9309
- status: testResult === "pass" ? "test_pass" : "test_fail"
9611
+ testResult,
9612
+ source: "test-result"
9310
9613
  });
9614
+ if (isDaemonRunning()) {
9615
+ await notifyActivitySocket({
9616
+ id: "test-result",
9617
+ ts: Date.now(),
9618
+ tool,
9619
+ status: testResult === "pass" ? "test_pass" : "test_fail"
9620
+ });
9621
+ }
9311
9622
  }
9312
9623
  }
9313
9624
  }
9314
- const safeCwd = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9625
+ const safeCwd = typeof payload.cwd === "string" && import_path23.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9315
9626
  const config = getConfig(safeCwd);
9316
9627
  if (shouldSnapshot(tool, {}, config)) {
9317
9628
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9320,9 +9631,9 @@ function registerLogCommand(program2) {
9320
9631
  const msg = err2 instanceof Error ? err2.message : String(err2);
9321
9632
  process.stderr.write(`[Node9] audit log error: ${msg}
9322
9633
  `);
9323
- const debugPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9634
+ const debugPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "hook-debug.log");
9324
9635
  try {
9325
- import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9636
+ import_fs21.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9326
9637
  `);
9327
9638
  } catch {
9328
9639
  }
@@ -9722,14 +10033,14 @@ function registerConfigShowCommand(program2) {
9722
10033
 
9723
10034
  // src/cli/commands/doctor.ts
9724
10035
  var import_chalk7 = __toESM(require("chalk"));
9725
- var import_fs20 = __toESM(require("fs"));
9726
- var import_path22 = __toESM(require("path"));
9727
- var import_os16 = __toESM(require("os"));
10036
+ var import_fs22 = __toESM(require("fs"));
10037
+ var import_path24 = __toESM(require("path"));
10038
+ var import_os18 = __toESM(require("os"));
9728
10039
  var import_child_process10 = require("child_process");
9729
10040
  init_daemon();
9730
10041
  function registerDoctorCommand(program2, version2) {
9731
10042
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9732
- const homeDir2 = import_os16.default.homedir();
10043
+ const homeDir2 = import_os18.default.homedir();
9733
10044
  let failures = 0;
9734
10045
  function pass(msg) {
9735
10046
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -9778,10 +10089,10 @@ function registerDoctorCommand(program2, version2) {
9778
10089
  );
9779
10090
  }
9780
10091
  section("Configuration");
9781
- const globalConfigPath = import_path22.default.join(homeDir2, ".node9", "config.json");
9782
- if (import_fs20.default.existsSync(globalConfigPath)) {
10092
+ const globalConfigPath = import_path24.default.join(homeDir2, ".node9", "config.json");
10093
+ if (import_fs22.default.existsSync(globalConfigPath)) {
9783
10094
  try {
9784
- JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
10095
+ JSON.parse(import_fs22.default.readFileSync(globalConfigPath, "utf-8"));
9785
10096
  pass("~/.node9/config.json found and valid");
9786
10097
  } catch {
9787
10098
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9789,10 +10100,10 @@ function registerDoctorCommand(program2, version2) {
9789
10100
  } else {
9790
10101
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9791
10102
  }
9792
- const projectConfigPath = import_path22.default.join(process.cwd(), "node9.config.json");
9793
- if (import_fs20.default.existsSync(projectConfigPath)) {
10103
+ const projectConfigPath = import_path24.default.join(process.cwd(), "node9.config.json");
10104
+ if (import_fs22.default.existsSync(projectConfigPath)) {
9794
10105
  try {
9795
- JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
10106
+ JSON.parse(import_fs22.default.readFileSync(projectConfigPath, "utf-8"));
9796
10107
  pass("node9.config.json found and valid (project)");
9797
10108
  } catch {
9798
10109
  fail(
@@ -9801,8 +10112,8 @@ function registerDoctorCommand(program2, version2) {
9801
10112
  );
9802
10113
  }
9803
10114
  }
9804
- const credsPath = import_path22.default.join(homeDir2, ".node9", "credentials.json");
9805
- if (import_fs20.default.existsSync(credsPath)) {
10115
+ const credsPath = import_path24.default.join(homeDir2, ".node9", "credentials.json");
10116
+ if (import_fs22.default.existsSync(credsPath)) {
9806
10117
  pass("Cloud credentials found (~/.node9/credentials.json)");
9807
10118
  } else {
9808
10119
  warn(
@@ -9811,10 +10122,10 @@ function registerDoctorCommand(program2, version2) {
9811
10122
  );
9812
10123
  }
9813
10124
  section("Agent Hooks");
9814
- const claudeSettingsPath = import_path22.default.join(homeDir2, ".claude", "settings.json");
9815
- if (import_fs20.default.existsSync(claudeSettingsPath)) {
10125
+ const claudeSettingsPath = import_path24.default.join(homeDir2, ".claude", "settings.json");
10126
+ if (import_fs22.default.existsSync(claudeSettingsPath)) {
9816
10127
  try {
9817
- const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
10128
+ const cs = JSON.parse(import_fs22.default.readFileSync(claudeSettingsPath, "utf-8"));
9818
10129
  const hasHook = cs.hooks?.PreToolUse?.some(
9819
10130
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9820
10131
  );
@@ -9830,10 +10141,10 @@ function registerDoctorCommand(program2, version2) {
9830
10141
  } else {
9831
10142
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9832
10143
  }
9833
- const geminiSettingsPath = import_path22.default.join(homeDir2, ".gemini", "settings.json");
9834
- if (import_fs20.default.existsSync(geminiSettingsPath)) {
10144
+ const geminiSettingsPath = import_path24.default.join(homeDir2, ".gemini", "settings.json");
10145
+ if (import_fs22.default.existsSync(geminiSettingsPath)) {
9835
10146
  try {
9836
- const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
10147
+ const gs = JSON.parse(import_fs22.default.readFileSync(geminiSettingsPath, "utf-8"));
9837
10148
  const hasHook = gs.hooks?.BeforeTool?.some(
9838
10149
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9839
10150
  );
@@ -9849,10 +10160,10 @@ function registerDoctorCommand(program2, version2) {
9849
10160
  } else {
9850
10161
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9851
10162
  }
9852
- const cursorHooksPath = import_path22.default.join(homeDir2, ".cursor", "hooks.json");
9853
- if (import_fs20.default.existsSync(cursorHooksPath)) {
10163
+ const cursorHooksPath = import_path24.default.join(homeDir2, ".cursor", "hooks.json");
10164
+ if (import_fs22.default.existsSync(cursorHooksPath)) {
9854
10165
  try {
9855
- const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
10166
+ const cur = JSON.parse(import_fs22.default.readFileSync(cursorHooksPath, "utf-8"));
9856
10167
  const hasHook = cur.hooks?.preToolUse?.some(
9857
10168
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9858
10169
  );
@@ -9890,9 +10201,9 @@ function registerDoctorCommand(program2, version2) {
9890
10201
 
9891
10202
  // src/cli/commands/audit.ts
9892
10203
  var import_chalk8 = __toESM(require("chalk"));
9893
- var import_fs21 = __toESM(require("fs"));
9894
- var import_path23 = __toESM(require("path"));
9895
- var import_os17 = __toESM(require("os"));
10204
+ var import_fs23 = __toESM(require("fs"));
10205
+ var import_path25 = __toESM(require("path"));
10206
+ var import_os19 = __toESM(require("os"));
9896
10207
  function formatRelativeTime(timestamp) {
9897
10208
  const diff = Date.now() - new Date(timestamp).getTime();
9898
10209
  const sec = Math.floor(diff / 1e3);
@@ -9905,14 +10216,14 @@ function formatRelativeTime(timestamp) {
9905
10216
  }
9906
10217
  function registerAuditCommand(program2) {
9907
10218
  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) => {
9908
- const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9909
- if (!import_fs21.default.existsSync(logPath)) {
10219
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10220
+ if (!import_fs23.default.existsSync(logPath)) {
9910
10221
  console.log(
9911
10222
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
9912
10223
  );
9913
10224
  return;
9914
10225
  }
9915
- const raw = import_fs21.default.readFileSync(logPath, "utf-8");
10226
+ const raw = import_fs23.default.readFileSync(logPath, "utf-8");
9916
10227
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
9917
10228
  let entries = lines.flatMap((line) => {
9918
10229
  try {
@@ -9964,8 +10275,443 @@ function registerAuditCommand(program2) {
9964
10275
  });
9965
10276
  }
9966
10277
 
9967
- // src/cli/commands/daemon-cmd.ts
10278
+ // src/cli/commands/report.ts
9968
10279
  var import_chalk9 = __toESM(require("chalk"));
10280
+ var import_fs24 = __toESM(require("fs"));
10281
+ var import_path26 = __toESM(require("path"));
10282
+ var import_os20 = __toESM(require("os"));
10283
+ 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;
10284
+ function buildTestTimestamps(allEntries) {
10285
+ const testTs = /* @__PURE__ */ new Set();
10286
+ for (const e of allEntries) {
10287
+ if (e.source !== "post-hook") continue;
10288
+ if (e.tool !== "Bash" && e.tool !== "bash") continue;
10289
+ const cmd = e.args?.command;
10290
+ if (typeof cmd === "string" && TEST_COMMAND_RE3.test(cmd)) {
10291
+ testTs.add(new Date(e.ts).getTime());
10292
+ }
10293
+ }
10294
+ return testTs;
10295
+ }
10296
+ function isTestEntry(entry, testTs) {
10297
+ if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
10298
+ if (entry.testRun === true) return true;
10299
+ const cmd = entry.args?.command;
10300
+ if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
10301
+ const t = new Date(entry.ts).getTime();
10302
+ for (const ts of testTs) {
10303
+ if (Math.abs(ts - t) <= 3e3) return true;
10304
+ }
10305
+ return false;
10306
+ }
10307
+ function getDateRange(period) {
10308
+ const now = /* @__PURE__ */ new Date();
10309
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
10310
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
10311
+ switch (period) {
10312
+ case "today":
10313
+ return { start: todayStart, end };
10314
+ case "7d": {
10315
+ const s = new Date(todayStart);
10316
+ s.setDate(s.getDate() - 6);
10317
+ return { start: s, end };
10318
+ }
10319
+ case "30d": {
10320
+ const s = new Date(todayStart);
10321
+ s.setDate(s.getDate() - 29);
10322
+ return { start: s, end };
10323
+ }
10324
+ case "month":
10325
+ return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
10326
+ }
10327
+ }
10328
+ function parseAuditLog(logPath) {
10329
+ if (!import_fs24.default.existsSync(logPath)) return [];
10330
+ const raw = import_fs24.default.readFileSync(logPath, "utf-8");
10331
+ return raw.split("\n").flatMap((line) => {
10332
+ if (!line.trim()) return [];
10333
+ try {
10334
+ return [JSON.parse(line)];
10335
+ } catch {
10336
+ return [];
10337
+ }
10338
+ });
10339
+ }
10340
+ function isAllow(decision) {
10341
+ return decision.startsWith("allow");
10342
+ }
10343
+ function isDlp(checkedBy) {
10344
+ return !!checkedBy?.includes("dlp");
10345
+ }
10346
+ function barStr(value, max, width) {
10347
+ if (max === 0 || width <= 0) return "\u2591".repeat(width);
10348
+ const filled = Math.max(1, Math.round(value / max * width));
10349
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
10350
+ }
10351
+ function colorBar(value, max, width) {
10352
+ const s = barStr(value, max, width);
10353
+ const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
10354
+ return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
10355
+ }
10356
+ function pct(num2, total) {
10357
+ if (total === 0) return "\u2013";
10358
+ return Math.round(num2 / total * 100) + "%";
10359
+ }
10360
+ function fmtDate(d) {
10361
+ const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
10362
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
10363
+ }
10364
+ function num(n) {
10365
+ return n.toLocaleString();
10366
+ }
10367
+ function fmtCost(usd) {
10368
+ if (usd < 1e-3) return "< $0.001";
10369
+ if (usd < 1) return "$" + usd.toFixed(4);
10370
+ return "$" + usd.toFixed(2);
10371
+ }
10372
+ var CLAUDE_PRICING = {
10373
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10374
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10375
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
10376
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10377
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10378
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10379
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10380
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10381
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
10382
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
10383
+ };
10384
+ function claudeModelPrice(model) {
10385
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10386
+ for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
10387
+ if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
10388
+ }
10389
+ return null;
10390
+ }
10391
+ function loadClaudeCost(start, end) {
10392
+ const empty = {
10393
+ total: 0,
10394
+ byDay: /* @__PURE__ */ new Map(),
10395
+ byModel: /* @__PURE__ */ new Map(),
10396
+ inputTokens: 0,
10397
+ cacheReadTokens: 0
10398
+ };
10399
+ const projectsDir = import_path26.default.join(import_os20.default.homedir(), ".claude", "projects");
10400
+ if (!import_fs24.default.existsSync(projectsDir)) return empty;
10401
+ let dirs;
10402
+ try {
10403
+ dirs = import_fs24.default.readdirSync(projectsDir);
10404
+ } catch {
10405
+ return empty;
10406
+ }
10407
+ let total = 0;
10408
+ let inputTokens = 0;
10409
+ let cacheReadTokens = 0;
10410
+ const byDay = /* @__PURE__ */ new Map();
10411
+ const byModel = /* @__PURE__ */ new Map();
10412
+ for (const proj of dirs) {
10413
+ const projPath = import_path26.default.join(projectsDir, proj);
10414
+ let files;
10415
+ try {
10416
+ const stat = import_fs24.default.statSync(projPath);
10417
+ if (!stat.isDirectory()) continue;
10418
+ files = import_fs24.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10419
+ } catch {
10420
+ continue;
10421
+ }
10422
+ for (const file of files) {
10423
+ try {
10424
+ const raw = import_fs24.default.readFileSync(import_path26.default.join(projPath, file), "utf-8");
10425
+ for (const line of raw.split("\n")) {
10426
+ if (!line.trim()) continue;
10427
+ let entry;
10428
+ try {
10429
+ entry = JSON.parse(line);
10430
+ } catch {
10431
+ continue;
10432
+ }
10433
+ if (entry.type !== "assistant") continue;
10434
+ if (!entry.timestamp) continue;
10435
+ const ts = new Date(entry.timestamp);
10436
+ if (ts < start || ts > end) continue;
10437
+ const usage = entry.message?.usage;
10438
+ const model = entry.message?.model;
10439
+ if (!usage || !model) continue;
10440
+ const p = claudeModelPrice(model);
10441
+ if (!p) continue;
10442
+ const inp = usage.input_tokens ?? 0;
10443
+ const out = usage.output_tokens ?? 0;
10444
+ const cw = usage.cache_creation_input_tokens ?? 0;
10445
+ const cr = usage.cache_read_input_tokens ?? 0;
10446
+ const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
10447
+ total += cost;
10448
+ inputTokens += inp;
10449
+ cacheReadTokens += cr;
10450
+ const dateKey = entry.timestamp.slice(0, 10);
10451
+ byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10452
+ const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10453
+ byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
10454
+ }
10455
+ } catch {
10456
+ continue;
10457
+ }
10458
+ }
10459
+ }
10460
+ return { total, byDay, byModel, inputTokens, cacheReadTokens };
10461
+ }
10462
+ function registerReportCommand(program2) {
10463
+ 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) => {
10464
+ const period = ["today", "7d", "30d", "month"].includes(
10465
+ options.period
10466
+ ) ? options.period : "7d";
10467
+ const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
10468
+ const allEntries = parseAuditLog(logPath);
10469
+ if (allEntries.length === 0) {
10470
+ console.log(
10471
+ import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
10472
+ );
10473
+ return;
10474
+ }
10475
+ const { start, end } = getDateRange(period);
10476
+ const {
10477
+ total: costUSD,
10478
+ byDay: costByDay,
10479
+ byModel: costByModel,
10480
+ inputTokens: costInputTokens,
10481
+ cacheReadTokens: costCacheRead
10482
+ } = loadClaudeCost(start, end);
10483
+ const periodMs = end.getTime() - start.getTime();
10484
+ const priorEnd = new Date(start.getTime() - 1);
10485
+ const priorStart = new Date(start.getTime() - periodMs);
10486
+ const priorEntries = allEntries.filter((e) => {
10487
+ if (e.source === "post-hook") return false;
10488
+ const ts = new Date(e.ts);
10489
+ return ts >= priorStart && ts <= priorEnd;
10490
+ });
10491
+ const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
10492
+ const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
10493
+ const excludeTests = options.tests === false;
10494
+ const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
10495
+ let filteredTestCount = 0;
10496
+ const entries = allEntries.filter((e) => {
10497
+ if (e.source === "post-hook") return false;
10498
+ const ts = new Date(e.ts);
10499
+ if (ts < start || ts > end) return false;
10500
+ if (excludeTests && isTestEntry(e, testTs)) {
10501
+ filteredTestCount++;
10502
+ return false;
10503
+ }
10504
+ return true;
10505
+ });
10506
+ if (entries.length === 0) {
10507
+ console.log(import_chalk9.default.yellow(`
10508
+ No activity for period "${period}".
10509
+ `));
10510
+ return;
10511
+ }
10512
+ let allowed = 0;
10513
+ let blocked = 0;
10514
+ let dlpHits = 0;
10515
+ let loopHits = 0;
10516
+ let testPasses = 0;
10517
+ let testFails = 0;
10518
+ const toolMap = /* @__PURE__ */ new Map();
10519
+ const blockMap = /* @__PURE__ */ new Map();
10520
+ const agentMap = /* @__PURE__ */ new Map();
10521
+ const mcpMap = /* @__PURE__ */ new Map();
10522
+ const dailyMap = /* @__PURE__ */ new Map();
10523
+ const hourMap = /* @__PURE__ */ new Map();
10524
+ for (const e of entries) {
10525
+ const allow = isAllow(e.decision);
10526
+ const dateKey = e.ts.slice(0, 10);
10527
+ if (allow) allowed++;
10528
+ else blocked++;
10529
+ if (isDlp(e.checkedBy)) dlpHits++;
10530
+ if (e.checkedBy === "loop-detected") loopHits++;
10531
+ const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
10532
+ t.calls++;
10533
+ if (!allow) t.blocked++;
10534
+ toolMap.set(e.tool, t);
10535
+ if (!allow && e.checkedBy) {
10536
+ blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
10537
+ }
10538
+ if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
10539
+ if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
10540
+ const hour = new Date(e.ts).getHours();
10541
+ hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
10542
+ const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
10543
+ d.calls++;
10544
+ if (!allow) d.blocked++;
10545
+ dailyMap.set(dateKey, d);
10546
+ }
10547
+ for (const e of allEntries) {
10548
+ if (e.source !== "test-result") continue;
10549
+ const ts = new Date(e.ts);
10550
+ if (ts < start || ts > end) continue;
10551
+ if (e.testResult === "pass") testPasses++;
10552
+ else if (e.testResult === "fail") testFails++;
10553
+ }
10554
+ const total = entries.length;
10555
+ const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
10556
+ const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
10557
+ const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
10558
+ const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
10559
+ const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
10560
+ const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
10561
+ const W = Math.min(process.stdout.columns || 80, 100);
10562
+ const INNER = W - 4;
10563
+ const COL = Math.floor(INNER / 2) - 1;
10564
+ const LABEL = 24;
10565
+ const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
10566
+ const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num(v.calls).length), 1);
10567
+ const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num(v).length), 1);
10568
+ const line = import_chalk9.default.dim("\u2500".repeat(W - 2));
10569
+ const periodLabel = {
10570
+ today: "Today",
10571
+ "7d": "Last 7 Days",
10572
+ "30d": "Last 30 Days",
10573
+ month: "This Month"
10574
+ };
10575
+ console.log("");
10576
+ console.log(
10577
+ " " + import_chalk9.default.bold.cyan("\u{1F6E1} node9 Report") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.white(periodLabel[period]) + import_chalk9.default.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + import_chalk9.default.dim(` ${num(total)} events`) + (excludeTests ? import_chalk9.default.dim(` \u2013tests (\u2013${filteredTestCount})`) : "")
10578
+ );
10579
+ console.log(" " + line);
10580
+ console.log("");
10581
+ const blockLabel = blocked > 0 ? import_chalk9.default.red(`\u{1F6D1} ${num(blocked)} blocked`) : import_chalk9.default.dim("\u{1F6D1} 0 blocked");
10582
+ const dlpLabel = dlpHits > 0 ? import_chalk9.default.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : import_chalk9.default.dim("\u{1F6A8} 0 DLP hits");
10583
+ const loopLabel = loopHits > 0 ? import_chalk9.default.yellow(`\u{1F504} ${loopHits} loops`) : import_chalk9.default.dim("\u{1F504} 0 loops");
10584
+ const currentRate = total > 0 ? blocked / total : 0;
10585
+ const trendLabel = (() => {
10586
+ if (priorBlockRate === null) return import_chalk9.default.dim(`${pct(blocked, total)} block rate`);
10587
+ const delta = Math.round((currentRate - priorBlockRate) * 100);
10588
+ const arrow = delta > 0 ? import_chalk9.default.red(`\u25B2${delta}%`) : delta < 0 ? import_chalk9.default.green(`\u25BC${Math.abs(delta)}%`) : import_chalk9.default.dim("\u2013");
10589
+ return import_chalk9.default.dim(`${pct(blocked, total)} block rate `) + arrow + import_chalk9.default.dim(" vs prior");
10590
+ })();
10591
+ const reads = toolMap.get("Read")?.calls ?? 0;
10592
+ const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
10593
+ const ratioLabel = reads > 0 ? import_chalk9.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk9.default.dim("edit/read \u2013");
10594
+ const testLabel = testPasses + testFails > 0 ? import_chalk9.default.dim("tests ") + import_chalk9.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk9.default.red(`${testFails}\u2717`) : "") : import_chalk9.default.dim("tests \u2013");
10595
+ console.log(
10596
+ " " + import_chalk9.default.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
10597
+ );
10598
+ console.log(" " + ratioLabel + " " + testLabel);
10599
+ console.log("");
10600
+ const toolHeaderRaw = "Top Tools";
10601
+ const blockHeaderRaw = "Top Blocks";
10602
+ console.log(
10603
+ " " + import_chalk9.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk9.default.bold(blockHeaderRaw)
10604
+ );
10605
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(COL)) + " " + import_chalk9.default.dim("\u2500".repeat(COL)));
10606
+ const rows = Math.max(topTools.length, topBlocks.length, 1);
10607
+ for (let i = 0; i < rows; i++) {
10608
+ let leftStyled = " ".repeat(COL);
10609
+ if (i < topTools.length) {
10610
+ const [tool, { calls }] = topTools[i];
10611
+ const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
10612
+ const countStr = num(calls).padStart(TOOL_COUNT_W);
10613
+ const b = colorBar(calls, maxTool, BAR);
10614
+ const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
10615
+ const pad = Math.max(0, COL - rawLen);
10616
+ leftStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(countStr) + " ".repeat(pad);
10617
+ }
10618
+ let rightStyled = "";
10619
+ if (i < topBlocks.length) {
10620
+ const [reason, count] = topBlocks[i];
10621
+ const label = reason.length > LABEL - 1 ? reason.slice(0, LABEL - 2) + "\u2026" : reason;
10622
+ const countStr = num(count).padStart(BLOCK_COUNT_W);
10623
+ const b = colorBar(count, maxBlock, BAR);
10624
+ rightStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.red(countStr);
10625
+ }
10626
+ console.log(" " + leftStyled + " " + rightStyled);
10627
+ }
10628
+ if (topBlocks.length === 0) {
10629
+ console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
10630
+ }
10631
+ if (agentMap.size > 1) {
10632
+ console.log("");
10633
+ console.log(" " + import_chalk9.default.bold("Agents"));
10634
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
10635
+ const maxAgent = Math.max(...agentMap.values(), 1);
10636
+ for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
10637
+ const label = agent.slice(0, LABEL - 1);
10638
+ const b = colorBar(count, maxAgent, BAR);
10639
+ console.log(" " + import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(num(count)));
10640
+ }
10641
+ }
10642
+ if (mcpMap.size > 0) {
10643
+ console.log("");
10644
+ console.log(" " + import_chalk9.default.bold("MCP Servers"));
10645
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
10646
+ const maxMcp = Math.max(...mcpMap.values(), 1);
10647
+ for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
10648
+ const label = server.slice(0, LABEL - 1).padEnd(LABEL);
10649
+ const b = colorBar(count, maxMcp, BAR);
10650
+ console.log(" " + import_chalk9.default.white(label) + b + " " + import_chalk9.default.white(num(count)));
10651
+ }
10652
+ }
10653
+ if (hourMap.size > 0) {
10654
+ const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
10655
+ const maxHour = Math.max(...hourMap.values(), 1);
10656
+ const bar = Array.from({ length: 24 }, (_, h) => {
10657
+ const v = hourMap.get(h) ?? 0;
10658
+ return BLOCKS[Math.round(v / maxHour * 8)];
10659
+ }).join("");
10660
+ console.log("");
10661
+ console.log(" " + import_chalk9.default.bold("Hour of Day") + import_chalk9.default.dim(" (local, 0h \u2013 23h)"));
10662
+ console.log(" " + import_chalk9.default.cyan(bar));
10663
+ console.log(" " + import_chalk9.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
10664
+ }
10665
+ if (dailyList.length > 1) {
10666
+ console.log("");
10667
+ console.log(" " + import_chalk9.default.bold("Daily Activity"));
10668
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(W - 2)));
10669
+ const DAY_BAR = Math.max(8, Math.min(30, W - 36));
10670
+ for (const [dateKey, { calls, blocked: db }] of dailyList) {
10671
+ const label = fmtDate(dateKey).padEnd(10);
10672
+ const b = colorBar(calls, maxDaily, DAY_BAR);
10673
+ const dayCost = costByDay.get(dateKey);
10674
+ const costNote = dayCost ? import_chalk9.default.magenta(` ${fmtCost(dayCost)}`) : "";
10675
+ const blockNote = db > 0 ? import_chalk9.default.red(` ${db} blocked`) : "";
10676
+ console.log(
10677
+ " " + import_chalk9.default.dim(label) + " " + b + " " + import_chalk9.default.white(num(calls)) + blockNote + costNote
10678
+ );
10679
+ }
10680
+ }
10681
+ if (costUSD > 0) {
10682
+ const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
10683
+ const avgPerDay = costUSD / periodDays;
10684
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
10685
+ const costHeaderRight = [
10686
+ import_chalk9.default.yellow(fmtCost(costUSD)),
10687
+ import_chalk9.default.dim(`avg ${fmtCost(avgPerDay)}/day`),
10688
+ cacheHitPct > 0 ? import_chalk9.default.dim(`${cacheHitPct}% cache hit`) : null
10689
+ ].filter(Boolean).join(import_chalk9.default.dim(" \xB7 "));
10690
+ console.log("");
10691
+ console.log(" " + import_chalk9.default.bold("Cost") + " " + costHeaderRight);
10692
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
10693
+ const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
10694
+ const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
10695
+ const MODEL_LABEL = 22;
10696
+ const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
10697
+ for (const [model, cost] of modelList) {
10698
+ const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
10699
+ const b = colorBar(cost, maxModelCost, MODEL_BAR);
10700
+ console.log(
10701
+ " " + import_chalk9.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk9.default.yellow(fmtCost(cost))
10702
+ );
10703
+ }
10704
+ }
10705
+ console.log("");
10706
+ console.log(
10707
+ " " + import_chalk9.default.dim("node9 audit --deny") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.dim("node9 report --period today|7d|30d|month --no-tests")
10708
+ );
10709
+ console.log("");
10710
+ });
10711
+ }
10712
+
10713
+ // src/cli/commands/daemon-cmd.ts
10714
+ var import_chalk10 = __toESM(require("chalk"));
9969
10715
  var import_child_process11 = require("child_process");
9970
10716
  init_daemon2();
9971
10717
  init_daemon();
@@ -9980,7 +10726,7 @@ function registerDaemonCommand(program2) {
9980
10726
  if (cmd === "status") return daemonStatus();
9981
10727
  if (cmd !== "start" && action !== void 0) {
9982
10728
  console.error(
9983
- import_chalk9.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10729
+ import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
9984
10730
  );
9985
10731
  process.exit(1);
9986
10732
  }
@@ -9988,7 +10734,7 @@ function registerDaemonCommand(program2) {
9988
10734
  process.env.NODE9_WATCH_MODE = "1";
9989
10735
  setTimeout(() => {
9990
10736
  openBrowserLocal();
9991
- console.log(import_chalk9.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10737
+ console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9992
10738
  }, 600);
9993
10739
  startDaemon();
9994
10740
  return;
@@ -9996,7 +10742,7 @@ function registerDaemonCommand(program2) {
9996
10742
  if (options.openui) {
9997
10743
  if (isDaemonRunning()) {
9998
10744
  openBrowserLocal();
9999
- console.log(import_chalk9.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10745
+ console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10000
10746
  process.exit(0);
10001
10747
  }
10002
10748
  const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
@@ -10009,7 +10755,7 @@ function registerDaemonCommand(program2) {
10009
10755
  if (isDaemonRunning()) break;
10010
10756
  }
10011
10757
  openBrowserLocal();
10012
- console.log(import_chalk9.default.green(`
10758
+ console.log(import_chalk10.default.green(`
10013
10759
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
10014
10760
  process.exit(0);
10015
10761
  }
@@ -10019,7 +10765,7 @@ function registerDaemonCommand(program2) {
10019
10765
  stdio: "ignore"
10020
10766
  });
10021
10767
  child.unref();
10022
- console.log(import_chalk9.default.green(`
10768
+ console.log(import_chalk10.default.green(`
10023
10769
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
10024
10770
  process.exit(0);
10025
10771
  }
@@ -10029,15 +10775,15 @@ function registerDaemonCommand(program2) {
10029
10775
  }
10030
10776
 
10031
10777
  // src/cli/commands/status.ts
10032
- var import_chalk10 = __toESM(require("chalk"));
10033
- var import_fs22 = __toESM(require("fs"));
10034
- var import_path24 = __toESM(require("path"));
10035
- var import_os18 = __toESM(require("os"));
10778
+ var import_chalk11 = __toESM(require("chalk"));
10779
+ var import_fs25 = __toESM(require("fs"));
10780
+ var import_path27 = __toESM(require("path"));
10781
+ var import_os21 = __toESM(require("os"));
10036
10782
  init_core();
10037
10783
  init_daemon();
10038
10784
  function readJson2(filePath) {
10039
10785
  try {
10040
- if (import_fs22.default.existsSync(filePath)) return JSON.parse(import_fs22.default.readFileSync(filePath, "utf-8"));
10786
+ if (import_fs25.default.existsSync(filePath)) return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
10041
10787
  } catch {
10042
10788
  }
10043
10789
  return null;
@@ -10051,21 +10797,21 @@ function wrappedMcpServers(servers) {
10051
10797
  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(" ")}`);
10052
10798
  }
10053
10799
  function printAgentSection(label, hookPairs, wrapped) {
10054
- console.log(import_chalk10.default.bold(` ${label}`));
10800
+ console.log(import_chalk11.default.bold(` ${label}`));
10055
10801
  for (const { name, present } of hookPairs) {
10056
10802
  if (present) {
10057
- console.log(import_chalk10.default.green(` \u2713 ${name}`));
10803
+ console.log(import_chalk11.default.green(` \u2713 ${name}`));
10058
10804
  } else {
10059
- console.log(import_chalk10.default.red(` \u2717 ${name}`) + import_chalk10.default.gray(" (not wired)"));
10805
+ console.log(import_chalk11.default.red(` \u2717 ${name}`) + import_chalk11.default.gray(" (not wired)"));
10060
10806
  }
10061
10807
  }
10062
10808
  if (wrapped.length > 0) {
10063
- console.log(import_chalk10.default.cyan(` MCP proxied:`));
10809
+ console.log(import_chalk11.default.cyan(` MCP proxied:`));
10064
10810
  for (const entry of wrapped) {
10065
- console.log(import_chalk10.default.gray(` \u2022 ${entry}`));
10811
+ console.log(import_chalk11.default.gray(` \u2022 ${entry}`));
10066
10812
  }
10067
10813
  } else {
10068
- console.log(import_chalk10.default.gray(` MCP proxied: none`));
10814
+ console.log(import_chalk11.default.gray(` MCP proxied: none`));
10069
10815
  }
10070
10816
  }
10071
10817
  function registerStatusCommand(program2) {
@@ -10076,58 +10822,58 @@ function registerStatusCommand(program2) {
10076
10822
  const settings = mergedConfig.settings;
10077
10823
  console.log("");
10078
10824
  if (creds && settings.approvers.cloud) {
10079
- console.log(import_chalk10.default.green(" \u25CF Agent mode") + import_chalk10.default.gray(" \u2014 cloud team policy enforced"));
10825
+ console.log(import_chalk11.default.green(" \u25CF Agent mode") + import_chalk11.default.gray(" \u2014 cloud team policy enforced"));
10080
10826
  } else if (creds && !settings.approvers.cloud) {
10081
10827
  console.log(
10082
- import_chalk10.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk10.default.gray(" \u2014 all decisions stay on this machine")
10828
+ import_chalk11.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 all decisions stay on this machine")
10083
10829
  );
10084
10830
  } else {
10085
10831
  console.log(
10086
- import_chalk10.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk10.default.gray(" \u2014 no API key (Local rules only)")
10832
+ import_chalk11.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 no API key (Local rules only)")
10087
10833
  );
10088
10834
  }
10089
10835
  console.log("");
10090
10836
  if (daemonRunning) {
10091
10837
  console.log(
10092
- import_chalk10.default.green(" \u25CF Daemon running") + import_chalk10.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10838
+ import_chalk11.default.green(" \u25CF Daemon running") + import_chalk11.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10093
10839
  );
10094
10840
  } else {
10095
- console.log(import_chalk10.default.gray(" \u25CB Daemon stopped"));
10841
+ console.log(import_chalk11.default.gray(" \u25CB Daemon stopped"));
10096
10842
  }
10097
10843
  if (settings.enableUndo) {
10098
10844
  console.log(
10099
- import_chalk10.default.magenta(" \u25CF Undo Engine") + import_chalk10.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10845
+ import_chalk11.default.magenta(" \u25CF Undo Engine") + import_chalk11.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10100
10846
  );
10101
10847
  }
10102
10848
  console.log("");
10103
- const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
10849
+ const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
10104
10850
  console.log(` Mode: ${modeLabel}`);
10105
- const projectConfig = import_path24.default.join(process.cwd(), "node9.config.json");
10106
- const globalConfig = import_path24.default.join(import_os18.default.homedir(), ".node9", "config.json");
10851
+ const projectConfig = import_path27.default.join(process.cwd(), "node9.config.json");
10852
+ const globalConfig = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
10107
10853
  console.log(
10108
- ` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
10854
+ ` Local: ${import_fs25.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
10109
10855
  );
10110
10856
  console.log(
10111
- ` Global: ${import_fs22.default.existsSync(globalConfig) ? import_chalk10.default.green("Active (~/.node9/config.json)") : import_chalk10.default.gray("Not present")}`
10857
+ ` Global: ${import_fs25.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
10112
10858
  );
10113
10859
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10114
10860
  console.log(
10115
- ` Sandbox: ${import_chalk10.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10861
+ ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10116
10862
  );
10117
10863
  }
10118
- const homeDir2 = import_os18.default.homedir();
10864
+ const homeDir2 = import_os21.default.homedir();
10119
10865
  const claudeSettings = readJson2(
10120
- import_path24.default.join(homeDir2, ".claude", "settings.json")
10866
+ import_path27.default.join(homeDir2, ".claude", "settings.json")
10121
10867
  );
10122
- const claudeConfig = readJson2(import_path24.default.join(homeDir2, ".claude.json"));
10868
+ const claudeConfig = readJson2(import_path27.default.join(homeDir2, ".claude.json"));
10123
10869
  const geminiSettings = readJson2(
10124
- import_path24.default.join(homeDir2, ".gemini", "settings.json")
10870
+ import_path27.default.join(homeDir2, ".gemini", "settings.json")
10125
10871
  );
10126
- const cursorConfig = readJson2(import_path24.default.join(homeDir2, ".cursor", "mcp.json"));
10872
+ const cursorConfig = readJson2(import_path27.default.join(homeDir2, ".cursor", "mcp.json"));
10127
10873
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10128
10874
  if (agentFound) {
10129
10875
  console.log("");
10130
- console.log(import_chalk10.default.bold(" Agent Wiring:"));
10876
+ console.log(import_chalk11.default.bold(" Agent Wiring:"));
10131
10877
  console.log("");
10132
10878
  if (claudeSettings || claudeConfig) {
10133
10879
  const preHook = claudeSettings?.hooks?.PreToolUse?.some(
@@ -10173,7 +10919,7 @@ function registerStatusCommand(program2) {
10173
10919
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
10174
10920
  console.log("");
10175
10921
  console.log(
10176
- import_chalk10.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk10.default.gray(" \u2014 all tool calls allowed")
10922
+ import_chalk11.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk11.default.gray(" \u2014 all tool calls allowed")
10177
10923
  );
10178
10924
  }
10179
10925
  console.log("");
@@ -10181,10 +10927,10 @@ function registerStatusCommand(program2) {
10181
10927
  }
10182
10928
 
10183
10929
  // src/cli/commands/init.ts
10184
- var import_chalk11 = __toESM(require("chalk"));
10185
- var import_fs23 = __toESM(require("fs"));
10186
- var import_path25 = __toESM(require("path"));
10187
- var import_os19 = __toESM(require("os"));
10930
+ var import_chalk12 = __toESM(require("chalk"));
10931
+ var import_fs26 = __toESM(require("fs"));
10932
+ var import_path28 = __toESM(require("path"));
10933
+ var import_os22 = __toESM(require("os"));
10188
10934
  var import_https2 = __toESM(require("https"));
10189
10935
  init_core();
10190
10936
  init_shields();
@@ -10220,7 +10966,7 @@ function fireTelemetryPing(agents) {
10220
10966
  }
10221
10967
  function registerInitCommand(program2) {
10222
10968
  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) => {
10223
- console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10969
+ console.log(import_chalk12.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10224
10970
  let chosenMode = options.mode.toLowerCase();
10225
10971
  if (!["standard", "strict", "audit"].includes(chosenMode)) {
10226
10972
  chosenMode = DEFAULT_CONFIG.settings.mode;
@@ -10239,37 +10985,37 @@ function registerInitCommand(program2) {
10239
10985
  const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
10240
10986
  if (hasNewShields) writeActiveShields(merged);
10241
10987
  } catch (err2) {
10242
- console.log(import_chalk11.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10988
+ console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10243
10989
  }
10244
10990
  }
10245
10991
  console.log("");
10246
10992
  }
10247
- const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
10248
- if (import_fs23.default.existsSync(configPath) && !options.force) {
10993
+ const configPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "config.json");
10994
+ if (import_fs26.default.existsSync(configPath) && !options.force) {
10249
10995
  try {
10250
- const existing = JSON.parse(import_fs23.default.readFileSync(configPath, "utf-8"));
10996
+ const existing = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
10251
10997
  const settings = existing.settings ?? {};
10252
10998
  if (settings.mode !== chosenMode) {
10253
10999
  settings.mode = chosenMode;
10254
11000
  existing.settings = settings;
10255
- import_fs23.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10256
- console.log(import_chalk11.default.green(`\u2705 Mode updated: ${chosenMode}`));
11001
+ import_fs26.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11002
+ console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
10257
11003
  } else {
10258
- console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
11004
+ console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10259
11005
  }
10260
11006
  } catch {
10261
- console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
11007
+ console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10262
11008
  }
10263
11009
  } else {
10264
11010
  const configToSave = {
10265
11011
  ...DEFAULT_CONFIG,
10266
11012
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10267
11013
  };
10268
- const dir = import_path25.default.dirname(configPath);
10269
- if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
10270
- import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10271
- console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
10272
- console.log(import_chalk11.default.gray(` Mode: ${chosenMode}`));
11014
+ const dir = import_path28.default.dirname(configPath);
11015
+ if (!import_fs26.default.existsSync(dir)) import_fs26.default.mkdirSync(dir, { recursive: true });
11016
+ import_fs26.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11017
+ console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
11018
+ console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
10273
11019
  }
10274
11020
  if (options.skipSetup) return;
10275
11021
  console.log("");
@@ -10279,18 +11025,18 @@ function registerInitCommand(program2) {
10279
11025
  );
10280
11026
  if (found.length === 0) {
10281
11027
  console.log(
10282
- import_chalk11.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
11028
+ import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
10283
11029
  );
10284
- console.log(import_chalk11.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
11030
+ console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
10285
11031
  return;
10286
11032
  }
10287
- console.log(import_chalk11.default.bold("Detected agents:"));
11033
+ console.log(import_chalk12.default.bold("Detected agents:"));
10288
11034
  for (const agent of found) {
10289
- console.log(import_chalk11.default.green(` \u2713 ${agent}`));
11035
+ console.log(import_chalk12.default.green(` \u2713 ${agent}`));
10290
11036
  }
10291
11037
  console.log("");
10292
11038
  for (const agent of found) {
10293
- console.log(import_chalk11.default.bold(`Wiring ${agent}...`));
11039
+ console.log(import_chalk12.default.bold(`Wiring ${agent}...`));
10294
11040
  if (agent === "claude") await setupClaude();
10295
11041
  else if (agent === "gemini") await setupGemini();
10296
11042
  else if (agent === "cursor") await setupCursor();
@@ -10307,26 +11053,26 @@ function registerInitCommand(program2) {
10307
11053
  console.log("");
10308
11054
  }
10309
11055
  const agentList = found.join(", ");
10310
- console.log(import_chalk11.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
11056
+ console.log(import_chalk12.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
10311
11057
  console.log("");
10312
- console.log(import_chalk11.default.white(" Watch live: ") + import_chalk11.default.cyan("node9 tail"));
10313
- console.log(import_chalk11.default.white(" Local UI: ") + import_chalk11.default.cyan("node9 daemon --openui"));
11058
+ console.log(import_chalk12.default.white(" Watch live: ") + import_chalk12.default.cyan("node9 tail"));
11059
+ console.log(import_chalk12.default.white(" Local UI: ") + import_chalk12.default.cyan("node9 daemon --openui"));
10314
11060
  console.log("");
10315
- console.log(import_chalk11.default.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"));
11061
+ console.log(import_chalk12.default.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"));
10316
11062
  console.log(
10317
- import_chalk11.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk11.default.cyan.bold("https://node9.ai")
11063
+ import_chalk12.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk12.default.cyan.bold("https://node9.ai")
10318
11064
  );
10319
- console.log(import_chalk11.default.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"));
11065
+ console.log(import_chalk12.default.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"));
10320
11066
  });
10321
11067
  }
10322
11068
 
10323
11069
  // src/cli/commands/undo.ts
10324
- var import_path26 = __toESM(require("path"));
10325
- var import_chalk13 = __toESM(require("chalk"));
11070
+ var import_path29 = __toESM(require("path"));
11071
+ var import_chalk14 = __toESM(require("chalk"));
10326
11072
 
10327
11073
  // src/tui/undo-navigator.ts
10328
11074
  var import_readline2 = __toESM(require("readline"));
10329
- var import_chalk12 = __toESM(require("chalk"));
11075
+ var import_chalk13 = __toESM(require("chalk"));
10330
11076
  var RESET = "\x1B[0m";
10331
11077
  var BOLD = "\x1B[1m";
10332
11078
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
@@ -10344,15 +11090,15 @@ function renderDiff(raw) {
10344
11090
  );
10345
11091
  for (const line of lines) {
10346
11092
  if (line.startsWith("+++") || line.startsWith("---")) {
10347
- process.stdout.write(import_chalk12.default.bold(line) + "\n");
11093
+ process.stdout.write(import_chalk13.default.bold(line) + "\n");
10348
11094
  } else if (line.startsWith("+")) {
10349
- process.stdout.write(import_chalk12.default.green(line) + "\n");
11095
+ process.stdout.write(import_chalk13.default.green(line) + "\n");
10350
11096
  } else if (line.startsWith("-")) {
10351
- process.stdout.write(import_chalk12.default.red(line) + "\n");
11097
+ process.stdout.write(import_chalk13.default.red(line) + "\n");
10352
11098
  } else if (line.startsWith("@@")) {
10353
- process.stdout.write(import_chalk12.default.cyan(line) + "\n");
11099
+ process.stdout.write(import_chalk13.default.cyan(line) + "\n");
10354
11100
  } else {
10355
- process.stdout.write(import_chalk12.default.gray(line) + "\n");
11101
+ process.stdout.write(import_chalk13.default.gray(line) + "\n");
10356
11102
  }
10357
11103
  }
10358
11104
  }
@@ -10371,23 +11117,23 @@ function render(entries, idx) {
10371
11117
  const step = idx + 1;
10372
11118
  process.stdout.write(CLEAR_SCREEN);
10373
11119
  process.stdout.write(
10374
- import_chalk12.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk12.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk12.default.gray(
11120
+ import_chalk13.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk13.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk13.default.gray(
10375
11121
  ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
10376
11122
  ) : "") + "\n\n"
10377
11123
  );
10378
11124
  process.stdout.write(
10379
- ` ${BOLD}Tool:${RESET} ${import_chalk12.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk12.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
11125
+ ` ${BOLD}Tool:${RESET} ${import_chalk13.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk13.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
10380
11126
  );
10381
- process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk12.default.gray(formatAge(entry.timestamp))}
11127
+ process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk13.default.gray(formatAge(entry.timestamp))}
10382
11128
  `);
10383
- process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk12.default.gray(entry.cwd)}
11129
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk13.default.gray(entry.cwd)}
10384
11130
  `);
10385
11131
  if (entry.files && entry.files.length > 0) {
10386
- process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk12.default.gray(entry.files.join(", "))}
11132
+ process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk13.default.gray(entry.files.join(", "))}
10387
11133
  `);
10388
11134
  }
10389
11135
  if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
10390
- process.stdout.write(import_chalk12.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
11136
+ process.stdout.write(import_chalk13.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
10391
11137
  }
10392
11138
  process.stdout.write("\n");
10393
11139
  const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
@@ -10395,12 +11141,12 @@ function render(entries, idx) {
10395
11141
  renderDiff(diff);
10396
11142
  } else {
10397
11143
  process.stdout.write(
10398
- import_chalk12.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
11144
+ import_chalk13.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
10399
11145
  );
10400
11146
  }
10401
11147
  process.stdout.write("\n");
10402
11148
  process.stdout.write(
10403
- import_chalk12.default.gray(" ") + (idx < total - 1 ? import_chalk12.default.white("[\u2190] older") : import_chalk12.default.gray("[\u2190] older")) + import_chalk12.default.gray(" ") + (idx > 0 ? import_chalk12.default.white("[\u2192] newer") : import_chalk12.default.gray("[\u2192] newer")) + import_chalk12.default.gray(" ") + import_chalk12.default.green("[\u21B5] restore here") + import_chalk12.default.gray(" ") + import_chalk12.default.yellow("[s] session start") + import_chalk12.default.gray(" ") + import_chalk12.default.gray("[q] quit") + "\n"
11149
+ import_chalk13.default.gray(" ") + (idx < total - 1 ? import_chalk13.default.white("[\u2190] older") : import_chalk13.default.gray("[\u2190] older")) + import_chalk13.default.gray(" ") + (idx > 0 ? import_chalk13.default.white("[\u2192] newer") : import_chalk13.default.gray("[\u2192] newer")) + import_chalk13.default.gray(" ") + import_chalk13.default.green("[\u21B5] restore here") + import_chalk13.default.gray(" ") + import_chalk13.default.yellow("[s] session start") + import_chalk13.default.gray(" ") + import_chalk13.default.gray("[q] quit") + "\n"
10404
11150
  );
10405
11151
  }
10406
11152
  async function runUndoNavigator(entries) {
@@ -10454,19 +11200,19 @@ async function runUndoNavigator(entries) {
10454
11200
  cleanup();
10455
11201
  process.stdout.write(CLEAR_SCREEN);
10456
11202
  const entry = display[idx];
10457
- process.stdout.write(import_chalk12.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
11203
+ process.stdout.write(import_chalk13.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
10458
11204
  if (applyUndo(entry.hash, entry.cwd)) {
10459
- process.stdout.write(import_chalk12.default.green("\u2705 Reverted successfully.\n\n"));
11205
+ process.stdout.write(import_chalk13.default.green("\u2705 Reverted successfully.\n\n"));
10460
11206
  resolve({ restored: true });
10461
11207
  } else {
10462
- process.stdout.write(import_chalk12.default.red("\u274C Undo failed.\n\n"));
11208
+ process.stdout.write(import_chalk13.default.red("\u274C Undo failed.\n\n"));
10463
11209
  resolve({ restored: false });
10464
11210
  }
10465
11211
  } else if (name === "q" || key?.ctrl && name === "c") {
10466
11212
  done = true;
10467
11213
  cleanup();
10468
11214
  process.stdout.write(CLEAR_SCREEN);
10469
- process.stdout.write(import_chalk12.default.gray("\nCancelled.\n\n"));
11215
+ process.stdout.write(import_chalk13.default.gray("\nCancelled.\n\n"));
10470
11216
  resolve({ restored: false });
10471
11217
  }
10472
11218
  };
@@ -10480,7 +11226,7 @@ function findMatchingCwd(startDir, history) {
10480
11226
  let dir = startDir;
10481
11227
  while (true) {
10482
11228
  if (cwds.has(dir)) return dir;
10483
- const parent = import_path26.default.dirname(dir);
11229
+ const parent = import_path29.default.dirname(dir);
10484
11230
  if (parent === dir) return null;
10485
11231
  dir = parent;
10486
11232
  }
@@ -10502,39 +11248,39 @@ function registerUndoCommand(program2) {
10502
11248
  if (history.length === 0) {
10503
11249
  if (!options.all && allHistory.length > 0) {
10504
11250
  console.log(
10505
- import_chalk13.default.yellow(
11251
+ import_chalk14.default.yellow(
10506
11252
  `
10507
11253
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
10508
- Run ${import_chalk13.default.cyan("node9 undo --all")} to see snapshots from all projects.
11254
+ Run ${import_chalk14.default.cyan("node9 undo --all")} to see snapshots from all projects.
10509
11255
  `
10510
11256
  )
10511
11257
  );
10512
11258
  } else {
10513
- console.log(import_chalk13.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
11259
+ console.log(import_chalk14.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
10514
11260
  }
10515
11261
  return;
10516
11262
  }
10517
11263
  if (options.list) {
10518
- console.log(import_chalk13.default.magenta.bold("\n\u23EA Snapshot History\n"));
11264
+ console.log(import_chalk14.default.magenta.bold("\n\u23EA Snapshot History\n"));
10519
11265
  console.log(
10520
- import_chalk13.default.gray(
11266
+ import_chalk14.default.gray(
10521
11267
  ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
10522
11268
  )
10523
11269
  );
10524
- console.log(import_chalk13.default.gray(" " + "\u2500".repeat(80)));
11270
+ console.log(import_chalk14.default.gray(" " + "\u2500".repeat(80)));
10525
11271
  const display = [...history].reverse();
10526
11272
  let prevTs = null;
10527
11273
  for (let i = 0; i < display.length; i++) {
10528
11274
  const e = display[i];
10529
11275
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
10530
- if (isGap) console.log(import_chalk13.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
11276
+ if (isGap) console.log(import_chalk14.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
10531
11277
  const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
10532
11278
  const tool = e.tool.slice(0, 8).padEnd(8);
10533
11279
  const when = formatAge2(e.timestamp).padEnd(10);
10534
11280
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
10535
11281
  console.log(
10536
- import_chalk13.default.white(
10537
- ` ${String(i + 1).padEnd(3)} ${label} ${import_chalk13.default.cyan(tool)} ${import_chalk13.default.gray(when)} ${import_chalk13.default.gray(dir)}`
11282
+ import_chalk14.default.white(
11283
+ ` ${String(i + 1).padEnd(3)} ${label} ${import_chalk14.default.cyan(tool)} ${import_chalk14.default.gray(when)} ${import_chalk14.default.gray(dir)}`
10538
11284
  )
10539
11285
  );
10540
11286
  prevTs = e.timestamp;
@@ -10547,7 +11293,7 @@ function registerUndoCommand(program2) {
10547
11293
  const idx = history.length - steps;
10548
11294
  if (idx < 0) {
10549
11295
  console.log(
10550
- import_chalk13.default.yellow(
11296
+ import_chalk14.default.yellow(
10551
11297
  `
10552
11298
  \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10553
11299
  `
@@ -10558,47 +11304,47 @@ function registerUndoCommand(program2) {
10558
11304
  const snapshot = history[idx];
10559
11305
  const ageStr = formatAge2(snapshot.timestamp);
10560
11306
  console.log(
10561
- import_chalk13.default.magenta.bold(`
11307
+ import_chalk14.default.magenta.bold(`
10562
11308
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
10563
11309
  );
10564
11310
  console.log(
10565
- import_chalk13.default.white(
10566
- ` Tool: ${import_chalk13.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk13.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
11311
+ import_chalk14.default.white(
11312
+ ` Tool: ${import_chalk14.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk14.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
10567
11313
  )
10568
11314
  );
10569
- console.log(import_chalk13.default.white(` When: ${import_chalk13.default.gray(ageStr)}`));
10570
- console.log(import_chalk13.default.white(` Dir: ${import_chalk13.default.gray(snapshot.cwd)}`));
11315
+ console.log(import_chalk14.default.white(` When: ${import_chalk14.default.gray(ageStr)}`));
11316
+ console.log(import_chalk14.default.white(` Dir: ${import_chalk14.default.gray(snapshot.cwd)}`));
10571
11317
  if (steps > 1)
10572
11318
  console.log(
10573
- import_chalk13.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
11319
+ import_chalk14.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10574
11320
  );
10575
11321
  console.log("");
10576
11322
  const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10577
11323
  if (diff) {
10578
11324
  const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10579
11325
  for (const line of lines) {
10580
- if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk13.default.bold(line));
10581
- else if (line.startsWith("+")) console.log(import_chalk13.default.green(line));
10582
- else if (line.startsWith("-")) console.log(import_chalk13.default.red(line));
10583
- else if (line.startsWith("@@")) console.log(import_chalk13.default.cyan(line));
10584
- else console.log(import_chalk13.default.gray(line));
11326
+ if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk14.default.bold(line));
11327
+ else if (line.startsWith("+")) console.log(import_chalk14.default.green(line));
11328
+ else if (line.startsWith("-")) console.log(import_chalk14.default.red(line));
11329
+ else if (line.startsWith("@@")) console.log(import_chalk14.default.cyan(line));
11330
+ else console.log(import_chalk14.default.gray(line));
10585
11331
  }
10586
11332
  console.log("");
10587
11333
  } else {
10588
11334
  console.log(
10589
- import_chalk13.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
11335
+ import_chalk14.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
10590
11336
  );
10591
11337
  }
10592
11338
  const { confirm: confirm3 } = await import("@inquirer/prompts");
10593
11339
  const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10594
11340
  if (proceed) {
10595
11341
  if (applyUndo(snapshot.hash, snapshot.cwd)) {
10596
- console.log(import_chalk13.default.green("\n\u2705 Reverted successfully.\n"));
11342
+ console.log(import_chalk14.default.green("\n\u2705 Reverted successfully.\n"));
10597
11343
  } else {
10598
- console.error(import_chalk13.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
11344
+ console.error(import_chalk14.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10599
11345
  }
10600
11346
  } else {
10601
- console.log(import_chalk13.default.gray("\nCancelled.\n"));
11347
+ console.log(import_chalk14.default.gray("\nCancelled.\n"));
10602
11348
  }
10603
11349
  return;
10604
11350
  }
@@ -10607,7 +11353,7 @@ function registerUndoCommand(program2) {
10607
11353
  }
10608
11354
 
10609
11355
  // src/cli/commands/watch.ts
10610
- var import_chalk14 = __toESM(require("chalk"));
11356
+ var import_chalk15 = __toESM(require("chalk"));
10611
11357
  var import_child_process12 = require("child_process");
10612
11358
  init_daemon();
10613
11359
  function registerWatchCommand(program2) {
@@ -10624,7 +11370,7 @@ function registerWatchCommand(program2) {
10624
11370
  throw new Error("not running");
10625
11371
  }
10626
11372
  } catch {
10627
- console.error(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
11373
+ console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10628
11374
  const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10629
11375
  detached: true,
10630
11376
  stdio: "ignore",
@@ -10646,12 +11392,12 @@ function registerWatchCommand(program2) {
10646
11392
  }
10647
11393
  }
10648
11394
  if (!ready) {
10649
- console.error(import_chalk14.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
11395
+ console.error(import_chalk15.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
10650
11396
  process.exit(1);
10651
11397
  }
10652
11398
  }
10653
11399
  console.error(
10654
- import_chalk14.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk14.default.dim(` \u2192 localhost:${port}`) + import_chalk14.default.dim(
11400
+ import_chalk15.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk15.default.dim(` \u2192 localhost:${port}`) + import_chalk15.default.dim(
10655
11401
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
10656
11402
  )
10657
11403
  );
@@ -10660,7 +11406,7 @@ function registerWatchCommand(program2) {
10660
11406
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
10661
11407
  });
10662
11408
  if (result.error) {
10663
- console.error(import_chalk14.default.red(`\u274C Failed to run command: ${result.error.message}`));
11409
+ console.error(import_chalk15.default.red(`\u274C Failed to run command: ${result.error.message}`));
10664
11410
  process.exit(1);
10665
11411
  }
10666
11412
  process.exit(result.status ?? 0);
@@ -10669,19 +11415,19 @@ function registerWatchCommand(program2) {
10669
11415
 
10670
11416
  // src/mcp-gateway/index.ts
10671
11417
  var import_readline3 = __toESM(require("readline"));
10672
- var import_chalk15 = __toESM(require("chalk"));
11418
+ var import_chalk16 = __toESM(require("chalk"));
10673
11419
  var import_child_process13 = require("child_process");
10674
11420
  var import_execa3 = require("execa");
10675
11421
  init_orchestrator();
10676
11422
  init_provenance();
10677
11423
 
10678
11424
  // src/mcp-pin.ts
10679
- var import_fs24 = __toESM(require("fs"));
10680
- var import_path27 = __toESM(require("path"));
10681
- var import_os20 = __toESM(require("os"));
10682
- var import_crypto8 = __toESM(require("crypto"));
11425
+ var import_fs27 = __toESM(require("fs"));
11426
+ var import_path30 = __toESM(require("path"));
11427
+ var import_os23 = __toESM(require("os"));
11428
+ var import_crypto9 = __toESM(require("crypto"));
10683
11429
  function getPinsFilePath() {
10684
- return import_path27.default.join(import_os20.default.homedir(), ".node9", "mcp-pins.json");
11430
+ return import_path30.default.join(import_os23.default.homedir(), ".node9", "mcp-pins.json");
10685
11431
  }
10686
11432
  function hashToolDefinitions(tools) {
10687
11433
  const sorted = [...tools].sort((a, b) => {
@@ -10690,15 +11436,15 @@ function hashToolDefinitions(tools) {
10690
11436
  return nameA.localeCompare(nameB);
10691
11437
  });
10692
11438
  const canonical = JSON.stringify(sorted);
10693
- return import_crypto8.default.createHash("sha256").update(canonical).digest("hex");
11439
+ return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
10694
11440
  }
10695
11441
  function getServerKey(upstreamCommand) {
10696
- return import_crypto8.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
11442
+ return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
10697
11443
  }
10698
11444
  function readMcpPinsSafe() {
10699
11445
  const filePath = getPinsFilePath();
10700
11446
  try {
10701
- const raw = import_fs24.default.readFileSync(filePath, "utf-8");
11447
+ const raw = import_fs27.default.readFileSync(filePath, "utf-8");
10702
11448
  if (!raw.trim()) {
10703
11449
  return { ok: false, reason: "corrupt", detail: "empty file" };
10704
11450
  }
@@ -10722,10 +11468,10 @@ function readMcpPins() {
10722
11468
  }
10723
11469
  function writeMcpPins(data) {
10724
11470
  const filePath = getPinsFilePath();
10725
- import_fs24.default.mkdirSync(import_path27.default.dirname(filePath), { recursive: true });
10726
- const tmp = `${filePath}.${import_crypto8.default.randomBytes(6).toString("hex")}.tmp`;
10727
- import_fs24.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10728
- import_fs24.default.renameSync(tmp, filePath);
11471
+ import_fs27.default.mkdirSync(import_path30.default.dirname(filePath), { recursive: true });
11472
+ const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
11473
+ import_fs27.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11474
+ import_fs27.default.renameSync(tmp, filePath);
10729
11475
  }
10730
11476
  function checkPin(serverKey, currentHash) {
10731
11477
  const result = readMcpPinsSafe();
@@ -10817,13 +11563,13 @@ async function runMcpGateway(upstreamCommand) {
10817
11563
  const prov = checkProvenance(executable);
10818
11564
  if (prov.trustLevel === "suspect") {
10819
11565
  console.error(
10820
- import_chalk15.default.red(
11566
+ import_chalk16.default.red(
10821
11567
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
10822
11568
  )
10823
11569
  );
10824
- console.error(import_chalk15.default.red(" Verify this binary is trusted before proceeding."));
11570
+ console.error(import_chalk16.default.red(" Verify this binary is trusted before proceeding."));
10825
11571
  }
10826
- console.error(import_chalk15.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
11572
+ console.error(import_chalk16.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10827
11573
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
10828
11574
  "NODE_OPTIONS",
10829
11575
  "NODE_PATH",
@@ -10926,10 +11672,10 @@ async function runMcpGateway(upstreamCommand) {
10926
11672
  mcpServer
10927
11673
  });
10928
11674
  if (!result.approved) {
10929
- console.error(import_chalk15.default.red(`
11675
+ console.error(import_chalk16.default.red(`
10930
11676
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
10931
- console.error(import_chalk15.default.gray(` Tool: ${toolName}`));
10932
- console.error(import_chalk15.default.gray(` Reason: ${result.reason ?? "Security Policy"}
11677
+ console.error(import_chalk16.default.gray(` Tool: ${toolName}`));
11678
+ console.error(import_chalk16.default.gray(` Reason: ${result.reason ?? "Security Policy"}
10933
11679
  `));
10934
11680
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
10935
11681
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -11008,7 +11754,7 @@ async function runMcpGateway(upstreamCommand) {
11008
11754
  updatePin(serverKey, upstreamCommand, currentHash, toolNames);
11009
11755
  pinState = "validated";
11010
11756
  console.error(
11011
- import_chalk15.default.green(
11757
+ import_chalk16.default.green(
11012
11758
  `\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
11013
11759
  )
11014
11760
  );
@@ -11021,11 +11767,11 @@ async function runMcpGateway(upstreamCommand) {
11021
11767
  } else if (pinStatus === "corrupt") {
11022
11768
  pinState = "quarantined";
11023
11769
  console.error(
11024
- import_chalk15.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11770
+ import_chalk16.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11025
11771
  );
11026
- console.error(import_chalk15.default.red(" Tool calls are blocked until the pin file is repaired."));
11772
+ console.error(import_chalk16.default.red(" Tool calls are blocked until the pin file is repaired."));
11027
11773
  console.error(
11028
- import_chalk15.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11774
+ import_chalk16.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11029
11775
  `)
11030
11776
  );
11031
11777
  const errorResponse = {
@@ -11042,13 +11788,13 @@ async function runMcpGateway(upstreamCommand) {
11042
11788
  } else {
11043
11789
  pinState = "quarantined";
11044
11790
  console.error(
11045
- import_chalk15.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11791
+ import_chalk16.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11046
11792
  );
11047
11793
  console.error(
11048
- import_chalk15.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11794
+ import_chalk16.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11049
11795
  );
11050
- console.error(import_chalk15.default.red(" Session quarantined \u2014 all tool calls blocked."));
11051
- console.error(import_chalk15.default.yellow(` Run: node9 mcp pin update ${serverKey}
11796
+ console.error(import_chalk16.default.red(" Session quarantined \u2014 all tool calls blocked."));
11797
+ console.error(import_chalk16.default.yellow(` Run: node9 mcp pin update ${serverKey}
11052
11798
  `));
11053
11799
  const errorResponse = {
11054
11800
  jsonrpc: "2.0",
@@ -11097,9 +11843,9 @@ function registerMcpGatewayCommand(program2) {
11097
11843
 
11098
11844
  // src/mcp-server/index.ts
11099
11845
  var import_readline4 = __toESM(require("readline"));
11100
- var import_fs25 = __toESM(require("fs"));
11101
- var import_os21 = __toESM(require("os"));
11102
- var import_path28 = __toESM(require("path"));
11846
+ var import_fs28 = __toESM(require("fs"));
11847
+ var import_os24 = __toESM(require("os"));
11848
+ var import_path31 = __toESM(require("path"));
11103
11849
  init_core();
11104
11850
  init_daemon();
11105
11851
  init_shields();
@@ -11274,13 +12020,13 @@ function handleStatus() {
11274
12020
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11275
12021
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11276
12022
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11277
- const projectConfig = import_path28.default.join(process.cwd(), "node9.config.json");
11278
- const globalConfig = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
12023
+ const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
12024
+ const globalConfig = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
11279
12025
  lines.push(
11280
- `Project config (node9.config.json): ${import_fs25.default.existsSync(projectConfig) ? "present" : "not found"}`
12026
+ `Project config (node9.config.json): ${import_fs28.default.existsSync(projectConfig) ? "present" : "not found"}`
11281
12027
  );
11282
12028
  lines.push(
11283
- `Global config (~/.node9/config.json): ${import_fs25.default.existsSync(globalConfig) ? "present" : "not found"}`
12029
+ `Global config (~/.node9/config.json): ${import_fs28.default.existsSync(globalConfig) ? "present" : "not found"}`
11284
12030
  );
11285
12031
  return lines.join("\n");
11286
12032
  }
@@ -11354,21 +12100,21 @@ function handleShieldDisable(args) {
11354
12100
  writeActiveShields(active.filter((s) => s !== name));
11355
12101
  return `Shield "${name}" disabled.`;
11356
12102
  }
11357
- var GLOBAL_CONFIG_PATH2 = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
12103
+ var GLOBAL_CONFIG_PATH2 = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
11358
12104
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11359
12105
  function readGlobalConfigRaw() {
11360
12106
  try {
11361
- if (import_fs25.default.existsSync(GLOBAL_CONFIG_PATH2)) {
11362
- return JSON.parse(import_fs25.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12107
+ if (import_fs28.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12108
+ return JSON.parse(import_fs28.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11363
12109
  }
11364
12110
  } catch {
11365
12111
  }
11366
12112
  return {};
11367
12113
  }
11368
12114
  function writeGlobalConfigRaw(data) {
11369
- const dir = import_path28.default.dirname(GLOBAL_CONFIG_PATH2);
11370
- if (!import_fs25.default.existsSync(dir)) import_fs25.default.mkdirSync(dir, { recursive: true });
11371
- import_fs25.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12115
+ const dir = import_path31.default.dirname(GLOBAL_CONFIG_PATH2);
12116
+ if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
12117
+ import_fs28.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11372
12118
  }
11373
12119
  function handleApproverList() {
11374
12120
  const config = getConfig();
@@ -11411,9 +12157,9 @@ function handleApproverSet(args) {
11411
12157
  }
11412
12158
  function handleAuditGet(args) {
11413
12159
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11414
- const auditPath = import_path28.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11415
- if (!import_fs25.default.existsSync(auditPath)) return "No audit log found.";
11416
- const lines = import_fs25.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
12160
+ const auditPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "audit.log");
12161
+ if (!import_fs28.default.existsSync(auditPath)) return "No audit log found.";
12162
+ const lines = import_fs28.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11417
12163
  const recent = lines.slice(-limit);
11418
12164
  const entries = recent.map((line) => {
11419
12165
  try {
@@ -11600,7 +12346,7 @@ function registerMcpServerCommand(program2) {
11600
12346
  }
11601
12347
 
11602
12348
  // src/cli/commands/trust.ts
11603
- var import_chalk16 = __toESM(require("chalk"));
12349
+ var import_chalk17 = __toESM(require("chalk"));
11604
12350
  init_trusted_hosts();
11605
12351
  function isValidHost(host) {
11606
12352
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
@@ -11611,51 +12357,51 @@ function registerTrustCommand(program2) {
11611
12357
  const normalized = normalizeHost(host.trim());
11612
12358
  if (!isValidHost(normalized)) {
11613
12359
  console.error(
11614
- import_chalk16.default.red(`
12360
+ import_chalk17.default.red(`
11615
12361
  \u274C Invalid host: "${host}"
11616
- `) + import_chalk16.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
12362
+ `) + import_chalk17.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
11617
12363
  );
11618
12364
  process.exit(1);
11619
12365
  }
11620
12366
  addTrustedHost(normalized);
11621
- console.log(import_chalk16.default.green(`
12367
+ console.log(import_chalk17.default.green(`
11622
12368
  \u2705 ${normalized} added to trusted hosts.`));
11623
12369
  console.log(
11624
- import_chalk16.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
12370
+ import_chalk17.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
11625
12371
  );
11626
12372
  });
11627
12373
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
11628
12374
  const normalized = normalizeHost(host.trim());
11629
12375
  const removed = removeTrustedHost(normalized);
11630
12376
  if (!removed) {
11631
- console.error(import_chalk16.default.yellow(`
12377
+ console.error(import_chalk17.default.yellow(`
11632
12378
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
11633
12379
  `));
11634
12380
  process.exit(1);
11635
12381
  }
11636
- console.log(import_chalk16.default.green(`
12382
+ console.log(import_chalk17.default.green(`
11637
12383
  \u2705 ${normalized} removed from trusted hosts.
11638
12384
  `));
11639
12385
  });
11640
12386
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
11641
12387
  const hosts = readTrustedHosts();
11642
12388
  if (hosts.length === 0) {
11643
- console.log(import_chalk16.default.gray("\n No trusted hosts configured.\n"));
11644
- console.log(` Add one: ${import_chalk16.default.cyan("node9 trust add api.mycompany.com")}
12389
+ console.log(import_chalk17.default.gray("\n No trusted hosts configured.\n"));
12390
+ console.log(` Add one: ${import_chalk17.default.cyan("node9 trust add api.mycompany.com")}
11645
12391
  `);
11646
12392
  return;
11647
12393
  }
11648
- console.log(import_chalk16.default.bold("\n\u{1F513} Trusted Hosts\n"));
12394
+ console.log(import_chalk17.default.bold("\n\u{1F513} Trusted Hosts\n"));
11649
12395
  for (const entry of hosts) {
11650
12396
  const date = new Date(entry.addedAt).toLocaleDateString();
11651
- console.log(` ${import_chalk16.default.cyan(entry.host.padEnd(40))} ${import_chalk16.default.gray(`added ${date}`)}`);
12397
+ console.log(` ${import_chalk17.default.cyan(entry.host.padEnd(40))} ${import_chalk17.default.gray(`added ${date}`)}`);
11652
12398
  }
11653
12399
  console.log("");
11654
12400
  });
11655
12401
  }
11656
12402
 
11657
12403
  // src/cli/commands/mcp-pin.ts
11658
- var import_chalk17 = __toESM(require("chalk"));
12404
+ var import_chalk18 = __toESM(require("chalk"));
11659
12405
  function registerMcpPinCommand(program2) {
11660
12406
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
11661
12407
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -11663,31 +12409,31 @@ function registerMcpPinCommand(program2) {
11663
12409
  const result = readMcpPinsSafe();
11664
12410
  if (!result.ok) {
11665
12411
  if (result.reason === "missing") {
11666
- console.log(import_chalk17.default.gray("\nNo MCP servers are pinned yet."));
12412
+ console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
11667
12413
  console.log(
11668
- import_chalk17.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
12414
+ import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
11669
12415
  );
11670
12416
  return;
11671
12417
  }
11672
- console.error(import_chalk17.default.red(`
12418
+ console.error(import_chalk18.default.red(`
11673
12419
  \u274C Pin file is corrupt: ${result.detail}`));
11674
- console.error(import_chalk17.default.yellow(" Run: node9 mcp pin reset\n"));
12420
+ console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
11675
12421
  process.exit(1);
11676
12422
  }
11677
12423
  const entries = Object.entries(result.pins.servers);
11678
12424
  if (entries.length === 0) {
11679
- console.log(import_chalk17.default.gray("\nNo MCP servers are pinned yet."));
12425
+ console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
11680
12426
  console.log(
11681
- import_chalk17.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
12427
+ import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
11682
12428
  );
11683
12429
  return;
11684
12430
  }
11685
- console.log(import_chalk17.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
12431
+ console.log(import_chalk18.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
11686
12432
  for (const [key, entry] of entries) {
11687
- console.log(` ${import_chalk17.default.cyan(key)} ${import_chalk17.default.gray(entry.label)}`);
11688
- console.log(` Tools (${entry.toolCount}): ${import_chalk17.default.white(entry.toolNames.join(", "))}`);
11689
- console.log(` Hash: ${import_chalk17.default.gray(entry.toolsHash.slice(0, 16))}...`);
11690
- console.log(` Pinned: ${import_chalk17.default.gray(entry.pinnedAt)}`);
12433
+ console.log(` ${import_chalk18.default.cyan(key)} ${import_chalk18.default.gray(entry.label)}`);
12434
+ console.log(` Tools (${entry.toolCount}): ${import_chalk18.default.white(entry.toolNames.join(", "))}`);
12435
+ console.log(` Hash: ${import_chalk18.default.gray(entry.toolsHash.slice(0, 16))}...`);
12436
+ console.log(` Pinned: ${import_chalk18.default.gray(entry.pinnedAt)}`);
11691
12437
  console.log("");
11692
12438
  }
11693
12439
  });
@@ -11698,55 +12444,55 @@ function registerMcpPinCommand(program2) {
11698
12444
  try {
11699
12445
  pins = readMcpPins();
11700
12446
  } catch {
11701
- console.error(import_chalk17.default.red("\n\u274C Pin file is corrupt."));
11702
- console.error(import_chalk17.default.yellow(" Run: node9 mcp pin reset\n"));
12447
+ console.error(import_chalk18.default.red("\n\u274C Pin file is corrupt."));
12448
+ console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
11703
12449
  process.exit(1);
11704
12450
  }
11705
12451
  if (!pins.servers[serverKey]) {
11706
- console.error(import_chalk17.default.red(`
12452
+ console.error(import_chalk18.default.red(`
11707
12453
  \u274C No pin found for server key "${serverKey}"
11708
12454
  `));
11709
- console.error(`Run ${import_chalk17.default.cyan("node9 mcp pin list")} to see pinned servers.
12455
+ console.error(`Run ${import_chalk18.default.cyan("node9 mcp pin list")} to see pinned servers.
11710
12456
  `);
11711
12457
  process.exit(1);
11712
12458
  }
11713
12459
  const label = pins.servers[serverKey].label;
11714
12460
  removePin(serverKey);
11715
- console.log(import_chalk17.default.green(`
11716
- \u{1F513} Pin removed for ${import_chalk17.default.cyan(serverKey)}`));
11717
- console.log(import_chalk17.default.gray(` Server: ${label}`));
11718
- console.log(import_chalk17.default.gray(" Next connection will re-pin with current tool definitions.\n"));
12461
+ console.log(import_chalk18.default.green(`
12462
+ \u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
12463
+ console.log(import_chalk18.default.gray(` Server: ${label}`));
12464
+ console.log(import_chalk18.default.gray(" Next connection will re-pin with current tool definitions.\n"));
11719
12465
  });
11720
12466
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
11721
12467
  const result = readMcpPinsSafe();
11722
12468
  if (!result.ok && result.reason === "missing") {
11723
- console.log(import_chalk17.default.gray("\nNo pins to clear.\n"));
12469
+ console.log(import_chalk18.default.gray("\nNo pins to clear.\n"));
11724
12470
  return;
11725
12471
  }
11726
12472
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
11727
12473
  clearAllPins();
11728
- console.log(import_chalk17.default.green(`
12474
+ console.log(import_chalk18.default.green(`
11729
12475
  \u{1F513} Cleared ${count} MCP pin(s).`));
11730
- console.log(import_chalk17.default.gray(" Next connection to each server will re-pin.\n"));
12476
+ console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
11731
12477
  });
11732
12478
  }
11733
12479
 
11734
12480
  // src/cli.ts
11735
12481
  var { version } = JSON.parse(
11736
- import_fs28.default.readFileSync(import_path31.default.join(__dirname, "../package.json"), "utf-8")
12482
+ import_fs31.default.readFileSync(import_path34.default.join(__dirname, "../package.json"), "utf-8")
11737
12483
  );
11738
12484
  var program = new import_commander.Command();
11739
12485
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
11740
12486
  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) => {
11741
12487
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
11742
- const credPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "credentials.json");
11743
- if (!import_fs28.default.existsSync(import_path31.default.dirname(credPath)))
11744
- import_fs28.default.mkdirSync(import_path31.default.dirname(credPath), { recursive: true });
12488
+ const credPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "credentials.json");
12489
+ if (!import_fs31.default.existsSync(import_path34.default.dirname(credPath)))
12490
+ import_fs31.default.mkdirSync(import_path34.default.dirname(credPath), { recursive: true });
11745
12491
  const profileName = options.profile || "default";
11746
12492
  let existingCreds = {};
11747
12493
  try {
11748
- if (import_fs28.default.existsSync(credPath)) {
11749
- const raw = JSON.parse(import_fs28.default.readFileSync(credPath, "utf-8"));
12494
+ if (import_fs31.default.existsSync(credPath)) {
12495
+ const raw = JSON.parse(import_fs31.default.readFileSync(credPath, "utf-8"));
11750
12496
  if (raw.apiKey) {
11751
12497
  existingCreds = {
11752
12498
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -11758,13 +12504,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11758
12504
  } catch {
11759
12505
  }
11760
12506
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
11761
- import_fs28.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12507
+ import_fs31.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
11762
12508
  if (profileName === "default") {
11763
- const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
12509
+ const configPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
11764
12510
  let config = {};
11765
12511
  try {
11766
- if (import_fs28.default.existsSync(configPath))
11767
- config = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
12512
+ if (import_fs31.default.existsSync(configPath))
12513
+ config = JSON.parse(import_fs31.default.readFileSync(configPath, "utf-8"));
11768
12514
  } catch {
11769
12515
  }
11770
12516
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -11779,19 +12525,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11779
12525
  approvers.cloud = false;
11780
12526
  }
11781
12527
  s.approvers = approvers;
11782
- if (!import_fs28.default.existsSync(import_path31.default.dirname(configPath)))
11783
- import_fs28.default.mkdirSync(import_path31.default.dirname(configPath), { recursive: true });
11784
- import_fs28.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12528
+ if (!import_fs31.default.existsSync(import_path34.default.dirname(configPath)))
12529
+ import_fs31.default.mkdirSync(import_path34.default.dirname(configPath), { recursive: true });
12530
+ import_fs31.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
11785
12531
  }
11786
12532
  if (options.profile && profileName !== "default") {
11787
- console.log(import_chalk19.default.green(`\u2705 Profile "${profileName}" saved`));
11788
- console.log(import_chalk19.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
12533
+ console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
12534
+ console.log(import_chalk20.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
11789
12535
  } else if (options.local) {
11790
- console.log(import_chalk19.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
11791
- console.log(import_chalk19.default.gray(` All decisions stay on this machine.`));
12536
+ console.log(import_chalk20.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
12537
+ console.log(import_chalk20.default.gray(` All decisions stay on this machine.`));
11792
12538
  } else {
11793
- console.log(import_chalk19.default.green(`\u2705 Logged in \u2014 agent mode`));
11794
- console.log(import_chalk19.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
12539
+ console.log(import_chalk20.default.green(`\u2705 Logged in \u2014 agent mode`));
12540
+ console.log(import_chalk20.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
11795
12541
  }
11796
12542
  });
11797
12543
  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) => {
@@ -11799,19 +12545,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
11799
12545
  if (target === "claude") return await setupClaude();
11800
12546
  if (target === "cursor") return await setupCursor();
11801
12547
  if (target === "hud") return setupHud();
11802
- console.error(import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12548
+ console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11803
12549
  process.exit(1);
11804
12550
  });
11805
12551
  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) => {
11806
12552
  if (!target) {
11807
- console.log(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
11808
- console.log(" Usage: " + import_chalk19.default.white("node9 setup <target>") + "\n");
12553
+ console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
12554
+ console.log(" Usage: " + import_chalk20.default.white("node9 setup <target>") + "\n");
11809
12555
  console.log(" Targets:");
11810
- console.log(" " + import_chalk19.default.green("claude") + " \u2014 Claude Code (hook mode)");
11811
- console.log(" " + import_chalk19.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
11812
- console.log(" " + import_chalk19.default.green("cursor") + " \u2014 Cursor (hook mode)");
12556
+ console.log(" " + import_chalk20.default.green("claude") + " \u2014 Claude Code (hook mode)");
12557
+ console.log(" " + import_chalk20.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
12558
+ console.log(" " + import_chalk20.default.green("cursor") + " \u2014 Cursor (hook mode)");
11813
12559
  process.stdout.write(
11814
- " " + import_chalk19.default.green("hud") + " \u2014 Claude Code security statusline\n"
12560
+ " " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
11815
12561
  );
11816
12562
  console.log("");
11817
12563
  return;
@@ -11821,7 +12567,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
11821
12567
  if (t === "claude") return await setupClaude();
11822
12568
  if (t === "cursor") return await setupCursor();
11823
12569
  if (t === "hud") return setupHud();
11824
- console.error(import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12570
+ console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11825
12571
  process.exit(1);
11826
12572
  });
11827
12573
  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) => {
@@ -11832,31 +12578,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
11832
12578
  else if (target === "hud") fn = teardownHud;
11833
12579
  else {
11834
12580
  console.error(
11835
- import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
12581
+ import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
11836
12582
  );
11837
12583
  process.exit(1);
11838
12584
  }
11839
- console.log(import_chalk19.default.cyan(`
12585
+ console.log(import_chalk20.default.cyan(`
11840
12586
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
11841
12587
  `));
11842
12588
  try {
11843
12589
  fn();
11844
12590
  } catch (err2) {
11845
- console.error(import_chalk19.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
12591
+ console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
11846
12592
  process.exit(1);
11847
12593
  }
11848
- console.log(import_chalk19.default.gray("\n Restart the agent for changes to take effect."));
12594
+ console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
11849
12595
  });
11850
12596
  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) => {
11851
- console.log(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
11852
- console.log(import_chalk19.default.bold("Stopping daemon..."));
12597
+ console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
12598
+ console.log(import_chalk20.default.bold("Stopping daemon..."));
11853
12599
  try {
11854
12600
  stopDaemon();
11855
- console.log(import_chalk19.default.green(" \u2705 Daemon stopped"));
12601
+ console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
11856
12602
  } catch {
11857
- console.log(import_chalk19.default.blue(" \u2139\uFE0F Daemon was not running"));
12603
+ console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
11858
12604
  }
11859
- console.log(import_chalk19.default.bold("\nRemoving hooks..."));
12605
+ console.log(import_chalk20.default.bold("\nRemoving hooks..."));
11860
12606
  let teardownFailed = false;
11861
12607
  for (const [label, fn] of [
11862
12608
  ["Claude", teardownClaude],
@@ -11868,45 +12614,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
11868
12614
  } catch (err2) {
11869
12615
  teardownFailed = true;
11870
12616
  console.error(
11871
- import_chalk19.default.red(
12617
+ import_chalk20.default.red(
11872
12618
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
11873
12619
  )
11874
12620
  );
11875
12621
  }
11876
12622
  }
11877
12623
  if (options.purge) {
11878
- const node9Dir = import_path31.default.join(import_os24.default.homedir(), ".node9");
11879
- if (import_fs28.default.existsSync(node9Dir)) {
12624
+ const node9Dir = import_path34.default.join(import_os27.default.homedir(), ".node9");
12625
+ if (import_fs31.default.existsSync(node9Dir)) {
11880
12626
  const confirmed = await (0, import_prompts2.confirm)({
11881
12627
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
11882
12628
  default: false
11883
12629
  });
11884
12630
  if (confirmed) {
11885
- import_fs28.default.rmSync(node9Dir, { recursive: true });
11886
- if (import_fs28.default.existsSync(node9Dir)) {
12631
+ import_fs31.default.rmSync(node9Dir, { recursive: true });
12632
+ if (import_fs31.default.existsSync(node9Dir)) {
11887
12633
  console.error(
11888
- import_chalk19.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12634
+ import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
11889
12635
  );
11890
12636
  } else {
11891
- console.log(import_chalk19.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
12637
+ console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
11892
12638
  }
11893
12639
  } else {
11894
- console.log(import_chalk19.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12640
+ console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
11895
12641
  }
11896
12642
  } else {
11897
- console.log(import_chalk19.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
12643
+ console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
11898
12644
  }
11899
12645
  } else {
11900
12646
  console.log(
11901
- import_chalk19.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
12647
+ import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
11902
12648
  );
11903
12649
  }
11904
12650
  if (teardownFailed) {
11905
- console.error(import_chalk19.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
12651
+ console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
11906
12652
  process.exit(1);
11907
12653
  }
11908
- console.log(import_chalk19.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
11909
- console.log(import_chalk19.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
12654
+ console.log(import_chalk20.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
12655
+ console.log(import_chalk20.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
11910
12656
  });
11911
12657
  registerDoctorCommand(program, version);
11912
12658
  program.command("explain").description(
@@ -11919,7 +12665,7 @@ program.command("explain").description(
11919
12665
  try {
11920
12666
  args = JSON.parse(trimmed);
11921
12667
  } catch {
11922
- console.error(import_chalk19.default.red(`
12668
+ console.error(import_chalk20.default.red(`
11923
12669
  \u274C Invalid JSON: ${trimmed}
11924
12670
  `));
11925
12671
  process.exit(1);
@@ -11930,60 +12676,61 @@ program.command("explain").description(
11930
12676
  }
11931
12677
  const result = await explainPolicy(tool, args);
11932
12678
  console.log("");
11933
- console.log(import_chalk19.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12679
+ console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
11934
12680
  console.log("");
11935
- console.log(` ${import_chalk19.default.bold("Tool:")} ${import_chalk19.default.white(result.tool)}`);
12681
+ console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
11936
12682
  if (argsRaw) {
11937
12683
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
11938
- console.log(` ${import_chalk19.default.bold("Input:")} ${import_chalk19.default.gray(preview)}`);
12684
+ console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
11939
12685
  }
11940
12686
  console.log("");
11941
- console.log(import_chalk19.default.bold("Config Sources (Waterfall):"));
12687
+ console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
11942
12688
  for (const tier of result.waterfall) {
11943
- const num = import_chalk19.default.gray(` ${tier.tier}.`);
12689
+ const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
11944
12690
  const label = tier.label.padEnd(16);
11945
12691
  let statusStr;
11946
12692
  if (tier.tier === 1) {
11947
- statusStr = import_chalk19.default.gray(tier.note ?? "");
12693
+ statusStr = import_chalk20.default.gray(tier.note ?? "");
11948
12694
  } else if (tier.status === "active") {
11949
- const loc = tier.path ? import_chalk19.default.gray(tier.path) : "";
11950
- const note = tier.note ? import_chalk19.default.gray(`(${tier.note})`) : "";
11951
- statusStr = import_chalk19.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
12695
+ const loc = tier.path ? import_chalk20.default.gray(tier.path) : "";
12696
+ const note = tier.note ? import_chalk20.default.gray(`(${tier.note})`) : "";
12697
+ statusStr = import_chalk20.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
11952
12698
  } else {
11953
- statusStr = import_chalk19.default.gray("\u25CB " + (tier.note ?? "not found"));
12699
+ statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
11954
12700
  }
11955
- console.log(`${num} ${import_chalk19.default.white(label)} ${statusStr}`);
12701
+ console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
11956
12702
  }
11957
12703
  console.log("");
11958
- console.log(import_chalk19.default.bold("Policy Evaluation:"));
12704
+ console.log(import_chalk20.default.bold("Policy Evaluation:"));
11959
12705
  for (const step of result.steps) {
11960
12706
  const isFinal = step.isFinal;
11961
12707
  let icon;
11962
- if (step.outcome === "allow") icon = import_chalk19.default.green(" \u2705");
11963
- else if (step.outcome === "review") icon = import_chalk19.default.red(" \u{1F534}");
11964
- else if (step.outcome === "skip") icon = import_chalk19.default.gray(" \u2500 ");
11965
- else icon = import_chalk19.default.gray(" \u25CB ");
12708
+ if (step.outcome === "allow") icon = import_chalk20.default.green(" \u2705");
12709
+ else if (step.outcome === "review") icon = import_chalk20.default.red(" \u{1F534}");
12710
+ else if (step.outcome === "skip") icon = import_chalk20.default.gray(" \u2500 ");
12711
+ else icon = import_chalk20.default.gray(" \u25CB ");
11966
12712
  const name = step.name.padEnd(18);
11967
- const nameStr = isFinal ? import_chalk19.default.white.bold(name) : import_chalk19.default.white(name);
11968
- const detail = isFinal ? import_chalk19.default.white(step.detail) : import_chalk19.default.gray(step.detail);
11969
- const arrow = isFinal ? import_chalk19.default.yellow(" \u2190 STOP") : "";
12713
+ const nameStr = isFinal ? import_chalk20.default.white.bold(name) : import_chalk20.default.white(name);
12714
+ const detail = isFinal ? import_chalk20.default.white(step.detail) : import_chalk20.default.gray(step.detail);
12715
+ const arrow = isFinal ? import_chalk20.default.yellow(" \u2190 STOP") : "";
11970
12716
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
11971
12717
  }
11972
12718
  console.log("");
11973
12719
  if (result.decision === "allow") {
11974
- console.log(import_chalk19.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk19.default.gray(" \u2014 no approval needed"));
12720
+ console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
11975
12721
  } else {
11976
12722
  console.log(
11977
- import_chalk19.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk19.default.gray(" \u2014 human approval required")
12723
+ import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
11978
12724
  );
11979
12725
  if (result.blockedByLabel) {
11980
- console.log(import_chalk19.default.gray(` Reason: ${result.blockedByLabel}`));
12726
+ console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
11981
12727
  }
11982
12728
  }
11983
12729
  console.log("");
11984
12730
  });
11985
12731
  registerInitCommand(program);
11986
12732
  registerAuditCommand(program);
12733
+ registerReportCommand(program);
11987
12734
  registerStatusCommand(program);
11988
12735
  registerDaemonCommand(program);
11989
12736
  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) => {
@@ -11991,7 +12738,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
11991
12738
  try {
11992
12739
  await startTail2(options);
11993
12740
  } catch (err2) {
11994
- console.error(import_chalk19.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
12741
+ console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
11995
12742
  process.exit(1);
11996
12743
  }
11997
12744
  });
@@ -12023,14 +12770,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12023
12770
  Run "node9 addto claude" to register it as the statusLine.`
12024
12771
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12025
12772
  if (subcommand === "debug") {
12026
- const flagFile = import_path31.default.join(import_os24.default.homedir(), ".node9", "hud-debug");
12773
+ const flagFile = import_path34.default.join(import_os27.default.homedir(), ".node9", "hud-debug");
12027
12774
  if (state === "on") {
12028
- import_fs28.default.mkdirSync(import_path31.default.dirname(flagFile), { recursive: true });
12029
- import_fs28.default.writeFileSync(flagFile, "");
12775
+ import_fs31.default.mkdirSync(import_path34.default.dirname(flagFile), { recursive: true });
12776
+ import_fs31.default.writeFileSync(flagFile, "");
12030
12777
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12031
12778
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12032
12779
  } else if (state === "off") {
12033
- if (import_fs28.default.existsSync(flagFile)) import_fs28.default.unlinkSync(flagFile);
12780
+ if (import_fs31.default.existsSync(flagFile)) import_fs31.default.unlinkSync(flagFile);
12034
12781
  console.log("HUD debug logging disabled.");
12035
12782
  } else {
12036
12783
  console.error("Usage: node9 hud debug on|off");
@@ -12045,7 +12792,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12045
12792
  const ms = parseDuration(options.duration);
12046
12793
  if (ms === null) {
12047
12794
  console.error(
12048
- import_chalk19.default.red(`
12795
+ import_chalk20.default.red(`
12049
12796
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12050
12797
  `)
12051
12798
  );
@@ -12053,20 +12800,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12053
12800
  }
12054
12801
  pauseNode9(ms, options.duration);
12055
12802
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12056
- console.log(import_chalk19.default.yellow(`
12803
+ console.log(import_chalk20.default.yellow(`
12057
12804
  \u23F8 Node9 paused until ${expiresAt}`));
12058
- console.log(import_chalk19.default.gray(` All tool calls will be allowed without review.`));
12059
- console.log(import_chalk19.default.gray(` Run "node9 resume" to re-enable early.
12805
+ console.log(import_chalk20.default.gray(` All tool calls will be allowed without review.`));
12806
+ console.log(import_chalk20.default.gray(` Run "node9 resume" to re-enable early.
12060
12807
  `));
12061
12808
  });
12062
12809
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12063
12810
  const { paused } = checkPause();
12064
12811
  if (!paused) {
12065
- console.log(import_chalk19.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12812
+ console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12066
12813
  return;
12067
12814
  }
12068
12815
  resumeNode9();
12069
- console.log(import_chalk19.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12816
+ console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12070
12817
  });
12071
12818
  var HOOK_BASED_AGENTS = {
12072
12819
  claude: "claude",
@@ -12079,15 +12826,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12079
12826
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12080
12827
  const target = HOOK_BASED_AGENTS[firstArg2];
12081
12828
  console.error(
12082
- import_chalk19.default.yellow(`
12829
+ import_chalk20.default.yellow(`
12083
12830
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12084
12831
  );
12085
- console.error(import_chalk19.default.white(`
12832
+ console.error(import_chalk20.default.white(`
12086
12833
  "${target}" uses its own hook system. Use:`));
12087
12834
  console.error(
12088
- import_chalk19.default.green(` node9 addto ${target} `) + import_chalk19.default.gray("# one-time setup")
12835
+ import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
12089
12836
  );
12090
- console.error(import_chalk19.default.green(` ${target} `) + import_chalk19.default.gray("# run normally"));
12837
+ console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
12091
12838
  process.exit(1);
12092
12839
  }
12093
12840
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12104,7 +12851,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12104
12851
  }
12105
12852
  );
12106
12853
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12107
- console.error(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12854
+ console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12108
12855
  const daemonReady = await autoStartDaemonAndWait();
12109
12856
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12110
12857
  }
@@ -12117,12 +12864,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12117
12864
  }
12118
12865
  if (!result.approved) {
12119
12866
  console.error(
12120
- import_chalk19.default.red(`
12867
+ import_chalk20.default.red(`
12121
12868
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12122
12869
  );
12123
12870
  process.exit(1);
12124
12871
  }
12125
- console.error(import_chalk19.default.green("\n\u2705 Approved \u2014 running command...\n"));
12872
+ console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
12126
12873
  await runProxy(fullCommand);
12127
12874
  } else {
12128
12875
  program.help();
@@ -12137,9 +12884,9 @@ if (process.argv[2] !== "daemon") {
12137
12884
  const isCheckHook = process.argv[2] === "check";
12138
12885
  if (isCheckHook) {
12139
12886
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12140
- const logPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log");
12887
+ const logPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "hook-debug.log");
12141
12888
  const msg = reason instanceof Error ? reason.message : String(reason);
12142
- import_fs28.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12889
+ import_fs31.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12143
12890
  `);
12144
12891
  }
12145
12892
  process.exit(0);