@node9/proxy 1.9.3 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.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 path34 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path34}: ${issue.message}`;
165
173
  });
166
174
  return {
167
175
  sanitized,
@@ -261,6 +269,11 @@ var init_config_schema = __esm({
261
269
  dlp: import_zod.z.object({
262
270
  enabled: import_zod.z.boolean().optional(),
263
271
  scanIgnoredTools: import_zod.z.boolean().optional()
272
+ }).optional(),
273
+ loopDetection: import_zod.z.object({
274
+ enabled: import_zod.z.boolean().optional(),
275
+ threshold: import_zod.z.number().min(2).optional(),
276
+ windowSeconds: import_zod.z.number().min(10).optional()
264
277
  }).optional()
265
278
  }).optional(),
266
279
  environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
@@ -555,7 +568,8 @@ function getConfig(cwd) {
555
568
  onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
556
569
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
557
570
  },
558
- dlp: { ...DEFAULT_CONFIG.policy.dlp }
571
+ dlp: { ...DEFAULT_CONFIG.policy.dlp },
572
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
559
573
  };
560
574
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
561
575
  const applyLayer = (source) => {
@@ -594,6 +608,13 @@ function getConfig(cwd) {
594
608
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
595
609
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
596
610
  }
611
+ if (p.loopDetection) {
612
+ const ld = p.loopDetection;
613
+ if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
614
+ if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
615
+ if (ld.windowSeconds !== void 0)
616
+ mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
617
+ }
597
618
  const envs = source.environments || {};
598
619
  for (const [envName, envConfig] of Object.entries(envs)) {
599
620
  if (envConfig && typeof envConfig === "object") {
@@ -900,7 +921,8 @@ var init_config = __esm({
900
921
  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
922
  }
902
923
  ],
903
- dlp: { enabled: true, scanIgnoredTools: true }
924
+ dlp: { enabled: true, scanIgnoredTools: true },
925
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
904
926
  },
905
927
  environments: {}
906
928
  };
@@ -1704,9 +1726,9 @@ function matchesPattern(text, patterns) {
1704
1726
  const withoutDotSlash = text.replace(/^\.\//, "");
1705
1727
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1706
1728
  }
1707
- function getNestedValue(obj, path32) {
1729
+ function getNestedValue(obj, path34) {
1708
1730
  if (!obj || typeof obj !== "object") return null;
1709
- return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
1731
+ return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
1710
1732
  }
1711
1733
  function shouldSnapshot(toolName, args, config) {
1712
1734
  if (!config.settings.enableUndo) return false;
@@ -3079,6 +3101,58 @@ var init_cloud = __esm({
3079
3101
  }
3080
3102
  });
3081
3103
 
3104
+ // src/loop-detector.ts
3105
+ function loopStateFile() {
3106
+ return import_path14.default.join(import_os10.default.homedir(), ".node9", "loop-state.json");
3107
+ }
3108
+ function computeArgsHash(args) {
3109
+ const str = JSON.stringify(args ?? "");
3110
+ return import_crypto3.default.createHash("sha256").update(str).digest("hex").slice(0, 16);
3111
+ }
3112
+ function readState() {
3113
+ try {
3114
+ if (!import_fs11.default.existsSync(loopStateFile())) return [];
3115
+ const raw = import_fs11.default.readFileSync(loopStateFile(), "utf-8");
3116
+ const parsed = JSON.parse(raw);
3117
+ if (!Array.isArray(parsed)) return [];
3118
+ return parsed;
3119
+ } catch {
3120
+ return [];
3121
+ }
3122
+ }
3123
+ function writeState(records) {
3124
+ const dir = import_path14.default.dirname(loopStateFile());
3125
+ if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
3126
+ const tmpPath = `${loopStateFile()}.${import_os10.default.hostname()}.${process.pid}.tmp`;
3127
+ import_fs11.default.writeFileSync(tmpPath, JSON.stringify(records));
3128
+ import_fs11.default.renameSync(tmpPath, loopStateFile());
3129
+ }
3130
+ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
3131
+ try {
3132
+ const hash = computeArgsHash(args);
3133
+ const now = Date.now();
3134
+ const cutoff = now - windowMs;
3135
+ const records = readState().filter((r) => r.ts >= cutoff);
3136
+ records.push({ t: tool, h: hash, ts: now });
3137
+ const count = records.filter((r) => r.t === tool && r.h === hash).length;
3138
+ writeState(records.slice(-MAX_RECORDS));
3139
+ return { looping: count >= threshold, count };
3140
+ } catch {
3141
+ return { looping: false, count: 0 };
3142
+ }
3143
+ }
3144
+ var import_fs11, import_path14, import_os10, import_crypto3, MAX_RECORDS;
3145
+ var init_loop_detector = __esm({
3146
+ "src/loop-detector.ts"() {
3147
+ "use strict";
3148
+ import_fs11 = __toESM(require("fs"));
3149
+ import_path14 = __toESM(require("path"));
3150
+ import_os10 = __toESM(require("os"));
3151
+ import_crypto3 = __toESM(require("crypto"));
3152
+ MAX_RECORDS = 500;
3153
+ }
3154
+ });
3155
+
3082
3156
  // src/auth/orchestrator.ts
3083
3157
  function isWriteTool(toolName) {
3084
3158
  const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
@@ -3119,7 +3193,7 @@ function notifyActivity(data) {
3119
3193
  }
3120
3194
  async function authorizeHeadless(toolName, args, meta, options) {
3121
3195
  if (!options?.calledFromDaemon) {
3122
- const actId = (0, import_crypto3.randomUUID)();
3196
+ const actId = (0, import_crypto4.randomUUID)();
3123
3197
  const actTs = Date.now();
3124
3198
  await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
3125
3199
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
@@ -3166,6 +3240,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3166
3240
  let explainableLabel = "Local Config";
3167
3241
  let policyMatchedField;
3168
3242
  let policyMatchedWord;
3243
+ let policyRuleDescription;
3169
3244
  let riskMetadata;
3170
3245
  let statefulRecoveryCommand;
3171
3246
  let localSmartRuleMatched = false;
@@ -3259,6 +3334,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3259
3334
  return { approved: true, checkedBy: "audit" };
3260
3335
  }
3261
3336
  if (!taintWarning && !isIgnoredTool(toolName)) {
3337
+ const ld = config.policy.loopDetection;
3338
+ if (ld.enabled) {
3339
+ const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
3340
+ if (loopResult.looping) {
3341
+ 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?`;
3342
+ if (!isManual)
3343
+ appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
3344
+ return {
3345
+ approved: false,
3346
+ reason,
3347
+ blockedBy: "loop-detection",
3348
+ blockedByLabel: "\u{1F504} Loop Detected"
3349
+ };
3350
+ }
3351
+ }
3262
3352
  if (getActiveTrustSession(toolName)) {
3263
3353
  if (approvers.cloud && creds?.apiKey)
3264
3354
  await auditLocalAllow(toolName, args, "trust", creds, meta);
@@ -3314,6 +3404,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3314
3404
  policyMatchedField = policyResult.matchedField;
3315
3405
  policyMatchedWord = policyResult.matchedWord;
3316
3406
  if (policyResult.ruleName) localSmartRuleMatched = true;
3407
+ if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
3408
+ else if (policyResult.reason) policyRuleDescription = policyResult.reason;
3317
3409
  riskMetadata = computeRiskMetadata(
3318
3410
  args,
3319
3411
  policyResult.tier ?? 6,
@@ -3577,13 +3669,14 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
3577
3669
  hashAuditArgs
3578
3670
  );
3579
3671
  }
3580
- return finalResult;
3672
+ const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
3673
+ return enrichedResult;
3581
3674
  }
3582
- var import_crypto3, WRITE_TOOLS;
3675
+ var import_crypto4, WRITE_TOOLS;
3583
3676
  var init_orchestrator = __esm({
3584
3677
  "src/auth/orchestrator.ts"() {
3585
3678
  "use strict";
3586
- import_crypto3 = require("crypto");
3679
+ import_crypto4 = require("crypto");
3587
3680
  init_native();
3588
3681
  init_context_sniper();
3589
3682
  init_dlp();
@@ -3593,6 +3686,7 @@ var init_orchestrator = __esm({
3593
3686
  init_state();
3594
3687
  init_daemon();
3595
3688
  init_cloud();
3689
+ init_loop_detector();
3596
3690
  WRITE_TOOLS = /* @__PURE__ */ new Set([
3597
3691
  "write",
3598
3692
  "write_file",
@@ -5266,11 +5360,11 @@ function commonPathPrefix(paths) {
5266
5360
  const prefix = common.join("/").replace(/\/?$/, "/");
5267
5361
  return prefix.length > 1 ? prefix : null;
5268
5362
  }
5269
- var import_crypto4, SuggestionTracker;
5363
+ var import_crypto5, SuggestionTracker;
5270
5364
  var init_suggestion_tracker = __esm({
5271
5365
  "src/daemon/suggestion-tracker.ts"() {
5272
5366
  "use strict";
5273
- import_crypto4 = require("crypto");
5367
+ import_crypto5 = require("crypto");
5274
5368
  SuggestionTracker = class {
5275
5369
  events = /* @__PURE__ */ new Map();
5276
5370
  threshold;
@@ -5316,7 +5410,7 @@ var init_suggestion_tracker = __esm({
5316
5410
  }
5317
5411
  } : { type: "ignoredTool", toolName };
5318
5412
  return {
5319
- id: (0, import_crypto4.randomUUID)(),
5413
+ id: (0, import_crypto5.randomUUID)(),
5320
5414
  toolName,
5321
5415
  allowCount: events.length,
5322
5416
  suggestedRule,
@@ -5330,12 +5424,12 @@ var init_suggestion_tracker = __esm({
5330
5424
  });
5331
5425
 
5332
5426
  // src/daemon/taint-store.ts
5333
- var import_fs12, import_path15, DEFAULT_TTL_MS, TaintStore;
5427
+ var import_fs13, import_path16, DEFAULT_TTL_MS, TaintStore;
5334
5428
  var init_taint_store = __esm({
5335
5429
  "src/daemon/taint-store.ts"() {
5336
5430
  "use strict";
5337
- import_fs12 = __toESM(require("fs"));
5338
- import_path15 = __toESM(require("path"));
5431
+ import_fs13 = __toESM(require("fs"));
5432
+ import_path16 = __toESM(require("path"));
5339
5433
  DEFAULT_TTL_MS = 60 * 60 * 1e3;
5340
5434
  TaintStore = class {
5341
5435
  records = /* @__PURE__ */ new Map();
@@ -5400,9 +5494,9 @@ var init_taint_store = __esm({
5400
5494
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5401
5495
  _resolve(filePath) {
5402
5496
  try {
5403
- return import_fs12.default.realpathSync.native(import_path15.default.resolve(filePath));
5497
+ return import_fs13.default.realpathSync.native(import_path16.default.resolve(filePath));
5404
5498
  } catch {
5405
- return import_path15.default.resolve(filePath);
5499
+ return import_path16.default.resolve(filePath);
5406
5500
  }
5407
5501
  }
5408
5502
  };
@@ -5520,8 +5614,8 @@ var init_session_history = __esm({
5520
5614
  // src/daemon/state.ts
5521
5615
  function loadInsightCounts() {
5522
5616
  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"));
5617
+ if (!import_fs14.default.existsSync(INSIGHT_COUNTS_FILE)) return;
5618
+ const data = JSON.parse(import_fs14.default.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
5525
5619
  for (const [tool, count] of Object.entries(data)) {
5526
5620
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
5527
5621
  }
@@ -5560,23 +5654,23 @@ function markRejectionHandlerRegistered() {
5560
5654
  daemonRejectionHandlerRegistered = true;
5561
5655
  }
5562
5656
  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`;
5657
+ const dir = import_path17.default.dirname(filePath);
5658
+ if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
5659
+ const tmpPath = `${filePath}.${(0, import_crypto6.randomUUID)()}.tmp`;
5566
5660
  try {
5567
- import_fs13.default.writeFileSync(tmpPath, data, options);
5661
+ import_fs14.default.writeFileSync(tmpPath, data, options);
5568
5662
  } catch (err2) {
5569
5663
  try {
5570
- import_fs13.default.unlinkSync(tmpPath);
5664
+ import_fs14.default.unlinkSync(tmpPath);
5571
5665
  } catch {
5572
5666
  }
5573
5667
  throw err2;
5574
5668
  }
5575
5669
  try {
5576
- import_fs13.default.renameSync(tmpPath, filePath);
5670
+ import_fs14.default.renameSync(tmpPath, filePath);
5577
5671
  } catch (err2) {
5578
5672
  try {
5579
- import_fs13.default.unlinkSync(tmpPath);
5673
+ import_fs14.default.unlinkSync(tmpPath);
5580
5674
  } catch {
5581
5675
  }
5582
5676
  throw err2;
@@ -5600,16 +5694,16 @@ function appendAuditLog(data) {
5600
5694
  decision: data.decision,
5601
5695
  source: "daemon"
5602
5696
  };
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");
5697
+ const dir = import_path17.default.dirname(AUDIT_LOG_FILE);
5698
+ if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
5699
+ import_fs14.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
5606
5700
  } catch {
5607
5701
  }
5608
5702
  }
5609
5703
  function getAuditHistory(limit = 20) {
5610
5704
  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");
5705
+ if (!import_fs14.default.existsSync(AUDIT_LOG_FILE)) return [];
5706
+ const lines = import_fs14.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
5613
5707
  if (lines.length === 1 && lines[0] === "") return [];
5614
5708
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
5615
5709
  } catch {
@@ -5618,19 +5712,19 @@ function getAuditHistory(limit = 20) {
5618
5712
  }
5619
5713
  function getOrgName() {
5620
5714
  try {
5621
- if (import_fs13.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5715
+ if (import_fs14.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5622
5716
  } catch {
5623
5717
  }
5624
5718
  return null;
5625
5719
  }
5626
5720
  function hasStoredSlackKey() {
5627
- return import_fs13.default.existsSync(CREDENTIALS_FILE);
5721
+ return import_fs14.default.existsSync(CREDENTIALS_FILE);
5628
5722
  }
5629
5723
  function writeGlobalSetting(key, value) {
5630
5724
  let config = {};
5631
5725
  try {
5632
- if (import_fs13.default.existsSync(GLOBAL_CONFIG_FILE)) {
5633
- config = JSON.parse(import_fs13.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5726
+ if (import_fs14.default.existsSync(GLOBAL_CONFIG_FILE)) {
5727
+ config = JSON.parse(import_fs14.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
5634
5728
  }
5635
5729
  } catch {
5636
5730
  }
@@ -5642,8 +5736,8 @@ function writeTrustEntry(toolName, durationMs) {
5642
5736
  try {
5643
5737
  let trust = { entries: [] };
5644
5738
  try {
5645
- if (import_fs13.default.existsSync(TRUST_FILE2))
5646
- trust = JSON.parse(import_fs13.default.readFileSync(TRUST_FILE2, "utf-8"));
5739
+ if (import_fs14.default.existsSync(TRUST_FILE2))
5740
+ trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
5647
5741
  } catch {
5648
5742
  }
5649
5743
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -5654,8 +5748,8 @@ function writeTrustEntry(toolName, durationMs) {
5654
5748
  }
5655
5749
  function readPersistentDecisions() {
5656
5750
  try {
5657
- if (import_fs13.default.existsSync(DECISIONS_FILE)) {
5658
- return JSON.parse(import_fs13.default.readFileSync(DECISIONS_FILE, "utf-8"));
5751
+ if (import_fs14.default.existsSync(DECISIONS_FILE)) {
5752
+ return JSON.parse(import_fs14.default.readFileSync(DECISIONS_FILE, "utf-8"));
5659
5753
  }
5660
5754
  } catch {
5661
5755
  }
@@ -5692,7 +5786,7 @@ function estimateToolCost(tool, args) {
5692
5786
  const filePath = a.file_path ?? a.path;
5693
5787
  if (filePath) {
5694
5788
  try {
5695
- const bytes = import_fs13.default.statSync(filePath).size;
5789
+ const bytes = import_fs14.default.statSync(filePath).size;
5696
5790
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
5697
5791
  } catch {
5698
5792
  }
@@ -5750,7 +5844,7 @@ function abandonPending() {
5750
5844
  });
5751
5845
  if (autoStarted) {
5752
5846
  try {
5753
- import_fs13.default.unlinkSync(DAEMON_PID_FILE);
5847
+ import_fs14.default.unlinkSync(DAEMON_PID_FILE);
5754
5848
  } catch {
5755
5849
  }
5756
5850
  setTimeout(() => {
@@ -5761,7 +5855,7 @@ function abandonPending() {
5761
5855
  }
5762
5856
  function startActivitySocket() {
5763
5857
  try {
5764
- import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5858
+ import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5765
5859
  } catch {
5766
5860
  }
5767
5861
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -5842,34 +5936,34 @@ function startActivitySocket() {
5842
5936
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
5843
5937
  process.on("exit", () => {
5844
5938
  try {
5845
- import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5939
+ import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5846
5940
  } catch {
5847
5941
  }
5848
5942
  });
5849
5943
  }
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;
5944
+ 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
5945
  var init_state2 = __esm({
5852
5946
  "src/daemon/state.ts"() {
5853
5947
  "use strict";
5854
5948
  import_net2 = __toESM(require("net"));
5855
- import_fs13 = __toESM(require("fs"));
5856
- import_path16 = __toESM(require("path"));
5857
- import_os11 = __toESM(require("os"));
5949
+ import_fs14 = __toESM(require("fs"));
5950
+ import_path17 = __toESM(require("path"));
5951
+ import_os12 = __toESM(require("os"));
5858
5952
  import_child_process3 = require("child_process");
5859
- import_crypto5 = require("crypto");
5953
+ import_crypto6 = require("crypto");
5860
5954
  init_daemon();
5861
5955
  init_suggestion_tracker();
5862
5956
  init_taint_store();
5863
5957
  init_session_counters();
5864
5958
  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");
5959
+ homeDir = import_os12.default.homedir();
5960
+ DAEMON_PID_FILE = import_path17.default.join(homeDir, ".node9", "daemon.pid");
5961
+ DECISIONS_FILE = import_path17.default.join(homeDir, ".node9", "decisions.json");
5962
+ AUDIT_LOG_FILE = import_path17.default.join(homeDir, ".node9", "audit.log");
5963
+ TRUST_FILE2 = import_path17.default.join(homeDir, ".node9", "trust.json");
5964
+ GLOBAL_CONFIG_FILE = import_path17.default.join(homeDir, ".node9", "config.json");
5965
+ CREDENTIALS_FILE = import_path17.default.join(homeDir, ".node9", "credentials.json");
5966
+ INSIGHT_COUNTS_FILE = import_path17.default.join(homeDir, ".node9", "insight-counts.json");
5873
5967
  pending = /* @__PURE__ */ new Map();
5874
5968
  sseClients = /* @__PURE__ */ new Set();
5875
5969
  suggestionTracker = new SuggestionTracker(3);
@@ -5887,7 +5981,7 @@ var init_state2 = __esm({
5887
5981
  "2h": 2 * 60 * 6e4
5888
5982
  };
5889
5983
  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");
5984
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path17.default.join(import_os12.default.tmpdir(), "node9-activity.sock");
5891
5985
  ACTIVITY_RING_SIZE = 100;
5892
5986
  activityRing = [];
5893
5987
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5912,8 +6006,8 @@ var init_state2 = __esm({
5912
6006
  function patchConfig(configPath, patch) {
5913
6007
  let config = {};
5914
6008
  try {
5915
- if (import_fs14.default.existsSync(configPath)) {
5916
- config = JSON.parse(import_fs14.default.readFileSync(configPath, "utf8"));
6009
+ if (import_fs15.default.existsSync(configPath)) {
6010
+ config = JSON.parse(import_fs15.default.readFileSync(configPath, "utf8"));
5917
6011
  }
5918
6012
  } catch {
5919
6013
  throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
@@ -5932,44 +6026,44 @@ function patchConfig(configPath, patch) {
5932
6026
  ignored.push(patch.toolName);
5933
6027
  }
5934
6028
  }
5935
- const dir = import_path17.default.dirname(configPath);
5936
- import_fs14.default.mkdirSync(dir, { recursive: true });
6029
+ const dir = import_path18.default.dirname(configPath);
6030
+ import_fs15.default.mkdirSync(dir, { recursive: true });
5937
6031
  const tmp = configPath + ".node9-tmp";
5938
6032
  try {
5939
- import_fs14.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
6033
+ import_fs15.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
5940
6034
  } catch (err2) {
5941
6035
  try {
5942
- import_fs14.default.unlinkSync(tmp);
6036
+ import_fs15.default.unlinkSync(tmp);
5943
6037
  } catch {
5944
6038
  }
5945
6039
  throw err2;
5946
6040
  }
5947
6041
  try {
5948
- import_fs14.default.renameSync(tmp, configPath);
6042
+ import_fs15.default.renameSync(tmp, configPath);
5949
6043
  } catch (err2) {
5950
6044
  try {
5951
- import_fs14.default.unlinkSync(tmp);
6045
+ import_fs15.default.unlinkSync(tmp);
5952
6046
  } catch {
5953
6047
  }
5954
6048
  throw err2;
5955
6049
  }
5956
6050
  }
5957
- var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
6051
+ var import_fs15, import_path18, import_os13, GLOBAL_CONFIG_PATH;
5958
6052
  var init_patch = __esm({
5959
6053
  "src/config/patch.ts"() {
5960
6054
  "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");
6055
+ import_fs15 = __toESM(require("fs"));
6056
+ import_path18 = __toESM(require("path"));
6057
+ import_os13 = __toESM(require("os"));
6058
+ GLOBAL_CONFIG_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "config.json");
5965
6059
  }
5966
6060
  });
5967
6061
 
5968
6062
  // src/daemon/server.ts
5969
6063
  function startDaemon() {
5970
6064
  loadInsightCounts();
5971
- const csrfToken = (0, import_crypto6.randomUUID)();
5972
- const internalToken = (0, import_crypto6.randomUUID)();
6065
+ const csrfToken = (0, import_crypto7.randomUUID)();
6066
+ const internalToken = (0, import_crypto7.randomUUID)();
5973
6067
  const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
5974
6068
  const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
5975
6069
  const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
@@ -5982,7 +6076,7 @@ function startDaemon() {
5982
6076
  idleTimer = setTimeout(() => {
5983
6077
  if (autoStarted) {
5984
6078
  try {
5985
- import_fs15.default.unlinkSync(DAEMON_PID_FILE);
6079
+ import_fs16.default.unlinkSync(DAEMON_PID_FILE);
5986
6080
  } catch {
5987
6081
  }
5988
6082
  }
@@ -6103,7 +6197,7 @@ data: ${JSON.stringify(item.data)}
6103
6197
  cwd,
6104
6198
  localSmartRuleMatched = false
6105
6199
  } = JSON.parse(body);
6106
- const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto6.randomUUID)();
6200
+ const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto7.randomUUID)();
6107
6201
  const entry = {
6108
6202
  id,
6109
6203
  toolName,
@@ -6145,7 +6239,7 @@ data: ${JSON.stringify(item.data)}
6145
6239
  status: "pending"
6146
6240
  });
6147
6241
  }
6148
- const projectCwd = typeof cwd === "string" && import_path18.default.isAbsolute(cwd) ? cwd : void 0;
6242
+ const projectCwd = typeof cwd === "string" && import_path19.default.isAbsolute(cwd) ? cwd : void 0;
6149
6243
  const projectConfig = getConfig(projectCwd);
6150
6244
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6151
6245
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6535,8 +6629,8 @@ data: ${JSON.stringify(item.data)}
6535
6629
  const body = await readBody(req);
6536
6630
  const data = body ? JSON.parse(body) : {};
6537
6631
  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)) {
6632
+ const node9Dir = import_path19.default.dirname(GLOBAL_CONFIG_PATH);
6633
+ if (!import_path19.default.resolve(configPath).startsWith(node9Dir + import_path19.default.sep)) {
6540
6634
  res.writeHead(400, { "Content-Type": "application/json" });
6541
6635
  return res.end(
6542
6636
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6647,14 +6741,14 @@ data: ${JSON.stringify(item.data)}
6647
6741
  server.on("error", (e) => {
6648
6742
  if (e.code === "EADDRINUSE") {
6649
6743
  try {
6650
- if (import_fs15.default.existsSync(DAEMON_PID_FILE)) {
6651
- const { pid } = JSON.parse(import_fs15.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6744
+ if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
6745
+ const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6652
6746
  process.kill(pid, 0);
6653
6747
  return process.exit(0);
6654
6748
  }
6655
6749
  } catch {
6656
6750
  try {
6657
- import_fs15.default.unlinkSync(DAEMON_PID_FILE);
6751
+ import_fs16.default.unlinkSync(DAEMON_PID_FILE);
6658
6752
  } catch {
6659
6753
  }
6660
6754
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6713,14 +6807,14 @@ data: ${JSON.stringify(item.data)}
6713
6807
  }
6714
6808
  startActivitySocket();
6715
6809
  }
6716
- var import_http, import_fs15, import_path18, import_crypto6, import_child_process4, import_chalk2;
6810
+ var import_http, import_fs16, import_path19, import_crypto7, import_child_process4, import_chalk2;
6717
6811
  var init_server = __esm({
6718
6812
  "src/daemon/server.ts"() {
6719
6813
  "use strict";
6720
6814
  import_http = __toESM(require("http"));
6721
- import_fs15 = __toESM(require("fs"));
6722
- import_path18 = __toESM(require("path"));
6723
- import_crypto6 = require("crypto");
6815
+ import_fs16 = __toESM(require("fs"));
6816
+ import_path19 = __toESM(require("path"));
6817
+ import_crypto7 = require("crypto");
6724
6818
  import_child_process4 = require("child_process");
6725
6819
  import_chalk2 = __toESM(require("chalk"));
6726
6820
  init_core();
@@ -6734,24 +6828,24 @@ var init_server = __esm({
6734
6828
 
6735
6829
  // src/daemon/index.ts
6736
6830
  function stopDaemon() {
6737
- if (!import_fs16.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
6831
+ if (!import_fs17.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
6738
6832
  try {
6739
- const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6833
+ const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6740
6834
  process.kill(pid, "SIGTERM");
6741
6835
  console.log(import_chalk3.default.green("\u2705 Stopped."));
6742
6836
  } catch {
6743
6837
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
6744
6838
  } finally {
6745
6839
  try {
6746
- import_fs16.default.unlinkSync(DAEMON_PID_FILE);
6840
+ import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6747
6841
  } catch {
6748
6842
  }
6749
6843
  }
6750
6844
  }
6751
6845
  function daemonStatus() {
6752
- if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
6846
+ if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
6753
6847
  try {
6754
- const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6848
+ const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6755
6849
  process.kill(pid, 0);
6756
6850
  console.log(import_chalk3.default.green("Node9 daemon: running"));
6757
6851
  return;
@@ -6770,11 +6864,11 @@ function daemonStatus() {
6770
6864
  console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
6771
6865
  }
6772
6866
  }
6773
- var import_fs16, import_chalk3, import_child_process5;
6867
+ var import_fs17, import_chalk3, import_child_process5;
6774
6868
  var init_daemon2 = __esm({
6775
6869
  "src/daemon/index.ts"() {
6776
6870
  "use strict";
6777
- import_fs16 = __toESM(require("fs"));
6871
+ import_fs17 = __toESM(require("fs"));
6778
6872
  import_chalk3 = __toESM(require("chalk"));
6779
6873
  import_child_process5 = require("child_process");
6780
6874
  init_server();
@@ -6795,44 +6889,64 @@ function getIcon(tool) {
6795
6889
  }
6796
6890
  return "\u{1F6E0}\uFE0F";
6797
6891
  }
6892
+ function visibleLength(s) {
6893
+ return s.replace(/\x1B\[[0-9;]*m/g, "").length;
6894
+ }
6895
+ function wrappedLineCount(text) {
6896
+ const cols = process.stdout.columns;
6897
+ if (!cols) return 1;
6898
+ const len = visibleLength(text);
6899
+ return Math.max(1, Math.ceil(len / cols));
6900
+ }
6798
6901
  function formatBase(activity) {
6799
6902
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6800
6903
  const icon = getIcon(activity.tool);
6801
6904
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6802
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os22.default.homedir(), "~");
6905
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os24.default.homedir(), "~");
6803
6906
  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)}`;
6907
+ return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
6805
6908
  }
6806
6909
  function renderResult(activity, result) {
6807
6910
  const base = formatBase(activity);
6808
6911
  let status;
6809
6912
  if (result.status === "allow") {
6810
- status = import_chalk18.default.green("\u2713 ALLOW");
6913
+ status = import_chalk19.default.green("\u2713 ALLOW");
6811
6914
  } else if (result.status === "dlp") {
6812
- status = import_chalk18.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6915
+ status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6813
6916
  } else {
6814
- status = import_chalk18.default.red("\u2717 BLOCK");
6917
+ status = import_chalk19.default.red("\u2717 BLOCK");
6815
6918
  }
6816
6919
  const cost = result.costEstimate ?? activity.costEstimate;
6817
- const costSuffix = cost == null ? "" : import_chalk18.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
6920
+ const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
6818
6921
  if (process.stdout.isTTY) {
6819
- import_readline5.default.clearLine(process.stdout, 0);
6820
- import_readline5.default.cursorTo(process.stdout, 0);
6922
+ if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
6923
+ import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
6924
+ import_readline5.default.cursorTo(process.stdout, 0);
6925
+ process.stdout.write(ERASE_DOWN);
6926
+ } else {
6927
+ import_readline5.default.clearLine(process.stdout, 0);
6928
+ import_readline5.default.cursorTo(process.stdout, 0);
6929
+ }
6930
+ pendingShownForId = null;
6931
+ pendingWrappedLines = 0;
6821
6932
  }
6822
6933
  console.log(`${base} ${status}${costSuffix}`);
6823
6934
  }
6824
6935
  function renderPending(activity) {
6825
6936
  if (!process.stdout.isTTY) return;
6826
- process.stdout.write(`${formatBase(activity)} ${import_chalk18.default.yellow("\u25CF \u2026")}\r`);
6937
+ const line = `${formatBase(activity)} ${import_chalk19.default.yellow("\u25CF \u2026")}`;
6938
+ pendingShownForId = activity.id;
6939
+ pendingWrappedLines = wrappedLineCount(line);
6940
+ process.stdout.write(`${line}\r`);
6827
6941
  }
6828
6942
  async function ensureDaemon() {
6829
6943
  let pidPort = null;
6830
- if (import_fs26.default.existsSync(PID_FILE)) {
6944
+ if (import_fs28.default.existsSync(PID_FILE)) {
6831
6945
  try {
6832
- const { port } = JSON.parse(import_fs26.default.readFileSync(PID_FILE, "utf-8"));
6946
+ const { port } = JSON.parse(import_fs28.default.readFileSync(PID_FILE, "utf-8"));
6833
6947
  pidPort = port;
6834
6948
  } catch {
6835
- console.error(import_chalk18.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6949
+ console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
6836
6950
  }
6837
6951
  }
6838
6952
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6843,7 +6957,7 @@ async function ensureDaemon() {
6843
6957
  if (res.ok) return checkPort;
6844
6958
  } catch {
6845
6959
  }
6846
- console.log(import_chalk18.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6960
+ console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
6847
6961
  const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
6848
6962
  detached: true,
6849
6963
  stdio: "ignore",
@@ -6860,7 +6974,7 @@ async function ensureDaemon() {
6860
6974
  } catch {
6861
6975
  }
6862
6976
  }
6863
- console.error(import_chalk18.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6977
+ console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
6864
6978
  process.exit(1);
6865
6979
  }
6866
6980
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6949,9 +7063,9 @@ function buildRecoveryCardLines(req) {
6949
7063
  ];
6950
7064
  }
6951
7065
  function readApproversFromDisk() {
6952
- const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
7066
+ const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
6953
7067
  try {
6954
- const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
7068
+ const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
6955
7069
  const settings = raw.settings ?? {};
6956
7070
  return settings.approvers ?? {};
6957
7071
  } catch {
@@ -6962,20 +7076,20 @@ function approverStatusLine() {
6962
7076
  const a = readApproversFromDisk();
6963
7077
  const fmt = (label, key) => {
6964
7078
  const on = a[key] !== false;
6965
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk18.default.green("\u2713") : import_chalk18.default.dim("\u2717")}`;
7079
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk19.default.green("\u2713") : import_chalk19.default.dim("\u2717")}`;
6966
7080
  };
6967
7081
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
6968
7082
  }
6969
7083
  function toggleApprover(channel) {
6970
- const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
7084
+ const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
6971
7085
  try {
6972
- const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
7086
+ const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
6973
7087
  const settings = raw.settings ?? {};
6974
7088
  const approvers = settings.approvers ?? {};
6975
7089
  approvers[channel] = approvers[channel] === false;
6976
7090
  settings.approvers = approvers;
6977
7091
  raw.settings = settings;
6978
- import_fs26.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7092
+ import_fs28.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
6979
7093
  } catch (err2) {
6980
7094
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
6981
7095
  `);
@@ -7007,7 +7121,7 @@ async function startTail(options = {}) {
7007
7121
  req2.end();
7008
7122
  });
7009
7123
  if (result.ok) {
7010
- console.log(import_chalk18.default.green("\u2713 Flight Recorder buffer cleared."));
7124
+ console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
7011
7125
  } else if (result.code === "ECONNREFUSED") {
7012
7126
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7013
7127
  } else if (result.code === "ETIMEDOUT") {
@@ -7051,7 +7165,7 @@ async function startTail(options = {}) {
7051
7165
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7052
7166
  if (channel) {
7053
7167
  toggleApprover(channel);
7054
- console.log(import_chalk18.default.dim(` Approvers: ${approverStatusLine()}`));
7168
+ console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
7055
7169
  }
7056
7170
  };
7057
7171
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7117,7 +7231,7 @@ async function startTail(options = {}) {
7117
7231
  localAllowCounts.get(req2.toolName) ?? 0
7118
7232
  )
7119
7233
  );
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");
7234
+ 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
7235
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7122
7236
  for (const line of stampedLines) process.stdout.write(line + "\n");
7123
7237
  process.stdout.write(SHOW_CURSOR);
@@ -7145,8 +7259,8 @@ async function startTail(options = {}) {
7145
7259
  }
7146
7260
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7147
7261
  try {
7148
- import_fs26.default.appendFileSync(
7149
- import_path29.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log"),
7262
+ import_fs28.default.appendFileSync(
7263
+ import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log"),
7150
7264
  `[tail] POST /decision failed: ${String(err2)}
7151
7265
  `
7152
7266
  );
@@ -7168,7 +7282,7 @@ async function startTail(options = {}) {
7168
7282
  );
7169
7283
  const stampedLines = buildCardLines(req2, priorCount);
7170
7284
  if (externalDecision) {
7171
- const source = externalDecision === "allow" ? import_chalk18.default.green("\u2713 ALLOWED") : import_chalk18.default.red("\u2717 DENIED");
7285
+ const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
7172
7286
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7173
7287
  }
7174
7288
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7227,16 +7341,16 @@ async function startTail(options = {}) {
7227
7341
  }
7228
7342
  } catch {
7229
7343
  }
7230
- console.log(import_chalk18.default.cyan.bold(`
7231
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk18.default.dim(`\u2192 ${dashboardUrl}`));
7344
+ console.log(import_chalk19.default.cyan.bold(`
7345
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk19.default.dim(`\u2192 ${dashboardUrl}`));
7232
7346
  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`));
7347
+ console.log(import_chalk19.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7348
+ console.log(import_chalk19.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7235
7349
  }
7236
7350
  if (options.history) {
7237
- console.log(import_chalk18.default.dim("Showing history + live events.\n"));
7351
+ console.log(import_chalk19.default.dim("Showing history + live events.\n"));
7238
7352
  } else {
7239
- console.log(import_chalk18.default.dim("Showing live events only. Use --history to include past.\n"));
7353
+ console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
7240
7354
  }
7241
7355
  process.on("SIGINT", () => {
7242
7356
  exitIdleMode();
@@ -7246,13 +7360,13 @@ async function startTail(options = {}) {
7246
7360
  import_readline5.default.clearLine(process.stdout, 0);
7247
7361
  import_readline5.default.cursorTo(process.stdout, 0);
7248
7362
  }
7249
- console.log(import_chalk18.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7363
+ console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7250
7364
  process.exit(0);
7251
7365
  });
7252
7366
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7253
7367
  const req = import_http2.default.get(sseUrl, (res) => {
7254
7368
  if (res.statusCode !== 200) {
7255
- console.error(import_chalk18.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7369
+ console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7256
7370
  process.exit(1);
7257
7371
  }
7258
7372
  if (canApprove) enterIdleMode();
@@ -7283,7 +7397,7 @@ async function startTail(options = {}) {
7283
7397
  import_readline5.default.clearLine(process.stdout, 0);
7284
7398
  import_readline5.default.cursorTo(process.stdout, 0);
7285
7399
  }
7286
- console.log(import_chalk18.default.red("\n\u274C Daemon disconnected."));
7400
+ console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
7287
7401
  process.exit(1);
7288
7402
  });
7289
7403
  });
@@ -7375,9 +7489,9 @@ async function startTail(options = {}) {
7375
7489
  const hash = data.hash ?? "";
7376
7490
  const summary = data.argsSummary ?? data.tool;
7377
7491
  const fileCount = data.fileCount ?? 0;
7378
- const files = fileCount > 0 ? import_chalk18.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7492
+ const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7379
7493
  process.stdout.write(
7380
- `${import_chalk18.default.dim(time)} ${import_chalk18.default.cyan("\u{1F4F8} snapshot")} ${import_chalk18.default.dim(hash)} ${summary}${files}
7494
+ `${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
7381
7495
  `
7382
7496
  );
7383
7497
  return;
@@ -7394,26 +7508,26 @@ async function startTail(options = {}) {
7394
7508
  }
7395
7509
  req.on("error", (err2) => {
7396
7510
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7397
- console.error(import_chalk18.default.red(`
7511
+ console.error(import_chalk19.default.red(`
7398
7512
  \u274C ${msg}`));
7399
7513
  process.exit(1);
7400
7514
  });
7401
7515
  }
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;
7516
+ var import_http2, import_chalk19, import_fs28, import_os24, import_path31, 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
7517
  var init_tail = __esm({
7404
7518
  "src/tui/tail.ts"() {
7405
7519
  "use strict";
7406
7520
  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"));
7521
+ import_chalk19 = __toESM(require("chalk"));
7522
+ import_fs28 = __toESM(require("fs"));
7523
+ import_os24 = __toESM(require("os"));
7524
+ import_path31 = __toESM(require("path"));
7411
7525
  import_readline5 = __toESM(require("readline"));
7412
7526
  import_child_process14 = require("child_process");
7413
7527
  init_daemon2();
7414
7528
  init_daemon();
7415
7529
  init_core();
7416
- PID_FILE = import_path29.default.join(import_os22.default.homedir(), ".node9", "daemon.pid");
7530
+ PID_FILE = import_path31.default.join(import_os24.default.homedir(), ".node9", "daemon.pid");
7417
7531
  ICONS = {
7418
7532
  bash: "\u{1F4BB}",
7419
7533
  shell: "\u{1F4BB}",
@@ -7441,6 +7555,8 @@ var init_tail = __esm({
7441
7555
  HIDE_CURSOR = "\x1B[?25l";
7442
7556
  SHOW_CURSOR = "\x1B[?25h";
7443
7557
  ERASE_DOWN = "\x1B[J";
7558
+ pendingShownForId = null;
7559
+ pendingWrappedLines = 0;
7444
7560
  DIVIDER = "\u2500".repeat(60);
7445
7561
  }
7446
7562
  });
@@ -7509,10 +7625,10 @@ function bold(s) {
7509
7625
  function color(c, s) {
7510
7626
  return `${c}${s}${RESET3}`;
7511
7627
  }
7512
- function progressBar(pct, warnAt = 70, critAt = 85) {
7513
- const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
7628
+ function progressBar(pct2, warnAt = 70, critAt = 85) {
7629
+ const filled = Math.round(Math.min(pct2, 100) / 100 * BAR_WIDTH);
7514
7630
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7515
- const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7631
+ const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
7516
7632
  return `${c}${bar}${RESET3}`;
7517
7633
  }
7518
7634
  function formatTimeLeft(resetsAt) {
@@ -7526,9 +7642,9 @@ function formatTimeLeft(resetsAt) {
7526
7642
  return ` (${m}m left)`;
7527
7643
  }
7528
7644
  function safeReadJson(filePath) {
7529
- if (!import_fs27.default.existsSync(filePath)) return null;
7645
+ if (!import_fs29.default.existsSync(filePath)) return null;
7530
7646
  try {
7531
- return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
7647
+ return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
7532
7648
  } catch {
7533
7649
  return null;
7534
7650
  }
@@ -7549,12 +7665,12 @@ function countHooksInFile(filePath) {
7549
7665
  return Object.keys(cfg.hooks).length;
7550
7666
  }
7551
7667
  function countRulesInDir(rulesDir) {
7552
- if (!import_fs27.default.existsSync(rulesDir)) return 0;
7668
+ if (!import_fs29.default.existsSync(rulesDir)) return 0;
7553
7669
  let count = 0;
7554
7670
  try {
7555
- for (const entry of import_fs27.default.readdirSync(rulesDir, { withFileTypes: true })) {
7671
+ for (const entry of import_fs29.default.readdirSync(rulesDir, { withFileTypes: true })) {
7556
7672
  if (entry.isDirectory()) {
7557
- count += countRulesInDir(import_path30.default.join(rulesDir, entry.name));
7673
+ count += countRulesInDir(import_path32.default.join(rulesDir, entry.name));
7558
7674
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7559
7675
  count++;
7560
7676
  }
@@ -7565,46 +7681,46 @@ function countRulesInDir(rulesDir) {
7565
7681
  }
7566
7682
  function isSamePath(a, b) {
7567
7683
  try {
7568
- return import_path30.default.resolve(a) === import_path30.default.resolve(b);
7684
+ return import_path32.default.resolve(a) === import_path32.default.resolve(b);
7569
7685
  } catch {
7570
7686
  return false;
7571
7687
  }
7572
7688
  }
7573
7689
  function countConfigs(cwd) {
7574
- const homeDir2 = import_os23.default.homedir();
7575
- const claudeDir = import_path30.default.join(homeDir2, ".claude");
7690
+ const homeDir2 = import_os25.default.homedir();
7691
+ const claudeDir = import_path32.default.join(homeDir2, ".claude");
7576
7692
  let claudeMdCount = 0;
7577
7693
  let rulesCount = 0;
7578
7694
  let hooksCount = 0;
7579
7695
  const userMcpServers = /* @__PURE__ */ new Set();
7580
7696
  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");
7697
+ if (import_fs29.default.existsSync(import_path32.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7698
+ rulesCount += countRulesInDir(import_path32.default.join(claudeDir, "rules"));
7699
+ const userSettings = import_path32.default.join(claudeDir, "settings.json");
7584
7700
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7585
7701
  hooksCount += countHooksInFile(userSettings);
7586
- const userClaudeJson = import_path30.default.join(homeDir2, ".claude.json");
7702
+ const userClaudeJson = import_path32.default.join(homeDir2, ".claude.json");
7587
7703
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7588
7704
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7589
7705
  userMcpServers.delete(name);
7590
7706
  }
7591
7707
  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");
7708
+ if (import_fs29.default.existsSync(import_path32.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7709
+ if (import_fs29.default.existsSync(import_path32.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7710
+ const projectClaudeDir = import_path32.default.join(cwd, ".claude");
7595
7711
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7596
7712
  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");
7713
+ if (import_fs29.default.existsSync(import_path32.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7714
+ rulesCount += countRulesInDir(import_path32.default.join(projectClaudeDir, "rules"));
7715
+ const projSettings = import_path32.default.join(projectClaudeDir, "settings.json");
7600
7716
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7601
7717
  hooksCount += countHooksInFile(projSettings);
7602
7718
  }
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");
7719
+ if (import_fs29.default.existsSync(import_path32.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7720
+ const localSettings = import_path32.default.join(projectClaudeDir, "settings.local.json");
7605
7721
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7606
7722
  hooksCount += countHooksInFile(localSettings);
7607
- const mcpJsonServers = getMcpServerNames(import_path30.default.join(cwd, ".mcp.json"));
7723
+ const mcpJsonServers = getMcpServerNames(import_path32.default.join(cwd, ".mcp.json"));
7608
7724
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7609
7725
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7610
7726
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7637,12 +7753,12 @@ function readActiveShieldsHud() {
7637
7753
  return shieldsCache.value;
7638
7754
  }
7639
7755
  try {
7640
- const shieldsPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "shields.json");
7641
- if (!import_fs27.default.existsSync(shieldsPath)) {
7756
+ const shieldsPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "shields.json");
7757
+ if (!import_fs29.default.existsSync(shieldsPath)) {
7642
7758
  shieldsCache = { value: [], ts: now };
7643
7759
  return [];
7644
7760
  }
7645
- const parsed = JSON.parse(import_fs27.default.readFileSync(shieldsPath, "utf-8"));
7761
+ const parsed = JSON.parse(import_fs29.default.readFileSync(shieldsPath, "utf-8"));
7646
7762
  if (!Array.isArray(parsed.active)) {
7647
7763
  shieldsCache = { value: [], ts: now };
7648
7764
  return [];
@@ -7728,15 +7844,15 @@ function renderContextLine(stdin) {
7728
7844
  }
7729
7845
  const rl = stdin.rate_limits;
7730
7846
  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);
7847
+ const pct2 = Math.round(rl.five_hour.used_percentage);
7848
+ const bar = progressBar(pct2, 60, 80);
7733
7849
  const left = formatTimeLeft(rl.five_hour.resets_at);
7734
- parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
7850
+ parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
7735
7851
  }
7736
7852
  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}%`);
7853
+ const pct2 = Math.round(rl.seven_day.used_percentage);
7854
+ const bar = progressBar(pct2, 60, 80);
7855
+ parts.push(`${dim("\u2502")} 7d ${bar} ${pct2}%`);
7740
7856
  }
7741
7857
  if (parts.length === 0) return null;
7742
7858
  return parts.join(" ");
@@ -7744,17 +7860,17 @@ function renderContextLine(stdin) {
7744
7860
  async function main() {
7745
7861
  try {
7746
7862
  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"))) {
7863
+ if (import_fs29.default.existsSync(import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug"))) {
7748
7864
  try {
7749
- const logPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "hud-debug.log");
7865
+ const logPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "hud-debug.log");
7750
7866
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7751
7867
  let size = 0;
7752
7868
  try {
7753
- size = import_fs27.default.statSync(logPath).size;
7869
+ size = import_fs29.default.statSync(logPath).size;
7754
7870
  } catch {
7755
7871
  }
7756
7872
  if (size < MAX_LOG_SIZE) {
7757
- import_fs27.default.appendFileSync(
7873
+ import_fs29.default.appendFileSync(
7758
7874
  logPath,
7759
7875
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7760
7876
  );
@@ -7775,11 +7891,11 @@ async function main() {
7775
7891
  try {
7776
7892
  const cwd = stdin.cwd ?? process.cwd();
7777
7893
  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")
7894
+ import_path32.default.join(cwd, "node9.config.json"),
7895
+ import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json")
7780
7896
  ]) {
7781
- if (!import_fs27.default.existsSync(configPath)) continue;
7782
- const cfg = JSON.parse(import_fs27.default.readFileSync(configPath, "utf-8"));
7897
+ if (!import_fs29.default.existsSync(configPath)) continue;
7898
+ const cfg = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7783
7899
  const hud = cfg.settings?.hud;
7784
7900
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7785
7901
  }
@@ -7797,13 +7913,13 @@ async function main() {
7797
7913
  renderOffline();
7798
7914
  }
7799
7915
  }
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;
7916
+ var import_fs29, import_path32, import_os25, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
7801
7917
  var init_hud = __esm({
7802
7918
  "src/cli/hud.ts"() {
7803
7919
  "use strict";
7804
- import_fs27 = __toESM(require("fs"));
7805
- import_path30 = __toESM(require("path"));
7806
- import_os23 = __toESM(require("os"));
7920
+ import_fs29 = __toESM(require("fs"));
7921
+ import_path32 = __toESM(require("path"));
7922
+ import_os25 = __toESM(require("os"));
7807
7923
  import_http3 = __toESM(require("http"));
7808
7924
  init_daemon();
7809
7925
  RESET3 = "\x1B[0m";
@@ -7829,9 +7945,9 @@ var import_commander = require("commander");
7829
7945
  init_core();
7830
7946
 
7831
7947
  // src/setup.ts
7832
- var import_fs11 = __toESM(require("fs"));
7833
- var import_path14 = __toESM(require("path"));
7834
- var import_os10 = __toESM(require("os"));
7948
+ var import_fs12 = __toESM(require("fs"));
7949
+ var import_path15 = __toESM(require("path"));
7950
+ var import_os11 = __toESM(require("os"));
7835
7951
  var import_chalk = __toESM(require("chalk"));
7836
7952
  var import_prompts = require("@inquirer/prompts");
7837
7953
  var import_smol_toml = require("smol-toml");
@@ -7859,26 +7975,26 @@ function fullPathCommand(subcommand) {
7859
7975
  }
7860
7976
  function readJson(filePath) {
7861
7977
  try {
7862
- if (import_fs11.default.existsSync(filePath)) {
7863
- return JSON.parse(import_fs11.default.readFileSync(filePath, "utf-8"));
7978
+ if (import_fs12.default.existsSync(filePath)) {
7979
+ return JSON.parse(import_fs12.default.readFileSync(filePath, "utf-8"));
7864
7980
  }
7865
7981
  } catch {
7866
7982
  }
7867
7983
  return null;
7868
7984
  }
7869
7985
  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");
7986
+ const dir = import_path15.default.dirname(filePath);
7987
+ if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
7988
+ import_fs12.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7873
7989
  }
7874
7990
  function isNode9Hook(cmd) {
7875
7991
  if (!cmd) return false;
7876
7992
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
7877
7993
  }
7878
7994
  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");
7995
+ const homeDir2 = import_os11.default.homedir();
7996
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
7997
+ const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
7882
7998
  let changed = false;
7883
7999
  const settings = readJson(hooksPath);
7884
8000
  if (settings?.hooks) {
@@ -7926,8 +8042,8 @@ function teardownClaude() {
7926
8042
  }
7927
8043
  }
7928
8044
  function teardownGemini() {
7929
- const homeDir2 = import_os10.default.homedir();
7930
- const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
8045
+ const homeDir2 = import_os11.default.homedir();
8046
+ const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
7931
8047
  const settings = readJson(settingsPath);
7932
8048
  if (!settings) {
7933
8049
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7970,8 +8086,8 @@ function teardownGemini() {
7970
8086
  }
7971
8087
  }
7972
8088
  function teardownCursor() {
7973
- const homeDir2 = import_os10.default.homedir();
7974
- const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
8089
+ const homeDir2 = import_os11.default.homedir();
8090
+ const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
7975
8091
  const mcpConfig = readJson(mcpPath);
7976
8092
  if (!mcpConfig?.mcpServers) {
7977
8093
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -8002,9 +8118,9 @@ function teardownCursor() {
8002
8118
  }
8003
8119
  }
8004
8120
  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");
8121
+ const homeDir2 = import_os11.default.homedir();
8122
+ const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
8123
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8008
8124
  const claudeConfig = readJson(mcpPath) ?? {};
8009
8125
  const settings = readJson(hooksPath) ?? {};
8010
8126
  const servers = claudeConfig.mcpServers ?? {};
@@ -8101,8 +8217,8 @@ async function setupClaude() {
8101
8217
  }
8102
8218
  }
8103
8219
  async function setupGemini() {
8104
- const homeDir2 = import_os10.default.homedir();
8105
- const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
8220
+ const homeDir2 = import_os11.default.homedir();
8221
+ const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
8106
8222
  const settings = readJson(settingsPath) ?? {};
8107
8223
  const servers = settings.mcpServers ?? {};
8108
8224
  let hooksChanged = false;
@@ -8197,10 +8313,10 @@ async function setupGemini() {
8197
8313
  printDaemonTip();
8198
8314
  }
8199
8315
  }
8200
- function detectAgents(homeDir2 = import_os10.default.homedir()) {
8316
+ function detectAgents(homeDir2 = import_os11.default.homedir()) {
8201
8317
  const exists = (p) => {
8202
8318
  try {
8203
- return import_fs11.default.existsSync(p);
8319
+ return import_fs12.default.existsSync(p);
8204
8320
  } catch (err2) {
8205
8321
  const code = err2.code;
8206
8322
  if (code !== "ENOENT") {
@@ -8211,15 +8327,15 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
8211
8327
  }
8212
8328
  };
8213
8329
  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"))
8330
+ claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
8331
+ gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
8332
+ cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
8333
+ codex: exists(import_path15.default.join(homeDir2, ".codex"))
8218
8334
  };
8219
8335
  }
8220
8336
  async function setupCursor() {
8221
- const homeDir2 = import_os10.default.homedir();
8222
- const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
8337
+ const homeDir2 = import_os11.default.homedir();
8338
+ const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
8223
8339
  const mcpConfig = readJson(mcpPath) ?? {};
8224
8340
  const servers = mcpConfig.mcpServers ?? {};
8225
8341
  let anythingChanged = false;
@@ -8285,21 +8401,21 @@ async function setupCursor() {
8285
8401
  }
8286
8402
  function readToml(filePath) {
8287
8403
  try {
8288
- if (import_fs11.default.existsSync(filePath)) {
8289
- return (0, import_smol_toml.parse)(import_fs11.default.readFileSync(filePath, "utf-8"));
8404
+ if (import_fs12.default.existsSync(filePath)) {
8405
+ return (0, import_smol_toml.parse)(import_fs12.default.readFileSync(filePath, "utf-8"));
8290
8406
  }
8291
8407
  } catch {
8292
8408
  }
8293
8409
  return null;
8294
8410
  }
8295
8411
  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));
8412
+ const dir = import_path15.default.dirname(filePath);
8413
+ if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
8414
+ import_fs12.default.writeFileSync(filePath, (0, import_smol_toml.stringify)(data));
8299
8415
  }
8300
8416
  async function setupCodex() {
8301
- const homeDir2 = import_os10.default.homedir();
8302
- const configPath = import_path14.default.join(homeDir2, ".codex", "config.toml");
8417
+ const homeDir2 = import_os11.default.homedir();
8418
+ const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
8303
8419
  const config = readToml(configPath) ?? {};
8304
8420
  const servers = config.mcp_servers ?? {};
8305
8421
  let anythingChanged = false;
@@ -8364,8 +8480,8 @@ async function setupCodex() {
8364
8480
  }
8365
8481
  }
8366
8482
  function setupHud() {
8367
- const homeDir2 = import_os10.default.homedir();
8368
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8483
+ const homeDir2 = import_os11.default.homedir();
8484
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8369
8485
  const settings = readJson(hooksPath) ?? {};
8370
8486
  const hudCommand = fullPathCommand("hud");
8371
8487
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8391,8 +8507,8 @@ function setupHud() {
8391
8507
  console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
8392
8508
  }
8393
8509
  function teardownHud() {
8394
- const homeDir2 = import_os10.default.homedir();
8395
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
8510
+ const homeDir2 = import_os11.default.homedir();
8511
+ const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
8396
8512
  const settings = readJson(hooksPath);
8397
8513
  if (!settings) {
8398
8514
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8412,10 +8528,10 @@ function teardownHud() {
8412
8528
 
8413
8529
  // src/cli.ts
8414
8530
  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"));
8531
+ var import_chalk20 = __toESM(require("chalk"));
8532
+ var import_fs30 = __toESM(require("fs"));
8533
+ var import_path33 = __toESM(require("path"));
8534
+ var import_os26 = __toESM(require("os"));
8419
8535
  var import_prompts2 = require("@inquirer/prompts");
8420
8536
 
8421
8537
  // src/utils/duration.ts
@@ -8640,10 +8756,10 @@ async function autoStartDaemonAndWait() {
8640
8756
 
8641
8757
  // src/cli/commands/check.ts
8642
8758
  var import_chalk5 = __toESM(require("chalk"));
8643
- var import_fs18 = __toESM(require("fs"));
8759
+ var import_fs19 = __toESM(require("fs"));
8644
8760
  var import_child_process9 = require("child_process");
8645
- var import_path20 = __toESM(require("path"));
8646
- var import_os14 = __toESM(require("os"));
8761
+ var import_path21 = __toESM(require("path"));
8762
+ var import_os15 = __toESM(require("os"));
8647
8763
  init_orchestrator();
8648
8764
  init_daemon();
8649
8765
  init_config();
@@ -8651,12 +8767,12 @@ init_policy();
8651
8767
 
8652
8768
  // src/undo.ts
8653
8769
  var import_child_process8 = require("child_process");
8654
- var import_crypto7 = __toESM(require("crypto"));
8655
- var import_fs17 = __toESM(require("fs"));
8770
+ var import_crypto8 = __toESM(require("crypto"));
8771
+ var import_fs18 = __toESM(require("fs"));
8656
8772
  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");
8773
+ var import_path20 = __toESM(require("path"));
8774
+ var import_os14 = __toESM(require("os"));
8775
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path20.default.join(import_os14.default.tmpdir(), "node9-activity.sock");
8660
8776
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8661
8777
  try {
8662
8778
  const payload = JSON.stringify({
@@ -8676,22 +8792,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8676
8792
  } catch {
8677
8793
  }
8678
8794
  }
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");
8795
+ var SNAPSHOT_STACK_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "snapshots.json");
8796
+ var UNDO_LATEST_PATH = import_path20.default.join(import_os14.default.homedir(), ".node9", "undo_latest.txt");
8681
8797
  var MAX_SNAPSHOTS = 10;
8682
8798
  var GIT_TIMEOUT = 15e3;
8683
8799
  function readStack() {
8684
8800
  try {
8685
- if (import_fs17.default.existsSync(SNAPSHOT_STACK_PATH))
8686
- return JSON.parse(import_fs17.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8801
+ if (import_fs18.default.existsSync(SNAPSHOT_STACK_PATH))
8802
+ return JSON.parse(import_fs18.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
8687
8803
  } catch {
8688
8804
  }
8689
8805
  return [];
8690
8806
  }
8691
8807
  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));
8808
+ const dir = import_path20.default.dirname(SNAPSHOT_STACK_PATH);
8809
+ if (!import_fs18.default.existsSync(dir)) import_fs18.default.mkdirSync(dir, { recursive: true });
8810
+ import_fs18.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
8695
8811
  }
8696
8812
  function extractFilePath(args) {
8697
8813
  if (!args || typeof args !== "object") return null;
@@ -8711,12 +8827,12 @@ function buildArgsSummary(tool, args) {
8711
8827
  return "";
8712
8828
  }
8713
8829
  function findProjectRoot(filePath) {
8714
- let dir = import_path19.default.dirname(filePath);
8830
+ let dir = import_path20.default.dirname(filePath);
8715
8831
  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"))) {
8832
+ if (import_fs18.default.existsSync(import_path20.default.join(dir, ".git")) || import_fs18.default.existsSync(import_path20.default.join(dir, "package.json"))) {
8717
8833
  return dir;
8718
8834
  }
8719
- const parent = import_path19.default.dirname(dir);
8835
+ const parent = import_path20.default.dirname(dir);
8720
8836
  if (parent === dir) return process.cwd();
8721
8837
  dir = parent;
8722
8838
  }
@@ -8724,7 +8840,7 @@ function findProjectRoot(filePath) {
8724
8840
  function normalizeCwdForHash(cwd) {
8725
8841
  let normalized;
8726
8842
  try {
8727
- normalized = import_fs17.default.realpathSync(cwd);
8843
+ normalized = import_fs18.default.realpathSync(cwd);
8728
8844
  } catch {
8729
8845
  normalized = cwd;
8730
8846
  }
@@ -8733,17 +8849,17 @@ function normalizeCwdForHash(cwd) {
8733
8849
  return normalized;
8734
8850
  }
8735
8851
  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);
8852
+ const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8853
+ return import_path20.default.join(import_os14.default.homedir(), ".node9", "snapshots", hash);
8738
8854
  }
8739
8855
  function cleanOrphanedIndexFiles(shadowDir) {
8740
8856
  try {
8741
8857
  const cutoff = Date.now() - 6e4;
8742
- for (const f of import_fs17.default.readdirSync(shadowDir)) {
8858
+ for (const f of import_fs18.default.readdirSync(shadowDir)) {
8743
8859
  if (f.startsWith("index_")) {
8744
- const fp = import_path19.default.join(shadowDir, f);
8860
+ const fp = import_path20.default.join(shadowDir, f);
8745
8861
  try {
8746
- if (import_fs17.default.statSync(fp).mtimeMs < cutoff) import_fs17.default.unlinkSync(fp);
8862
+ if (import_fs18.default.statSync(fp).mtimeMs < cutoff) import_fs18.default.unlinkSync(fp);
8747
8863
  } catch {
8748
8864
  }
8749
8865
  }
@@ -8755,7 +8871,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8755
8871
  const hardcoded = [".git", ".node9"];
8756
8872
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8757
8873
  try {
8758
- import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8874
+ import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
8759
8875
  } catch {
8760
8876
  }
8761
8877
  }
@@ -8768,25 +8884,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8768
8884
  timeout: 3e3
8769
8885
  });
8770
8886
  if (check.status === 0) {
8771
- const ptPath = import_path19.default.join(shadowDir, "project-path.txt");
8887
+ const ptPath = import_path20.default.join(shadowDir, "project-path.txt");
8772
8888
  try {
8773
- const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
8889
+ const stored = import_fs18.default.readFileSync(ptPath, "utf8").trim();
8774
8890
  if (stored === normalizedCwd) return true;
8775
8891
  if (process.env.NODE9_DEBUG === "1")
8776
8892
  console.error(
8777
8893
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8778
8894
  );
8779
- import_fs17.default.rmSync(shadowDir, { recursive: true, force: true });
8895
+ import_fs18.default.rmSync(shadowDir, { recursive: true, force: true });
8780
8896
  } catch {
8781
8897
  try {
8782
- import_fs17.default.writeFileSync(ptPath, normalizedCwd, "utf8");
8898
+ import_fs18.default.writeFileSync(ptPath, normalizedCwd, "utf8");
8783
8899
  } catch {
8784
8900
  }
8785
8901
  return true;
8786
8902
  }
8787
8903
  }
8788
8904
  try {
8789
- import_fs17.default.mkdirSync(shadowDir, { recursive: true });
8905
+ import_fs18.default.mkdirSync(shadowDir, { recursive: true });
8790
8906
  } catch {
8791
8907
  }
8792
8908
  const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8795,7 +8911,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8795
8911
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8796
8912
  return false;
8797
8913
  }
8798
- const configFile = import_path19.default.join(shadowDir, "config");
8914
+ const configFile = import_path20.default.join(shadowDir, "config");
8799
8915
  (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8800
8916
  timeout: 3e3
8801
8917
  });
@@ -8803,7 +8919,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8803
8919
  timeout: 3e3
8804
8920
  });
8805
8921
  try {
8806
- import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8922
+ import_fs18.default.writeFileSync(import_path20.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
8807
8923
  } catch {
8808
8924
  }
8809
8925
  return true;
@@ -8823,12 +8939,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8823
8939
  let indexFile = null;
8824
8940
  try {
8825
8941
  const rawFilePath = extractFilePath(args);
8826
- const absFilePath = rawFilePath && import_path19.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8942
+ const absFilePath = rawFilePath && import_path20.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8827
8943
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8828
8944
  const shadowDir = getShadowRepoDir(cwd);
8829
8945
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8830
8946
  writeShadowExcludes(shadowDir, ignorePaths);
8831
- indexFile = import_path19.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8947
+ indexFile = import_path20.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8832
8948
  const shadowEnv = {
8833
8949
  ...process.env,
8834
8950
  GIT_DIR: shadowDir,
@@ -8900,7 +9016,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8900
9016
  writeStack(stack);
8901
9017
  const entry = stack[stack.length - 1];
8902
9018
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8903
- import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9019
+ import_fs18.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
8904
9020
  if (shouldGc) {
8905
9021
  (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8906
9022
  }
@@ -8911,7 +9027,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8911
9027
  } finally {
8912
9028
  if (indexFile) {
8913
9029
  try {
8914
- import_fs17.default.unlinkSync(indexFile);
9030
+ import_fs18.default.unlinkSync(indexFile);
8915
9031
  } catch {
8916
9032
  }
8917
9033
  }
@@ -8987,9 +9103,9 @@ function applyUndo(hash, cwd) {
8987
9103
  timeout: GIT_TIMEOUT
8988
9104
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8989
9105
  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);
9106
+ const fullPath = import_path20.default.join(dir, file);
9107
+ if (!snapshotFiles.has(file) && import_fs18.default.existsSync(fullPath)) {
9108
+ import_fs18.default.unlinkSync(fullPath);
8993
9109
  }
8994
9110
  }
8995
9111
  return true;
@@ -9013,9 +9129,9 @@ function registerCheckCommand(program2) {
9013
9129
  } catch (err2) {
9014
9130
  const tempConfig = getConfig();
9015
9131
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9016
- const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
9132
+ const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9017
9133
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9018
- import_fs18.default.appendFileSync(
9134
+ import_fs19.default.appendFileSync(
9019
9135
  logPath,
9020
9136
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9021
9137
  RAW: ${raw}
@@ -9028,10 +9144,10 @@ RAW: ${raw}
9028
9144
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9029
9145
  try {
9030
9146
  const scriptPath = process.argv[1];
9031
- if (typeof scriptPath !== "string" || !import_path20.default.isAbsolute(scriptPath))
9147
+ if (typeof scriptPath !== "string" || !import_path21.default.isAbsolute(scriptPath))
9032
9148
  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"));
9149
+ const resolvedScript = import_fs19.default.realpathSync(scriptPath);
9150
+ const expectedCli = import_fs19.default.realpathSync(import_path21.default.resolve(__dirname, "../../cli.js"));
9035
9151
  if (resolvedScript !== expectedCli)
9036
9152
  throw new Error(
9037
9153
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9057,10 +9173,10 @@ RAW: ${raw}
9057
9173
  }
9058
9174
  }
9059
9175
  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}
9176
+ const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9177
+ if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
9178
+ import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
9179
+ import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9064
9180
  `);
9065
9181
  }
9066
9182
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9073,8 +9189,8 @@ RAW: ${raw}
9073
9189
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9074
9190
  let ttyFd = null;
9075
9191
  try {
9076
- ttyFd = import_fs18.default.openSync("/dev/tty", "w");
9077
- const writeTty = (line) => import_fs18.default.writeSync(ttyFd, line + "\n");
9192
+ ttyFd = import_fs19.default.openSync("/dev/tty", "w");
9193
+ const writeTty = (line) => import_fs19.default.writeSync(ttyFd, line + "\n");
9078
9194
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9079
9195
  writeTty(import_chalk5.default.bgRed.white.bold(`
9080
9196
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9093,7 +9209,7 @@ RAW: ${raw}
9093
9209
  } finally {
9094
9210
  if (ttyFd !== null)
9095
9211
  try {
9096
- import_fs18.default.closeSync(ttyFd);
9212
+ import_fs19.default.closeSync(ttyFd);
9097
9213
  } catch {
9098
9214
  }
9099
9215
  }
@@ -9125,7 +9241,7 @@ RAW: ${raw}
9125
9241
  if (shouldSnapshot(toolName, toolInput, config)) {
9126
9242
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9127
9243
  }
9128
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9244
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9129
9245
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9130
9246
  cwd: safeCwdForAuth
9131
9247
  });
@@ -9137,12 +9253,12 @@ RAW: ${raw}
9137
9253
  }
9138
9254
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9139
9255
  try {
9140
- const tty = import_fs18.default.openSync("/dev/tty", "w");
9141
- import_fs18.default.writeSync(
9256
+ const tty = import_fs19.default.openSync("/dev/tty", "w");
9257
+ import_fs19.default.writeSync(
9142
9258
  tty,
9143
9259
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9144
9260
  );
9145
- import_fs18.default.closeSync(tty);
9261
+ import_fs19.default.closeSync(tty);
9146
9262
  } catch {
9147
9263
  }
9148
9264
  const daemonReady = await autoStartDaemonAndWait();
@@ -9169,9 +9285,9 @@ RAW: ${raw}
9169
9285
  });
9170
9286
  } catch (err2) {
9171
9287
  if (process.env.NODE9_DEBUG === "1") {
9172
- const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
9288
+ const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9173
9289
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9174
- import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9290
+ import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9175
9291
  `);
9176
9292
  }
9177
9293
  process.exit(0);
@@ -9205,9 +9321,9 @@ RAW: ${raw}
9205
9321
  }
9206
9322
 
9207
9323
  // 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"));
9324
+ var import_fs20 = __toESM(require("fs"));
9325
+ var import_path22 = __toESM(require("path"));
9326
+ var import_os16 = __toESM(require("os"));
9211
9327
  init_audit();
9212
9328
  init_config();
9213
9329
  init_policy();
@@ -9251,9 +9367,9 @@ function containsShellMetachar(token) {
9251
9367
  }
9252
9368
 
9253
9369
  // 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;
9370
+ 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
9371
  function detectTestResult(command, output) {
9256
- if (!TEST_COMMAND_RE.test(command)) return null;
9372
+ if (!TEST_COMMAND_RE2.test(command)) return null;
9257
9373
  const out = output.toLowerCase();
9258
9374
  if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
9259
9375
  out
@@ -9283,10 +9399,10 @@ function registerLogCommand(program2) {
9283
9399
  decision: "allowed",
9284
9400
  source: "post-hook"
9285
9401
  };
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");
9402
+ const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "audit.log");
9403
+ if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
9404
+ import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
9405
+ import_fs20.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9290
9406
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9291
9407
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9292
9408
  if (command) {
@@ -9302,16 +9418,24 @@ function registerLogCommand(program2) {
9302
9418
  if (bashCommand && output) {
9303
9419
  const testResult = detectTestResult(bashCommand, output);
9304
9420
  if (testResult) {
9305
- await notifyActivitySocket({
9306
- id: "test-result",
9307
- ts: Date.now(),
9421
+ appendToLog(LOCAL_AUDIT_LOG, {
9422
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9308
9423
  tool,
9309
- status: testResult === "pass" ? "test_pass" : "test_fail"
9424
+ testResult,
9425
+ source: "test-result"
9310
9426
  });
9427
+ if (isDaemonRunning()) {
9428
+ await notifyActivitySocket({
9429
+ id: "test-result",
9430
+ ts: Date.now(),
9431
+ tool,
9432
+ status: testResult === "pass" ? "test_pass" : "test_fail"
9433
+ });
9434
+ }
9311
9435
  }
9312
9436
  }
9313
9437
  }
9314
- const safeCwd = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9438
+ const safeCwd = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9315
9439
  const config = getConfig(safeCwd);
9316
9440
  if (shouldSnapshot(tool, {}, config)) {
9317
9441
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9320,9 +9444,9 @@ function registerLogCommand(program2) {
9320
9444
  const msg = err2 instanceof Error ? err2.message : String(err2);
9321
9445
  process.stderr.write(`[Node9] audit log error: ${msg}
9322
9446
  `);
9323
- const debugPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
9447
+ const debugPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9324
9448
  try {
9325
- import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9449
+ import_fs20.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9326
9450
  `);
9327
9451
  } catch {
9328
9452
  }
@@ -9722,14 +9846,14 @@ function registerConfigShowCommand(program2) {
9722
9846
 
9723
9847
  // src/cli/commands/doctor.ts
9724
9848
  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"));
9849
+ var import_fs21 = __toESM(require("fs"));
9850
+ var import_path23 = __toESM(require("path"));
9851
+ var import_os17 = __toESM(require("os"));
9728
9852
  var import_child_process10 = require("child_process");
9729
9853
  init_daemon();
9730
9854
  function registerDoctorCommand(program2, version2) {
9731
9855
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9732
- const homeDir2 = import_os16.default.homedir();
9856
+ const homeDir2 = import_os17.default.homedir();
9733
9857
  let failures = 0;
9734
9858
  function pass(msg) {
9735
9859
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -9778,10 +9902,10 @@ function registerDoctorCommand(program2, version2) {
9778
9902
  );
9779
9903
  }
9780
9904
  section("Configuration");
9781
- const globalConfigPath = import_path22.default.join(homeDir2, ".node9", "config.json");
9782
- if (import_fs20.default.existsSync(globalConfigPath)) {
9905
+ const globalConfigPath = import_path23.default.join(homeDir2, ".node9", "config.json");
9906
+ if (import_fs21.default.existsSync(globalConfigPath)) {
9783
9907
  try {
9784
- JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
9908
+ JSON.parse(import_fs21.default.readFileSync(globalConfigPath, "utf-8"));
9785
9909
  pass("~/.node9/config.json found and valid");
9786
9910
  } catch {
9787
9911
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9789,10 +9913,10 @@ function registerDoctorCommand(program2, version2) {
9789
9913
  } else {
9790
9914
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9791
9915
  }
9792
- const projectConfigPath = import_path22.default.join(process.cwd(), "node9.config.json");
9793
- if (import_fs20.default.existsSync(projectConfigPath)) {
9916
+ const projectConfigPath = import_path23.default.join(process.cwd(), "node9.config.json");
9917
+ if (import_fs21.default.existsSync(projectConfigPath)) {
9794
9918
  try {
9795
- JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
9919
+ JSON.parse(import_fs21.default.readFileSync(projectConfigPath, "utf-8"));
9796
9920
  pass("node9.config.json found and valid (project)");
9797
9921
  } catch {
9798
9922
  fail(
@@ -9801,8 +9925,8 @@ function registerDoctorCommand(program2, version2) {
9801
9925
  );
9802
9926
  }
9803
9927
  }
9804
- const credsPath = import_path22.default.join(homeDir2, ".node9", "credentials.json");
9805
- if (import_fs20.default.existsSync(credsPath)) {
9928
+ const credsPath = import_path23.default.join(homeDir2, ".node9", "credentials.json");
9929
+ if (import_fs21.default.existsSync(credsPath)) {
9806
9930
  pass("Cloud credentials found (~/.node9/credentials.json)");
9807
9931
  } else {
9808
9932
  warn(
@@ -9811,10 +9935,10 @@ function registerDoctorCommand(program2, version2) {
9811
9935
  );
9812
9936
  }
9813
9937
  section("Agent Hooks");
9814
- const claudeSettingsPath = import_path22.default.join(homeDir2, ".claude", "settings.json");
9815
- if (import_fs20.default.existsSync(claudeSettingsPath)) {
9938
+ const claudeSettingsPath = import_path23.default.join(homeDir2, ".claude", "settings.json");
9939
+ if (import_fs21.default.existsSync(claudeSettingsPath)) {
9816
9940
  try {
9817
- const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
9941
+ const cs = JSON.parse(import_fs21.default.readFileSync(claudeSettingsPath, "utf-8"));
9818
9942
  const hasHook = cs.hooks?.PreToolUse?.some(
9819
9943
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9820
9944
  );
@@ -9830,10 +9954,10 @@ function registerDoctorCommand(program2, version2) {
9830
9954
  } else {
9831
9955
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9832
9956
  }
9833
- const geminiSettingsPath = import_path22.default.join(homeDir2, ".gemini", "settings.json");
9834
- if (import_fs20.default.existsSync(geminiSettingsPath)) {
9957
+ const geminiSettingsPath = import_path23.default.join(homeDir2, ".gemini", "settings.json");
9958
+ if (import_fs21.default.existsSync(geminiSettingsPath)) {
9835
9959
  try {
9836
- const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
9960
+ const gs = JSON.parse(import_fs21.default.readFileSync(geminiSettingsPath, "utf-8"));
9837
9961
  const hasHook = gs.hooks?.BeforeTool?.some(
9838
9962
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9839
9963
  );
@@ -9849,10 +9973,10 @@ function registerDoctorCommand(program2, version2) {
9849
9973
  } else {
9850
9974
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9851
9975
  }
9852
- const cursorHooksPath = import_path22.default.join(homeDir2, ".cursor", "hooks.json");
9853
- if (import_fs20.default.existsSync(cursorHooksPath)) {
9976
+ const cursorHooksPath = import_path23.default.join(homeDir2, ".cursor", "hooks.json");
9977
+ if (import_fs21.default.existsSync(cursorHooksPath)) {
9854
9978
  try {
9855
- const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
9979
+ const cur = JSON.parse(import_fs21.default.readFileSync(cursorHooksPath, "utf-8"));
9856
9980
  const hasHook = cur.hooks?.preToolUse?.some(
9857
9981
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9858
9982
  );
@@ -9890,9 +10014,9 @@ function registerDoctorCommand(program2, version2) {
9890
10014
 
9891
10015
  // src/cli/commands/audit.ts
9892
10016
  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"));
10017
+ var import_fs22 = __toESM(require("fs"));
10018
+ var import_path24 = __toESM(require("path"));
10019
+ var import_os18 = __toESM(require("os"));
9896
10020
  function formatRelativeTime(timestamp) {
9897
10021
  const diff = Date.now() - new Date(timestamp).getTime();
9898
10022
  const sec = Math.floor(diff / 1e3);
@@ -9905,14 +10029,14 @@ function formatRelativeTime(timestamp) {
9905
10029
  }
9906
10030
  function registerAuditCommand(program2) {
9907
10031
  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)) {
10032
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "audit.log");
10033
+ if (!import_fs22.default.existsSync(logPath)) {
9910
10034
  console.log(
9911
10035
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
9912
10036
  );
9913
10037
  return;
9914
10038
  }
9915
- const raw = import_fs21.default.readFileSync(logPath, "utf-8");
10039
+ const raw = import_fs22.default.readFileSync(logPath, "utf-8");
9916
10040
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
9917
10041
  let entries = lines.flatMap((line) => {
9918
10042
  try {
@@ -9964,8 +10088,396 @@ function registerAuditCommand(program2) {
9964
10088
  });
9965
10089
  }
9966
10090
 
9967
- // src/cli/commands/daemon-cmd.ts
10091
+ // src/cli/commands/report.ts
9968
10092
  var import_chalk9 = __toESM(require("chalk"));
10093
+ var import_fs23 = __toESM(require("fs"));
10094
+ var import_path25 = __toESM(require("path"));
10095
+ var import_os19 = __toESM(require("os"));
10096
+ 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;
10097
+ function buildTestTimestamps(allEntries) {
10098
+ const testTs = /* @__PURE__ */ new Set();
10099
+ for (const e of allEntries) {
10100
+ if (e.source !== "post-hook") continue;
10101
+ if (e.tool !== "Bash" && e.tool !== "bash") continue;
10102
+ const cmd = e.args?.command;
10103
+ if (typeof cmd === "string" && TEST_COMMAND_RE3.test(cmd)) {
10104
+ testTs.add(new Date(e.ts).getTime());
10105
+ }
10106
+ }
10107
+ return testTs;
10108
+ }
10109
+ function isTestEntry(entry, testTs) {
10110
+ if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
10111
+ if (entry.testRun === true) return true;
10112
+ const cmd = entry.args?.command;
10113
+ if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
10114
+ const t = new Date(entry.ts).getTime();
10115
+ for (const ts of testTs) {
10116
+ if (Math.abs(ts - t) <= 3e3) return true;
10117
+ }
10118
+ return false;
10119
+ }
10120
+ function getDateRange(period) {
10121
+ const now = /* @__PURE__ */ new Date();
10122
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
10123
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
10124
+ switch (period) {
10125
+ case "today":
10126
+ return { start: todayStart, end };
10127
+ case "7d": {
10128
+ const s = new Date(todayStart);
10129
+ s.setDate(s.getDate() - 6);
10130
+ return { start: s, end };
10131
+ }
10132
+ case "30d": {
10133
+ const s = new Date(todayStart);
10134
+ s.setDate(s.getDate() - 29);
10135
+ return { start: s, end };
10136
+ }
10137
+ case "month":
10138
+ return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
10139
+ }
10140
+ }
10141
+ function parseAuditLog(logPath) {
10142
+ if (!import_fs23.default.existsSync(logPath)) return [];
10143
+ const raw = import_fs23.default.readFileSync(logPath, "utf-8");
10144
+ return raw.split("\n").flatMap((line) => {
10145
+ if (!line.trim()) return [];
10146
+ try {
10147
+ return [JSON.parse(line)];
10148
+ } catch {
10149
+ return [];
10150
+ }
10151
+ });
10152
+ }
10153
+ function isAllow(decision) {
10154
+ return decision.startsWith("allow");
10155
+ }
10156
+ function isDlp(checkedBy) {
10157
+ return !!checkedBy?.includes("dlp");
10158
+ }
10159
+ function barStr(value, max, width) {
10160
+ if (max === 0 || width <= 0) return "\u2591".repeat(width);
10161
+ const filled = Math.max(1, Math.round(value / max * width));
10162
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
10163
+ }
10164
+ function colorBar(value, max, width) {
10165
+ const s = barStr(value, max, width);
10166
+ const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
10167
+ return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
10168
+ }
10169
+ function pct(num2, total) {
10170
+ if (total === 0) return "\u2013";
10171
+ return Math.round(num2 / total * 100) + "%";
10172
+ }
10173
+ function fmtDate(d) {
10174
+ const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
10175
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
10176
+ }
10177
+ function num(n) {
10178
+ return n.toLocaleString();
10179
+ }
10180
+ function fmtCost(usd) {
10181
+ if (usd < 1e-3) return "< $0.001";
10182
+ if (usd < 1) return "$" + usd.toFixed(4);
10183
+ return "$" + usd.toFixed(2);
10184
+ }
10185
+ var CLAUDE_PRICING = {
10186
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10187
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
10188
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
10189
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10190
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10191
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10192
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10193
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
10194
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
10195
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
10196
+ };
10197
+ function claudeModelPrice(model) {
10198
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
10199
+ for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
10200
+ if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
10201
+ }
10202
+ return null;
10203
+ }
10204
+ function loadClaudeCost(start, end) {
10205
+ const projectsDir = import_path25.default.join(import_os19.default.homedir(), ".claude", "projects");
10206
+ if (!import_fs23.default.existsSync(projectsDir)) return { total: 0, byDay: /* @__PURE__ */ new Map() };
10207
+ let dirs;
10208
+ try {
10209
+ dirs = import_fs23.default.readdirSync(projectsDir);
10210
+ } catch {
10211
+ return { total: 0, byDay: /* @__PURE__ */ new Map() };
10212
+ }
10213
+ let total = 0;
10214
+ const byDay = /* @__PURE__ */ new Map();
10215
+ for (const proj of dirs) {
10216
+ const projPath = import_path25.default.join(projectsDir, proj);
10217
+ let files;
10218
+ try {
10219
+ const stat = import_fs23.default.statSync(projPath);
10220
+ if (!stat.isDirectory()) continue;
10221
+ files = import_fs23.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10222
+ } catch {
10223
+ continue;
10224
+ }
10225
+ for (const file of files) {
10226
+ try {
10227
+ const raw = import_fs23.default.readFileSync(import_path25.default.join(projPath, file), "utf-8");
10228
+ for (const line of raw.split("\n")) {
10229
+ if (!line.trim()) continue;
10230
+ let entry;
10231
+ try {
10232
+ entry = JSON.parse(line);
10233
+ } catch {
10234
+ continue;
10235
+ }
10236
+ if (entry.type !== "assistant") continue;
10237
+ if (!entry.timestamp) continue;
10238
+ const ts = new Date(entry.timestamp);
10239
+ if (ts < start || ts > end) continue;
10240
+ const usage = entry.message?.usage;
10241
+ const model = entry.message?.model;
10242
+ if (!usage || !model) continue;
10243
+ const p = claudeModelPrice(model);
10244
+ if (!p) continue;
10245
+ const cost = (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
10246
+ total += cost;
10247
+ const dateKey = entry.timestamp.slice(0, 10);
10248
+ byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
10249
+ }
10250
+ } catch {
10251
+ continue;
10252
+ }
10253
+ }
10254
+ }
10255
+ return { total, byDay };
10256
+ }
10257
+ function registerReportCommand(program2) {
10258
+ 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) => {
10259
+ const period = ["today", "7d", "30d", "month"].includes(
10260
+ options.period
10261
+ ) ? options.period : "7d";
10262
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10263
+ const allEntries = parseAuditLog(logPath);
10264
+ if (allEntries.length === 0) {
10265
+ console.log(
10266
+ import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
10267
+ );
10268
+ return;
10269
+ }
10270
+ const { start, end } = getDateRange(period);
10271
+ const { total: costUSD, byDay: costByDay } = loadClaudeCost(start, end);
10272
+ const periodMs = end.getTime() - start.getTime();
10273
+ const priorEnd = new Date(start.getTime() - 1);
10274
+ const priorStart = new Date(start.getTime() - periodMs);
10275
+ const priorEntries = allEntries.filter((e) => {
10276
+ if (e.source === "post-hook") return false;
10277
+ const ts = new Date(e.ts);
10278
+ return ts >= priorStart && ts <= priorEnd;
10279
+ });
10280
+ const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
10281
+ const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
10282
+ const excludeTests = options.tests === false;
10283
+ const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
10284
+ let filteredTestCount = 0;
10285
+ const entries = allEntries.filter((e) => {
10286
+ if (e.source === "post-hook") return false;
10287
+ const ts = new Date(e.ts);
10288
+ if (ts < start || ts > end) return false;
10289
+ if (excludeTests && isTestEntry(e, testTs)) {
10290
+ filteredTestCount++;
10291
+ return false;
10292
+ }
10293
+ return true;
10294
+ });
10295
+ if (entries.length === 0) {
10296
+ console.log(import_chalk9.default.yellow(`
10297
+ No activity for period "${period}".
10298
+ `));
10299
+ return;
10300
+ }
10301
+ let allowed = 0;
10302
+ let blocked = 0;
10303
+ let dlpHits = 0;
10304
+ let loopHits = 0;
10305
+ let testPasses = 0;
10306
+ let testFails = 0;
10307
+ const toolMap = /* @__PURE__ */ new Map();
10308
+ const blockMap = /* @__PURE__ */ new Map();
10309
+ const agentMap = /* @__PURE__ */ new Map();
10310
+ const mcpMap = /* @__PURE__ */ new Map();
10311
+ const dailyMap = /* @__PURE__ */ new Map();
10312
+ const hourMap = /* @__PURE__ */ new Map();
10313
+ for (const e of entries) {
10314
+ const allow = isAllow(e.decision);
10315
+ const dateKey = e.ts.slice(0, 10);
10316
+ if (allow) allowed++;
10317
+ else blocked++;
10318
+ if (isDlp(e.checkedBy)) dlpHits++;
10319
+ if (e.checkedBy === "loop-detected") loopHits++;
10320
+ const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
10321
+ t.calls++;
10322
+ if (!allow) t.blocked++;
10323
+ toolMap.set(e.tool, t);
10324
+ if (!allow && e.checkedBy) {
10325
+ blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
10326
+ }
10327
+ if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
10328
+ if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
10329
+ const hour = new Date(e.ts).getHours();
10330
+ hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
10331
+ const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
10332
+ d.calls++;
10333
+ if (!allow) d.blocked++;
10334
+ dailyMap.set(dateKey, d);
10335
+ }
10336
+ for (const e of allEntries) {
10337
+ if (e.source !== "test-result") continue;
10338
+ const ts = new Date(e.ts);
10339
+ if (ts < start || ts > end) continue;
10340
+ if (e.testResult === "pass") testPasses++;
10341
+ else if (e.testResult === "fail") testFails++;
10342
+ }
10343
+ const total = entries.length;
10344
+ const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
10345
+ const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
10346
+ const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
10347
+ const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
10348
+ const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
10349
+ const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
10350
+ const W = Math.min(process.stdout.columns || 80, 100);
10351
+ const INNER = W - 4;
10352
+ const COL = Math.floor(INNER / 2) - 1;
10353
+ const LABEL = 24;
10354
+ const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
10355
+ const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num(v.calls).length), 1);
10356
+ const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num(v).length), 1);
10357
+ const line = import_chalk9.default.dim("\u2500".repeat(W - 2));
10358
+ const periodLabel = {
10359
+ today: "Today",
10360
+ "7d": "Last 7 Days",
10361
+ "30d": "Last 30 Days",
10362
+ month: "This Month"
10363
+ };
10364
+ console.log("");
10365
+ console.log(
10366
+ " " + 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})`) : "")
10367
+ );
10368
+ console.log(" " + line);
10369
+ console.log("");
10370
+ const blockLabel = blocked > 0 ? import_chalk9.default.red(`\u{1F6D1} ${num(blocked)} blocked`) : import_chalk9.default.dim("\u{1F6D1} 0 blocked");
10371
+ const dlpLabel = dlpHits > 0 ? import_chalk9.default.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : import_chalk9.default.dim("\u{1F6A8} 0 DLP hits");
10372
+ const loopLabel = loopHits > 0 ? import_chalk9.default.yellow(`\u{1F504} ${loopHits} loops`) : import_chalk9.default.dim("\u{1F504} 0 loops");
10373
+ const costLabel = costUSD > 0 ? import_chalk9.default.magenta(`\u{1F4B0} ${fmtCost(costUSD)}`) : import_chalk9.default.dim("\u{1F4B0} \u2013");
10374
+ const currentRate = total > 0 ? blocked / total : 0;
10375
+ const trendLabel = (() => {
10376
+ if (priorBlockRate === null) return import_chalk9.default.dim(`${pct(blocked, total)} block rate`);
10377
+ const delta = Math.round((currentRate - priorBlockRate) * 100);
10378
+ 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");
10379
+ return import_chalk9.default.dim(`${pct(blocked, total)} block rate `) + arrow + import_chalk9.default.dim(" vs prior");
10380
+ })();
10381
+ const reads = toolMap.get("Read")?.calls ?? 0;
10382
+ const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
10383
+ const ratioLabel = reads > 0 ? import_chalk9.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk9.default.dim("edit/read \u2013");
10384
+ 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");
10385
+ console.log(
10386
+ " " + import_chalk9.default.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel + " " + costLabel
10387
+ );
10388
+ console.log(" " + ratioLabel + " " + testLabel);
10389
+ console.log("");
10390
+ const toolHeaderRaw = "Top Tools";
10391
+ const blockHeaderRaw = "Top Blocks";
10392
+ console.log(
10393
+ " " + import_chalk9.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk9.default.bold(blockHeaderRaw)
10394
+ );
10395
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(COL)) + " " + import_chalk9.default.dim("\u2500".repeat(COL)));
10396
+ const rows = Math.max(topTools.length, topBlocks.length, 1);
10397
+ for (let i = 0; i < rows; i++) {
10398
+ let leftStyled = " ".repeat(COL);
10399
+ if (i < topTools.length) {
10400
+ const [tool, { calls }] = topTools[i];
10401
+ const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
10402
+ const countStr = num(calls).padStart(TOOL_COUNT_W);
10403
+ const b = colorBar(calls, maxTool, BAR);
10404
+ const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
10405
+ const pad = Math.max(0, COL - rawLen);
10406
+ leftStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(countStr) + " ".repeat(pad);
10407
+ }
10408
+ let rightStyled = "";
10409
+ if (i < topBlocks.length) {
10410
+ const [reason, count] = topBlocks[i];
10411
+ const label = reason.length > LABEL - 1 ? reason.slice(0, LABEL - 2) + "\u2026" : reason;
10412
+ const countStr = num(count).padStart(BLOCK_COUNT_W);
10413
+ const b = colorBar(count, maxBlock, BAR);
10414
+ rightStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.red(countStr);
10415
+ }
10416
+ console.log(" " + leftStyled + " " + rightStyled);
10417
+ }
10418
+ if (topBlocks.length === 0) {
10419
+ console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
10420
+ }
10421
+ if (agentMap.size > 1) {
10422
+ console.log("");
10423
+ console.log(" " + import_chalk9.default.bold("Agents"));
10424
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
10425
+ const maxAgent = Math.max(...agentMap.values(), 1);
10426
+ for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
10427
+ const label = agent.slice(0, LABEL - 1);
10428
+ const b = colorBar(count, maxAgent, BAR);
10429
+ console.log(" " + import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(num(count)));
10430
+ }
10431
+ }
10432
+ if (mcpMap.size > 0) {
10433
+ console.log("");
10434
+ console.log(" " + import_chalk9.default.bold("MCP Servers"));
10435
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
10436
+ const maxMcp = Math.max(...mcpMap.values(), 1);
10437
+ for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
10438
+ const label = server.slice(0, LABEL - 1).padEnd(LABEL);
10439
+ const b = colorBar(count, maxMcp, BAR);
10440
+ console.log(" " + import_chalk9.default.white(label) + b + " " + import_chalk9.default.white(num(count)));
10441
+ }
10442
+ }
10443
+ if (hourMap.size > 0) {
10444
+ const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
10445
+ const maxHour = Math.max(...hourMap.values(), 1);
10446
+ const bar = Array.from({ length: 24 }, (_, h) => {
10447
+ const v = hourMap.get(h) ?? 0;
10448
+ return BLOCKS[Math.round(v / maxHour * 8)];
10449
+ }).join("");
10450
+ console.log("");
10451
+ console.log(" " + import_chalk9.default.bold("Hour of Day") + import_chalk9.default.dim(" (local, 0h \u2013 23h)"));
10452
+ console.log(" " + import_chalk9.default.cyan(bar));
10453
+ console.log(" " + import_chalk9.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
10454
+ }
10455
+ if (dailyList.length > 1) {
10456
+ console.log("");
10457
+ console.log(" " + import_chalk9.default.bold("Daily Activity"));
10458
+ console.log(" " + import_chalk9.default.dim("\u2500".repeat(W - 2)));
10459
+ const DAY_BAR = Math.max(8, Math.min(30, W - 36));
10460
+ for (const [dateKey, { calls, blocked: db }] of dailyList) {
10461
+ const label = fmtDate(dateKey).padEnd(10);
10462
+ const b = colorBar(calls, maxDaily, DAY_BAR);
10463
+ const dayCost = costByDay.get(dateKey);
10464
+ const costNote = dayCost ? import_chalk9.default.magenta(` ${fmtCost(dayCost)}`) : "";
10465
+ const blockNote = db > 0 ? import_chalk9.default.red(` ${db} blocked`) : "";
10466
+ console.log(
10467
+ " " + import_chalk9.default.dim(label) + " " + b + " " + import_chalk9.default.white(num(calls)) + blockNote + costNote
10468
+ );
10469
+ }
10470
+ }
10471
+ console.log("");
10472
+ console.log(
10473
+ " " + 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")
10474
+ );
10475
+ console.log("");
10476
+ });
10477
+ }
10478
+
10479
+ // src/cli/commands/daemon-cmd.ts
10480
+ var import_chalk10 = __toESM(require("chalk"));
9969
10481
  var import_child_process11 = require("child_process");
9970
10482
  init_daemon2();
9971
10483
  init_daemon();
@@ -9980,7 +10492,7 @@ function registerDaemonCommand(program2) {
9980
10492
  if (cmd === "status") return daemonStatus();
9981
10493
  if (cmd !== "start" && action !== void 0) {
9982
10494
  console.error(
9983
- import_chalk9.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10495
+ import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
9984
10496
  );
9985
10497
  process.exit(1);
9986
10498
  }
@@ -9988,7 +10500,7 @@ function registerDaemonCommand(program2) {
9988
10500
  process.env.NODE9_WATCH_MODE = "1";
9989
10501
  setTimeout(() => {
9990
10502
  openBrowserLocal();
9991
- console.log(import_chalk9.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10503
+ console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
9992
10504
  }, 600);
9993
10505
  startDaemon();
9994
10506
  return;
@@ -9996,7 +10508,7 @@ function registerDaemonCommand(program2) {
9996
10508
  if (options.openui) {
9997
10509
  if (isDaemonRunning()) {
9998
10510
  openBrowserLocal();
9999
- console.log(import_chalk9.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10511
+ console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10000
10512
  process.exit(0);
10001
10513
  }
10002
10514
  const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
@@ -10009,7 +10521,7 @@ function registerDaemonCommand(program2) {
10009
10521
  if (isDaemonRunning()) break;
10010
10522
  }
10011
10523
  openBrowserLocal();
10012
- console.log(import_chalk9.default.green(`
10524
+ console.log(import_chalk10.default.green(`
10013
10525
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
10014
10526
  process.exit(0);
10015
10527
  }
@@ -10019,7 +10531,7 @@ function registerDaemonCommand(program2) {
10019
10531
  stdio: "ignore"
10020
10532
  });
10021
10533
  child.unref();
10022
- console.log(import_chalk9.default.green(`
10534
+ console.log(import_chalk10.default.green(`
10023
10535
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
10024
10536
  process.exit(0);
10025
10537
  }
@@ -10029,15 +10541,15 @@ function registerDaemonCommand(program2) {
10029
10541
  }
10030
10542
 
10031
10543
  // 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"));
10544
+ var import_chalk11 = __toESM(require("chalk"));
10545
+ var import_fs24 = __toESM(require("fs"));
10546
+ var import_path26 = __toESM(require("path"));
10547
+ var import_os20 = __toESM(require("os"));
10036
10548
  init_core();
10037
10549
  init_daemon();
10038
10550
  function readJson2(filePath) {
10039
10551
  try {
10040
- if (import_fs22.default.existsSync(filePath)) return JSON.parse(import_fs22.default.readFileSync(filePath, "utf-8"));
10552
+ if (import_fs24.default.existsSync(filePath)) return JSON.parse(import_fs24.default.readFileSync(filePath, "utf-8"));
10041
10553
  } catch {
10042
10554
  }
10043
10555
  return null;
@@ -10051,21 +10563,21 @@ function wrappedMcpServers(servers) {
10051
10563
  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
10564
  }
10053
10565
  function printAgentSection(label, hookPairs, wrapped) {
10054
- console.log(import_chalk10.default.bold(` ${label}`));
10566
+ console.log(import_chalk11.default.bold(` ${label}`));
10055
10567
  for (const { name, present } of hookPairs) {
10056
10568
  if (present) {
10057
- console.log(import_chalk10.default.green(` \u2713 ${name}`));
10569
+ console.log(import_chalk11.default.green(` \u2713 ${name}`));
10058
10570
  } else {
10059
- console.log(import_chalk10.default.red(` \u2717 ${name}`) + import_chalk10.default.gray(" (not wired)"));
10571
+ console.log(import_chalk11.default.red(` \u2717 ${name}`) + import_chalk11.default.gray(" (not wired)"));
10060
10572
  }
10061
10573
  }
10062
10574
  if (wrapped.length > 0) {
10063
- console.log(import_chalk10.default.cyan(` MCP proxied:`));
10575
+ console.log(import_chalk11.default.cyan(` MCP proxied:`));
10064
10576
  for (const entry of wrapped) {
10065
- console.log(import_chalk10.default.gray(` \u2022 ${entry}`));
10577
+ console.log(import_chalk11.default.gray(` \u2022 ${entry}`));
10066
10578
  }
10067
10579
  } else {
10068
- console.log(import_chalk10.default.gray(` MCP proxied: none`));
10580
+ console.log(import_chalk11.default.gray(` MCP proxied: none`));
10069
10581
  }
10070
10582
  }
10071
10583
  function registerStatusCommand(program2) {
@@ -10076,58 +10588,58 @@ function registerStatusCommand(program2) {
10076
10588
  const settings = mergedConfig.settings;
10077
10589
  console.log("");
10078
10590
  if (creds && settings.approvers.cloud) {
10079
- console.log(import_chalk10.default.green(" \u25CF Agent mode") + import_chalk10.default.gray(" \u2014 cloud team policy enforced"));
10591
+ console.log(import_chalk11.default.green(" \u25CF Agent mode") + import_chalk11.default.gray(" \u2014 cloud team policy enforced"));
10080
10592
  } else if (creds && !settings.approvers.cloud) {
10081
10593
  console.log(
10082
- import_chalk10.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk10.default.gray(" \u2014 all decisions stay on this machine")
10594
+ import_chalk11.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 all decisions stay on this machine")
10083
10595
  );
10084
10596
  } else {
10085
10597
  console.log(
10086
- import_chalk10.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk10.default.gray(" \u2014 no API key (Local rules only)")
10598
+ import_chalk11.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 no API key (Local rules only)")
10087
10599
  );
10088
10600
  }
10089
10601
  console.log("");
10090
10602
  if (daemonRunning) {
10091
10603
  console.log(
10092
- import_chalk10.default.green(" \u25CF Daemon running") + import_chalk10.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10604
+ import_chalk11.default.green(" \u25CF Daemon running") + import_chalk11.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
10093
10605
  );
10094
10606
  } else {
10095
- console.log(import_chalk10.default.gray(" \u25CB Daemon stopped"));
10607
+ console.log(import_chalk11.default.gray(" \u25CB Daemon stopped"));
10096
10608
  }
10097
10609
  if (settings.enableUndo) {
10098
10610
  console.log(
10099
- import_chalk10.default.magenta(" \u25CF Undo Engine") + import_chalk10.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10611
+ import_chalk11.default.magenta(" \u25CF Undo Engine") + import_chalk11.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
10100
10612
  );
10101
10613
  }
10102
10614
  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");
10615
+ const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
10104
10616
  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");
10617
+ const projectConfig = import_path26.default.join(process.cwd(), "node9.config.json");
10618
+ const globalConfig = import_path26.default.join(import_os20.default.homedir(), ".node9", "config.json");
10107
10619
  console.log(
10108
- ` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
10620
+ ` Local: ${import_fs24.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
10109
10621
  );
10110
10622
  console.log(
10111
- ` Global: ${import_fs22.default.existsSync(globalConfig) ? import_chalk10.default.green("Active (~/.node9/config.json)") : import_chalk10.default.gray("Not present")}`
10623
+ ` Global: ${import_fs24.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
10112
10624
  );
10113
10625
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10114
10626
  console.log(
10115
- ` Sandbox: ${import_chalk10.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10627
+ ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10116
10628
  );
10117
10629
  }
10118
- const homeDir2 = import_os18.default.homedir();
10630
+ const homeDir2 = import_os20.default.homedir();
10119
10631
  const claudeSettings = readJson2(
10120
- import_path24.default.join(homeDir2, ".claude", "settings.json")
10632
+ import_path26.default.join(homeDir2, ".claude", "settings.json")
10121
10633
  );
10122
- const claudeConfig = readJson2(import_path24.default.join(homeDir2, ".claude.json"));
10634
+ const claudeConfig = readJson2(import_path26.default.join(homeDir2, ".claude.json"));
10123
10635
  const geminiSettings = readJson2(
10124
- import_path24.default.join(homeDir2, ".gemini", "settings.json")
10636
+ import_path26.default.join(homeDir2, ".gemini", "settings.json")
10125
10637
  );
10126
- const cursorConfig = readJson2(import_path24.default.join(homeDir2, ".cursor", "mcp.json"));
10638
+ const cursorConfig = readJson2(import_path26.default.join(homeDir2, ".cursor", "mcp.json"));
10127
10639
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10128
10640
  if (agentFound) {
10129
10641
  console.log("");
10130
- console.log(import_chalk10.default.bold(" Agent Wiring:"));
10642
+ console.log(import_chalk11.default.bold(" Agent Wiring:"));
10131
10643
  console.log("");
10132
10644
  if (claudeSettings || claudeConfig) {
10133
10645
  const preHook = claudeSettings?.hooks?.PreToolUse?.some(
@@ -10173,7 +10685,7 @@ function registerStatusCommand(program2) {
10173
10685
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
10174
10686
  console.log("");
10175
10687
  console.log(
10176
- import_chalk10.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk10.default.gray(" \u2014 all tool calls allowed")
10688
+ import_chalk11.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk11.default.gray(" \u2014 all tool calls allowed")
10177
10689
  );
10178
10690
  }
10179
10691
  console.log("");
@@ -10181,10 +10693,10 @@ function registerStatusCommand(program2) {
10181
10693
  }
10182
10694
 
10183
10695
  // 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"));
10696
+ var import_chalk12 = __toESM(require("chalk"));
10697
+ var import_fs25 = __toESM(require("fs"));
10698
+ var import_path27 = __toESM(require("path"));
10699
+ var import_os21 = __toESM(require("os"));
10188
10700
  var import_https2 = __toESM(require("https"));
10189
10701
  init_core();
10190
10702
  init_shields();
@@ -10220,7 +10732,7 @@ function fireTelemetryPing(agents) {
10220
10732
  }
10221
10733
  function registerInitCommand(program2) {
10222
10734
  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"));
10735
+ console.log(import_chalk12.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
10224
10736
  let chosenMode = options.mode.toLowerCase();
10225
10737
  if (!["standard", "strict", "audit"].includes(chosenMode)) {
10226
10738
  chosenMode = DEFAULT_CONFIG.settings.mode;
@@ -10239,37 +10751,37 @@ function registerInitCommand(program2) {
10239
10751
  const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
10240
10752
  if (hasNewShields) writeActiveShields(merged);
10241
10753
  } catch (err2) {
10242
- console.log(import_chalk11.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10754
+ console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
10243
10755
  }
10244
10756
  }
10245
10757
  console.log("");
10246
10758
  }
10247
- const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
10248
- if (import_fs23.default.existsSync(configPath) && !options.force) {
10759
+ const configPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
10760
+ if (import_fs25.default.existsSync(configPath) && !options.force) {
10249
10761
  try {
10250
- const existing = JSON.parse(import_fs23.default.readFileSync(configPath, "utf-8"));
10762
+ const existing = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
10251
10763
  const settings = existing.settings ?? {};
10252
10764
  if (settings.mode !== chosenMode) {
10253
10765
  settings.mode = chosenMode;
10254
10766
  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}`));
10767
+ import_fs25.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10768
+ console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
10257
10769
  } else {
10258
- console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10770
+ console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10259
10771
  }
10260
10772
  } catch {
10261
- console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10773
+ console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
10262
10774
  }
10263
10775
  } else {
10264
10776
  const configToSave = {
10265
10777
  ...DEFAULT_CONFIG,
10266
10778
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10267
10779
  };
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}`));
10780
+ const dir = import_path27.default.dirname(configPath);
10781
+ if (!import_fs25.default.existsSync(dir)) import_fs25.default.mkdirSync(dir, { recursive: true });
10782
+ import_fs25.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10783
+ console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
10784
+ console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
10273
10785
  }
10274
10786
  if (options.skipSetup) return;
10275
10787
  console.log("");
@@ -10279,18 +10791,18 @@ function registerInitCommand(program2) {
10279
10791
  );
10280
10792
  if (found.length === 0) {
10281
10793
  console.log(
10282
- import_chalk11.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
10794
+ import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
10283
10795
  );
10284
- console.log(import_chalk11.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
10796
+ console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
10285
10797
  return;
10286
10798
  }
10287
- console.log(import_chalk11.default.bold("Detected agents:"));
10799
+ console.log(import_chalk12.default.bold("Detected agents:"));
10288
10800
  for (const agent of found) {
10289
- console.log(import_chalk11.default.green(` \u2713 ${agent}`));
10801
+ console.log(import_chalk12.default.green(` \u2713 ${agent}`));
10290
10802
  }
10291
10803
  console.log("");
10292
10804
  for (const agent of found) {
10293
- console.log(import_chalk11.default.bold(`Wiring ${agent}...`));
10805
+ console.log(import_chalk12.default.bold(`Wiring ${agent}...`));
10294
10806
  if (agent === "claude") await setupClaude();
10295
10807
  else if (agent === "gemini") await setupGemini();
10296
10808
  else if (agent === "cursor") await setupCursor();
@@ -10307,26 +10819,26 @@ function registerInitCommand(program2) {
10307
10819
  console.log("");
10308
10820
  }
10309
10821
  const agentList = found.join(", ");
10310
- console.log(import_chalk11.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
10822
+ console.log(import_chalk12.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
10311
10823
  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"));
10824
+ console.log(import_chalk12.default.white(" Watch live: ") + import_chalk12.default.cyan("node9 tail"));
10825
+ console.log(import_chalk12.default.white(" Local UI: ") + import_chalk12.default.cyan("node9 daemon --openui"));
10314
10826
  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"));
10827
+ 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
10828
  console.log(
10317
- import_chalk11.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk11.default.cyan.bold("https://node9.ai")
10829
+ import_chalk12.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk12.default.cyan.bold("https://node9.ai")
10318
10830
  );
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"));
10831
+ 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
10832
  });
10321
10833
  }
10322
10834
 
10323
10835
  // src/cli/commands/undo.ts
10324
- var import_path26 = __toESM(require("path"));
10325
- var import_chalk13 = __toESM(require("chalk"));
10836
+ var import_path28 = __toESM(require("path"));
10837
+ var import_chalk14 = __toESM(require("chalk"));
10326
10838
 
10327
10839
  // src/tui/undo-navigator.ts
10328
10840
  var import_readline2 = __toESM(require("readline"));
10329
- var import_chalk12 = __toESM(require("chalk"));
10841
+ var import_chalk13 = __toESM(require("chalk"));
10330
10842
  var RESET = "\x1B[0m";
10331
10843
  var BOLD = "\x1B[1m";
10332
10844
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
@@ -10344,15 +10856,15 @@ function renderDiff(raw) {
10344
10856
  );
10345
10857
  for (const line of lines) {
10346
10858
  if (line.startsWith("+++") || line.startsWith("---")) {
10347
- process.stdout.write(import_chalk12.default.bold(line) + "\n");
10859
+ process.stdout.write(import_chalk13.default.bold(line) + "\n");
10348
10860
  } else if (line.startsWith("+")) {
10349
- process.stdout.write(import_chalk12.default.green(line) + "\n");
10861
+ process.stdout.write(import_chalk13.default.green(line) + "\n");
10350
10862
  } else if (line.startsWith("-")) {
10351
- process.stdout.write(import_chalk12.default.red(line) + "\n");
10863
+ process.stdout.write(import_chalk13.default.red(line) + "\n");
10352
10864
  } else if (line.startsWith("@@")) {
10353
- process.stdout.write(import_chalk12.default.cyan(line) + "\n");
10865
+ process.stdout.write(import_chalk13.default.cyan(line) + "\n");
10354
10866
  } else {
10355
- process.stdout.write(import_chalk12.default.gray(line) + "\n");
10867
+ process.stdout.write(import_chalk13.default.gray(line) + "\n");
10356
10868
  }
10357
10869
  }
10358
10870
  }
@@ -10371,23 +10883,23 @@ function render(entries, idx) {
10371
10883
  const step = idx + 1;
10372
10884
  process.stdout.write(CLEAR_SCREEN);
10373
10885
  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(
10886
+ 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
10887
  ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
10376
10888
  ) : "") + "\n\n"
10377
10889
  );
10378
10890
  process.stdout.write(
10379
- ` ${BOLD}Tool:${RESET} ${import_chalk12.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk12.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
10891
+ ` ${BOLD}Tool:${RESET} ${import_chalk13.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk13.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
10380
10892
  );
10381
- process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk12.default.gray(formatAge(entry.timestamp))}
10893
+ process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk13.default.gray(formatAge(entry.timestamp))}
10382
10894
  `);
10383
- process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk12.default.gray(entry.cwd)}
10895
+ process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk13.default.gray(entry.cwd)}
10384
10896
  `);
10385
10897
  if (entry.files && entry.files.length > 0) {
10386
- process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk12.default.gray(entry.files.join(", "))}
10898
+ process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk13.default.gray(entry.files.join(", "))}
10387
10899
  `);
10388
10900
  }
10389
10901
  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"));
10902
+ process.stdout.write(import_chalk13.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
10391
10903
  }
10392
10904
  process.stdout.write("\n");
10393
10905
  const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
@@ -10395,12 +10907,12 @@ function render(entries, idx) {
10395
10907
  renderDiff(diff);
10396
10908
  } else {
10397
10909
  process.stdout.write(
10398
- import_chalk12.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
10910
+ import_chalk13.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
10399
10911
  );
10400
10912
  }
10401
10913
  process.stdout.write("\n");
10402
10914
  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"
10915
+ 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
10916
  );
10405
10917
  }
10406
10918
  async function runUndoNavigator(entries) {
@@ -10454,19 +10966,19 @@ async function runUndoNavigator(entries) {
10454
10966
  cleanup();
10455
10967
  process.stdout.write(CLEAR_SCREEN);
10456
10968
  const entry = display[idx];
10457
- process.stdout.write(import_chalk12.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
10969
+ process.stdout.write(import_chalk13.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
10458
10970
  if (applyUndo(entry.hash, entry.cwd)) {
10459
- process.stdout.write(import_chalk12.default.green("\u2705 Reverted successfully.\n\n"));
10971
+ process.stdout.write(import_chalk13.default.green("\u2705 Reverted successfully.\n\n"));
10460
10972
  resolve({ restored: true });
10461
10973
  } else {
10462
- process.stdout.write(import_chalk12.default.red("\u274C Undo failed.\n\n"));
10974
+ process.stdout.write(import_chalk13.default.red("\u274C Undo failed.\n\n"));
10463
10975
  resolve({ restored: false });
10464
10976
  }
10465
10977
  } else if (name === "q" || key?.ctrl && name === "c") {
10466
10978
  done = true;
10467
10979
  cleanup();
10468
10980
  process.stdout.write(CLEAR_SCREEN);
10469
- process.stdout.write(import_chalk12.default.gray("\nCancelled.\n\n"));
10981
+ process.stdout.write(import_chalk13.default.gray("\nCancelled.\n\n"));
10470
10982
  resolve({ restored: false });
10471
10983
  }
10472
10984
  };
@@ -10480,7 +10992,7 @@ function findMatchingCwd(startDir, history) {
10480
10992
  let dir = startDir;
10481
10993
  while (true) {
10482
10994
  if (cwds.has(dir)) return dir;
10483
- const parent = import_path26.default.dirname(dir);
10995
+ const parent = import_path28.default.dirname(dir);
10484
10996
  if (parent === dir) return null;
10485
10997
  dir = parent;
10486
10998
  }
@@ -10502,39 +11014,39 @@ function registerUndoCommand(program2) {
10502
11014
  if (history.length === 0) {
10503
11015
  if (!options.all && allHistory.length > 0) {
10504
11016
  console.log(
10505
- import_chalk13.default.yellow(
11017
+ import_chalk14.default.yellow(
10506
11018
  `
10507
11019
  \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.
11020
+ Run ${import_chalk14.default.cyan("node9 undo --all")} to see snapshots from all projects.
10509
11021
  `
10510
11022
  )
10511
11023
  );
10512
11024
  } else {
10513
- console.log(import_chalk13.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
11025
+ console.log(import_chalk14.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
10514
11026
  }
10515
11027
  return;
10516
11028
  }
10517
11029
  if (options.list) {
10518
- console.log(import_chalk13.default.magenta.bold("\n\u23EA Snapshot History\n"));
11030
+ console.log(import_chalk14.default.magenta.bold("\n\u23EA Snapshot History\n"));
10519
11031
  console.log(
10520
- import_chalk13.default.gray(
11032
+ import_chalk14.default.gray(
10521
11033
  ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
10522
11034
  )
10523
11035
  );
10524
- console.log(import_chalk13.default.gray(" " + "\u2500".repeat(80)));
11036
+ console.log(import_chalk14.default.gray(" " + "\u2500".repeat(80)));
10525
11037
  const display = [...history].reverse();
10526
11038
  let prevTs = null;
10527
11039
  for (let i = 0; i < display.length; i++) {
10528
11040
  const e = display[i];
10529
11041
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
10530
- if (isGap) console.log(import_chalk13.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
11042
+ if (isGap) console.log(import_chalk14.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
10531
11043
  const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
10532
11044
  const tool = e.tool.slice(0, 8).padEnd(8);
10533
11045
  const when = formatAge2(e.timestamp).padEnd(10);
10534
11046
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
10535
11047
  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)}`
11048
+ import_chalk14.default.white(
11049
+ ` ${String(i + 1).padEnd(3)} ${label} ${import_chalk14.default.cyan(tool)} ${import_chalk14.default.gray(when)} ${import_chalk14.default.gray(dir)}`
10538
11050
  )
10539
11051
  );
10540
11052
  prevTs = e.timestamp;
@@ -10547,7 +11059,7 @@ function registerUndoCommand(program2) {
10547
11059
  const idx = history.length - steps;
10548
11060
  if (idx < 0) {
10549
11061
  console.log(
10550
- import_chalk13.default.yellow(
11062
+ import_chalk14.default.yellow(
10551
11063
  `
10552
11064
  \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10553
11065
  `
@@ -10558,47 +11070,47 @@ function registerUndoCommand(program2) {
10558
11070
  const snapshot = history[idx];
10559
11071
  const ageStr = formatAge2(snapshot.timestamp);
10560
11072
  console.log(
10561
- import_chalk13.default.magenta.bold(`
11073
+ import_chalk14.default.magenta.bold(`
10562
11074
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
10563
11075
  );
10564
11076
  console.log(
10565
- import_chalk13.default.white(
10566
- ` Tool: ${import_chalk13.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk13.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
11077
+ import_chalk14.default.white(
11078
+ ` Tool: ${import_chalk14.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk14.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
10567
11079
  )
10568
11080
  );
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)}`));
11081
+ console.log(import_chalk14.default.white(` When: ${import_chalk14.default.gray(ageStr)}`));
11082
+ console.log(import_chalk14.default.white(` Dir: ${import_chalk14.default.gray(snapshot.cwd)}`));
10571
11083
  if (steps > 1)
10572
11084
  console.log(
10573
- import_chalk13.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
11085
+ import_chalk14.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
10574
11086
  );
10575
11087
  console.log("");
10576
11088
  const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10577
11089
  if (diff) {
10578
11090
  const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10579
11091
  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));
11092
+ if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk14.default.bold(line));
11093
+ else if (line.startsWith("+")) console.log(import_chalk14.default.green(line));
11094
+ else if (line.startsWith("-")) console.log(import_chalk14.default.red(line));
11095
+ else if (line.startsWith("@@")) console.log(import_chalk14.default.cyan(line));
11096
+ else console.log(import_chalk14.default.gray(line));
10585
11097
  }
10586
11098
  console.log("");
10587
11099
  } else {
10588
11100
  console.log(
10589
- import_chalk13.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
11101
+ import_chalk14.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
10590
11102
  );
10591
11103
  }
10592
11104
  const { confirm: confirm3 } = await import("@inquirer/prompts");
10593
11105
  const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10594
11106
  if (proceed) {
10595
11107
  if (applyUndo(snapshot.hash, snapshot.cwd)) {
10596
- console.log(import_chalk13.default.green("\n\u2705 Reverted successfully.\n"));
11108
+ console.log(import_chalk14.default.green("\n\u2705 Reverted successfully.\n"));
10597
11109
  } else {
10598
- console.error(import_chalk13.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
11110
+ console.error(import_chalk14.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
10599
11111
  }
10600
11112
  } else {
10601
- console.log(import_chalk13.default.gray("\nCancelled.\n"));
11113
+ console.log(import_chalk14.default.gray("\nCancelled.\n"));
10602
11114
  }
10603
11115
  return;
10604
11116
  }
@@ -10607,7 +11119,7 @@ function registerUndoCommand(program2) {
10607
11119
  }
10608
11120
 
10609
11121
  // src/cli/commands/watch.ts
10610
- var import_chalk14 = __toESM(require("chalk"));
11122
+ var import_chalk15 = __toESM(require("chalk"));
10611
11123
  var import_child_process12 = require("child_process");
10612
11124
  init_daemon();
10613
11125
  function registerWatchCommand(program2) {
@@ -10624,7 +11136,7 @@ function registerWatchCommand(program2) {
10624
11136
  throw new Error("not running");
10625
11137
  }
10626
11138
  } catch {
10627
- console.error(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
11139
+ console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
10628
11140
  const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10629
11141
  detached: true,
10630
11142
  stdio: "ignore",
@@ -10646,12 +11158,12 @@ function registerWatchCommand(program2) {
10646
11158
  }
10647
11159
  }
10648
11160
  if (!ready) {
10649
- console.error(import_chalk14.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
11161
+ console.error(import_chalk15.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
10650
11162
  process.exit(1);
10651
11163
  }
10652
11164
  }
10653
11165
  console.error(
10654
- import_chalk14.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk14.default.dim(` \u2192 localhost:${port}`) + import_chalk14.default.dim(
11166
+ import_chalk15.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk15.default.dim(` \u2192 localhost:${port}`) + import_chalk15.default.dim(
10655
11167
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
10656
11168
  )
10657
11169
  );
@@ -10660,7 +11172,7 @@ function registerWatchCommand(program2) {
10660
11172
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
10661
11173
  });
10662
11174
  if (result.error) {
10663
- console.error(import_chalk14.default.red(`\u274C Failed to run command: ${result.error.message}`));
11175
+ console.error(import_chalk15.default.red(`\u274C Failed to run command: ${result.error.message}`));
10664
11176
  process.exit(1);
10665
11177
  }
10666
11178
  process.exit(result.status ?? 0);
@@ -10669,19 +11181,19 @@ function registerWatchCommand(program2) {
10669
11181
 
10670
11182
  // src/mcp-gateway/index.ts
10671
11183
  var import_readline3 = __toESM(require("readline"));
10672
- var import_chalk15 = __toESM(require("chalk"));
11184
+ var import_chalk16 = __toESM(require("chalk"));
10673
11185
  var import_child_process13 = require("child_process");
10674
11186
  var import_execa3 = require("execa");
10675
11187
  init_orchestrator();
10676
11188
  init_provenance();
10677
11189
 
10678
11190
  // 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"));
11191
+ var import_fs26 = __toESM(require("fs"));
11192
+ var import_path29 = __toESM(require("path"));
11193
+ var import_os22 = __toESM(require("os"));
11194
+ var import_crypto9 = __toESM(require("crypto"));
10683
11195
  function getPinsFilePath() {
10684
- return import_path27.default.join(import_os20.default.homedir(), ".node9", "mcp-pins.json");
11196
+ return import_path29.default.join(import_os22.default.homedir(), ".node9", "mcp-pins.json");
10685
11197
  }
10686
11198
  function hashToolDefinitions(tools) {
10687
11199
  const sorted = [...tools].sort((a, b) => {
@@ -10690,15 +11202,15 @@ function hashToolDefinitions(tools) {
10690
11202
  return nameA.localeCompare(nameB);
10691
11203
  });
10692
11204
  const canonical = JSON.stringify(sorted);
10693
- return import_crypto8.default.createHash("sha256").update(canonical).digest("hex");
11205
+ return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
10694
11206
  }
10695
11207
  function getServerKey(upstreamCommand) {
10696
- return import_crypto8.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
11208
+ return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
10697
11209
  }
10698
11210
  function readMcpPinsSafe() {
10699
11211
  const filePath = getPinsFilePath();
10700
11212
  try {
10701
- const raw = import_fs24.default.readFileSync(filePath, "utf-8");
11213
+ const raw = import_fs26.default.readFileSync(filePath, "utf-8");
10702
11214
  if (!raw.trim()) {
10703
11215
  return { ok: false, reason: "corrupt", detail: "empty file" };
10704
11216
  }
@@ -10722,10 +11234,10 @@ function readMcpPins() {
10722
11234
  }
10723
11235
  function writeMcpPins(data) {
10724
11236
  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);
11237
+ import_fs26.default.mkdirSync(import_path29.default.dirname(filePath), { recursive: true });
11238
+ const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
11239
+ import_fs26.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11240
+ import_fs26.default.renameSync(tmp, filePath);
10729
11241
  }
10730
11242
  function checkPin(serverKey, currentHash) {
10731
11243
  const result = readMcpPinsSafe();
@@ -10817,13 +11329,13 @@ async function runMcpGateway(upstreamCommand) {
10817
11329
  const prov = checkProvenance(executable);
10818
11330
  if (prov.trustLevel === "suspect") {
10819
11331
  console.error(
10820
- import_chalk15.default.red(
11332
+ import_chalk16.default.red(
10821
11333
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
10822
11334
  )
10823
11335
  );
10824
- console.error(import_chalk15.default.red(" Verify this binary is trusted before proceeding."));
11336
+ console.error(import_chalk16.default.red(" Verify this binary is trusted before proceeding."));
10825
11337
  }
10826
- console.error(import_chalk15.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
11338
+ console.error(import_chalk16.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
10827
11339
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
10828
11340
  "NODE_OPTIONS",
10829
11341
  "NODE_PATH",
@@ -10926,10 +11438,10 @@ async function runMcpGateway(upstreamCommand) {
10926
11438
  mcpServer
10927
11439
  });
10928
11440
  if (!result.approved) {
10929
- console.error(import_chalk15.default.red(`
11441
+ console.error(import_chalk16.default.red(`
10930
11442
  \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"}
11443
+ console.error(import_chalk16.default.gray(` Tool: ${toolName}`));
11444
+ console.error(import_chalk16.default.gray(` Reason: ${result.reason ?? "Security Policy"}
10933
11445
  `));
10934
11446
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
10935
11447
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -11008,7 +11520,7 @@ async function runMcpGateway(upstreamCommand) {
11008
11520
  updatePin(serverKey, upstreamCommand, currentHash, toolNames);
11009
11521
  pinState = "validated";
11010
11522
  console.error(
11011
- import_chalk15.default.green(
11523
+ import_chalk16.default.green(
11012
11524
  `\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
11013
11525
  )
11014
11526
  );
@@ -11021,11 +11533,11 @@ async function runMcpGateway(upstreamCommand) {
11021
11533
  } else if (pinStatus === "corrupt") {
11022
11534
  pinState = "quarantined";
11023
11535
  console.error(
11024
- import_chalk15.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11536
+ import_chalk16.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
11025
11537
  );
11026
- console.error(import_chalk15.default.red(" Tool calls are blocked until the pin file is repaired."));
11538
+ console.error(import_chalk16.default.red(" Tool calls are blocked until the pin file is repaired."));
11027
11539
  console.error(
11028
- import_chalk15.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11540
+ import_chalk16.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
11029
11541
  `)
11030
11542
  );
11031
11543
  const errorResponse = {
@@ -11042,13 +11554,13 @@ async function runMcpGateway(upstreamCommand) {
11042
11554
  } else {
11043
11555
  pinState = "quarantined";
11044
11556
  console.error(
11045
- import_chalk15.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11557
+ import_chalk16.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
11046
11558
  );
11047
11559
  console.error(
11048
- import_chalk15.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11560
+ import_chalk16.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
11049
11561
  );
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}
11562
+ console.error(import_chalk16.default.red(" Session quarantined \u2014 all tool calls blocked."));
11563
+ console.error(import_chalk16.default.yellow(` Run: node9 mcp pin update ${serverKey}
11052
11564
  `));
11053
11565
  const errorResponse = {
11054
11566
  jsonrpc: "2.0",
@@ -11097,9 +11609,9 @@ function registerMcpGatewayCommand(program2) {
11097
11609
 
11098
11610
  // src/mcp-server/index.ts
11099
11611
  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"));
11612
+ var import_fs27 = __toESM(require("fs"));
11613
+ var import_os23 = __toESM(require("os"));
11614
+ var import_path30 = __toESM(require("path"));
11103
11615
  init_core();
11104
11616
  init_daemon();
11105
11617
  init_shields();
@@ -11274,13 +11786,13 @@ function handleStatus() {
11274
11786
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11275
11787
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11276
11788
  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");
11789
+ const projectConfig = import_path30.default.join(process.cwd(), "node9.config.json");
11790
+ const globalConfig = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
11279
11791
  lines.push(
11280
- `Project config (node9.config.json): ${import_fs25.default.existsSync(projectConfig) ? "present" : "not found"}`
11792
+ `Project config (node9.config.json): ${import_fs27.default.existsSync(projectConfig) ? "present" : "not found"}`
11281
11793
  );
11282
11794
  lines.push(
11283
- `Global config (~/.node9/config.json): ${import_fs25.default.existsSync(globalConfig) ? "present" : "not found"}`
11795
+ `Global config (~/.node9/config.json): ${import_fs27.default.existsSync(globalConfig) ? "present" : "not found"}`
11284
11796
  );
11285
11797
  return lines.join("\n");
11286
11798
  }
@@ -11354,21 +11866,21 @@ function handleShieldDisable(args) {
11354
11866
  writeActiveShields(active.filter((s) => s !== name));
11355
11867
  return `Shield "${name}" disabled.`;
11356
11868
  }
11357
- var GLOBAL_CONFIG_PATH2 = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
11869
+ var GLOBAL_CONFIG_PATH2 = import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json");
11358
11870
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11359
11871
  function readGlobalConfigRaw() {
11360
11872
  try {
11361
- if (import_fs25.default.existsSync(GLOBAL_CONFIG_PATH2)) {
11362
- return JSON.parse(import_fs25.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11873
+ if (import_fs27.default.existsSync(GLOBAL_CONFIG_PATH2)) {
11874
+ return JSON.parse(import_fs27.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
11363
11875
  }
11364
11876
  } catch {
11365
11877
  }
11366
11878
  return {};
11367
11879
  }
11368
11880
  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");
11881
+ const dir = import_path30.default.dirname(GLOBAL_CONFIG_PATH2);
11882
+ if (!import_fs27.default.existsSync(dir)) import_fs27.default.mkdirSync(dir, { recursive: true });
11883
+ import_fs27.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
11372
11884
  }
11373
11885
  function handleApproverList() {
11374
11886
  const config = getConfig();
@@ -11411,9 +11923,9 @@ function handleApproverSet(args) {
11411
11923
  }
11412
11924
  function handleAuditGet(args) {
11413
11925
  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);
11926
+ const auditPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "audit.log");
11927
+ if (!import_fs27.default.existsSync(auditPath)) return "No audit log found.";
11928
+ const lines = import_fs27.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
11417
11929
  const recent = lines.slice(-limit);
11418
11930
  const entries = recent.map((line) => {
11419
11931
  try {
@@ -11600,7 +12112,7 @@ function registerMcpServerCommand(program2) {
11600
12112
  }
11601
12113
 
11602
12114
  // src/cli/commands/trust.ts
11603
- var import_chalk16 = __toESM(require("chalk"));
12115
+ var import_chalk17 = __toESM(require("chalk"));
11604
12116
  init_trusted_hosts();
11605
12117
  function isValidHost(host) {
11606
12118
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
@@ -11611,51 +12123,51 @@ function registerTrustCommand(program2) {
11611
12123
  const normalized = normalizeHost(host.trim());
11612
12124
  if (!isValidHost(normalized)) {
11613
12125
  console.error(
11614
- import_chalk16.default.red(`
12126
+ import_chalk17.default.red(`
11615
12127
  \u274C Invalid host: "${host}"
11616
- `) + import_chalk16.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
12128
+ `) + import_chalk17.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
11617
12129
  );
11618
12130
  process.exit(1);
11619
12131
  }
11620
12132
  addTrustedHost(normalized);
11621
- console.log(import_chalk16.default.green(`
12133
+ console.log(import_chalk17.default.green(`
11622
12134
  \u2705 ${normalized} added to trusted hosts.`));
11623
12135
  console.log(
11624
- import_chalk16.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
12136
+ import_chalk17.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
11625
12137
  );
11626
12138
  });
11627
12139
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
11628
12140
  const normalized = normalizeHost(host.trim());
11629
12141
  const removed = removeTrustedHost(normalized);
11630
12142
  if (!removed) {
11631
- console.error(import_chalk16.default.yellow(`
12143
+ console.error(import_chalk17.default.yellow(`
11632
12144
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
11633
12145
  `));
11634
12146
  process.exit(1);
11635
12147
  }
11636
- console.log(import_chalk16.default.green(`
12148
+ console.log(import_chalk17.default.green(`
11637
12149
  \u2705 ${normalized} removed from trusted hosts.
11638
12150
  `));
11639
12151
  });
11640
12152
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
11641
12153
  const hosts = readTrustedHosts();
11642
12154
  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")}
12155
+ console.log(import_chalk17.default.gray("\n No trusted hosts configured.\n"));
12156
+ console.log(` Add one: ${import_chalk17.default.cyan("node9 trust add api.mycompany.com")}
11645
12157
  `);
11646
12158
  return;
11647
12159
  }
11648
- console.log(import_chalk16.default.bold("\n\u{1F513} Trusted Hosts\n"));
12160
+ console.log(import_chalk17.default.bold("\n\u{1F513} Trusted Hosts\n"));
11649
12161
  for (const entry of hosts) {
11650
12162
  const date = new Date(entry.addedAt).toLocaleDateString();
11651
- console.log(` ${import_chalk16.default.cyan(entry.host.padEnd(40))} ${import_chalk16.default.gray(`added ${date}`)}`);
12163
+ console.log(` ${import_chalk17.default.cyan(entry.host.padEnd(40))} ${import_chalk17.default.gray(`added ${date}`)}`);
11652
12164
  }
11653
12165
  console.log("");
11654
12166
  });
11655
12167
  }
11656
12168
 
11657
12169
  // src/cli/commands/mcp-pin.ts
11658
- var import_chalk17 = __toESM(require("chalk"));
12170
+ var import_chalk18 = __toESM(require("chalk"));
11659
12171
  function registerMcpPinCommand(program2) {
11660
12172
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
11661
12173
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -11663,31 +12175,31 @@ function registerMcpPinCommand(program2) {
11663
12175
  const result = readMcpPinsSafe();
11664
12176
  if (!result.ok) {
11665
12177
  if (result.reason === "missing") {
11666
- console.log(import_chalk17.default.gray("\nNo MCP servers are pinned yet."));
12178
+ console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
11667
12179
  console.log(
11668
- import_chalk17.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
12180
+ import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
11669
12181
  );
11670
12182
  return;
11671
12183
  }
11672
- console.error(import_chalk17.default.red(`
12184
+ console.error(import_chalk18.default.red(`
11673
12185
  \u274C Pin file is corrupt: ${result.detail}`));
11674
- console.error(import_chalk17.default.yellow(" Run: node9 mcp pin reset\n"));
12186
+ console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
11675
12187
  process.exit(1);
11676
12188
  }
11677
12189
  const entries = Object.entries(result.pins.servers);
11678
12190
  if (entries.length === 0) {
11679
- console.log(import_chalk17.default.gray("\nNo MCP servers are pinned yet."));
12191
+ console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
11680
12192
  console.log(
11681
- import_chalk17.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
12193
+ import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
11682
12194
  );
11683
12195
  return;
11684
12196
  }
11685
- console.log(import_chalk17.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
12197
+ console.log(import_chalk18.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
11686
12198
  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)}`);
12199
+ console.log(` ${import_chalk18.default.cyan(key)} ${import_chalk18.default.gray(entry.label)}`);
12200
+ console.log(` Tools (${entry.toolCount}): ${import_chalk18.default.white(entry.toolNames.join(", "))}`);
12201
+ console.log(` Hash: ${import_chalk18.default.gray(entry.toolsHash.slice(0, 16))}...`);
12202
+ console.log(` Pinned: ${import_chalk18.default.gray(entry.pinnedAt)}`);
11691
12203
  console.log("");
11692
12204
  }
11693
12205
  });
@@ -11698,55 +12210,55 @@ function registerMcpPinCommand(program2) {
11698
12210
  try {
11699
12211
  pins = readMcpPins();
11700
12212
  } 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"));
12213
+ console.error(import_chalk18.default.red("\n\u274C Pin file is corrupt."));
12214
+ console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
11703
12215
  process.exit(1);
11704
12216
  }
11705
12217
  if (!pins.servers[serverKey]) {
11706
- console.error(import_chalk17.default.red(`
12218
+ console.error(import_chalk18.default.red(`
11707
12219
  \u274C No pin found for server key "${serverKey}"
11708
12220
  `));
11709
- console.error(`Run ${import_chalk17.default.cyan("node9 mcp pin list")} to see pinned servers.
12221
+ console.error(`Run ${import_chalk18.default.cyan("node9 mcp pin list")} to see pinned servers.
11710
12222
  `);
11711
12223
  process.exit(1);
11712
12224
  }
11713
12225
  const label = pins.servers[serverKey].label;
11714
12226
  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"));
12227
+ console.log(import_chalk18.default.green(`
12228
+ \u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
12229
+ console.log(import_chalk18.default.gray(` Server: ${label}`));
12230
+ console.log(import_chalk18.default.gray(" Next connection will re-pin with current tool definitions.\n"));
11719
12231
  });
11720
12232
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
11721
12233
  const result = readMcpPinsSafe();
11722
12234
  if (!result.ok && result.reason === "missing") {
11723
- console.log(import_chalk17.default.gray("\nNo pins to clear.\n"));
12235
+ console.log(import_chalk18.default.gray("\nNo pins to clear.\n"));
11724
12236
  return;
11725
12237
  }
11726
12238
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
11727
12239
  clearAllPins();
11728
- console.log(import_chalk17.default.green(`
12240
+ console.log(import_chalk18.default.green(`
11729
12241
  \u{1F513} Cleared ${count} MCP pin(s).`));
11730
- console.log(import_chalk17.default.gray(" Next connection to each server will re-pin.\n"));
12242
+ console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
11731
12243
  });
11732
12244
  }
11733
12245
 
11734
12246
  // src/cli.ts
11735
12247
  var { version } = JSON.parse(
11736
- import_fs28.default.readFileSync(import_path31.default.join(__dirname, "../package.json"), "utf-8")
12248
+ import_fs30.default.readFileSync(import_path33.default.join(__dirname, "../package.json"), "utf-8")
11737
12249
  );
11738
12250
  var program = new import_commander.Command();
11739
12251
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
11740
12252
  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
12253
  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 });
12254
+ const credPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "credentials.json");
12255
+ if (!import_fs30.default.existsSync(import_path33.default.dirname(credPath)))
12256
+ import_fs30.default.mkdirSync(import_path33.default.dirname(credPath), { recursive: true });
11745
12257
  const profileName = options.profile || "default";
11746
12258
  let existingCreds = {};
11747
12259
  try {
11748
- if (import_fs28.default.existsSync(credPath)) {
11749
- const raw = JSON.parse(import_fs28.default.readFileSync(credPath, "utf-8"));
12260
+ if (import_fs30.default.existsSync(credPath)) {
12261
+ const raw = JSON.parse(import_fs30.default.readFileSync(credPath, "utf-8"));
11750
12262
  if (raw.apiKey) {
11751
12263
  existingCreds = {
11752
12264
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -11758,13 +12270,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11758
12270
  } catch {
11759
12271
  }
11760
12272
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
11761
- import_fs28.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12273
+ import_fs30.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
11762
12274
  if (profileName === "default") {
11763
- const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
12275
+ const configPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
11764
12276
  let config = {};
11765
12277
  try {
11766
- if (import_fs28.default.existsSync(configPath))
11767
- config = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
12278
+ if (import_fs30.default.existsSync(configPath))
12279
+ config = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
11768
12280
  } catch {
11769
12281
  }
11770
12282
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -11779,19 +12291,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11779
12291
  approvers.cloud = false;
11780
12292
  }
11781
12293
  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 });
12294
+ if (!import_fs30.default.existsSync(import_path33.default.dirname(configPath)))
12295
+ import_fs30.default.mkdirSync(import_path33.default.dirname(configPath), { recursive: true });
12296
+ import_fs30.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
11785
12297
  }
11786
12298
  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`));
12299
+ console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
12300
+ console.log(import_chalk20.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
11789
12301
  } 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.`));
12302
+ console.log(import_chalk20.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
12303
+ console.log(import_chalk20.default.gray(` All decisions stay on this machine.`));
11792
12304
  } 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.`));
12305
+ console.log(import_chalk20.default.green(`\u2705 Logged in \u2014 agent mode`));
12306
+ console.log(import_chalk20.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
11795
12307
  }
11796
12308
  });
11797
12309
  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 +12311,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
11799
12311
  if (target === "claude") return await setupClaude();
11800
12312
  if (target === "cursor") return await setupCursor();
11801
12313
  if (target === "hud") return setupHud();
11802
- console.error(import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12314
+ console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11803
12315
  process.exit(1);
11804
12316
  });
11805
12317
  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
12318
  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");
12319
+ console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
12320
+ console.log(" Usage: " + import_chalk20.default.white("node9 setup <target>") + "\n");
11809
12321
  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)");
12322
+ console.log(" " + import_chalk20.default.green("claude") + " \u2014 Claude Code (hook mode)");
12323
+ console.log(" " + import_chalk20.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
12324
+ console.log(" " + import_chalk20.default.green("cursor") + " \u2014 Cursor (hook mode)");
11813
12325
  process.stdout.write(
11814
- " " + import_chalk19.default.green("hud") + " \u2014 Claude Code security statusline\n"
12326
+ " " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
11815
12327
  );
11816
12328
  console.log("");
11817
12329
  return;
@@ -11821,7 +12333,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
11821
12333
  if (t === "claude") return await setupClaude();
11822
12334
  if (t === "cursor") return await setupCursor();
11823
12335
  if (t === "hud") return setupHud();
11824
- console.error(import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
12336
+ console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
11825
12337
  process.exit(1);
11826
12338
  });
11827
12339
  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 +12344,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
11832
12344
  else if (target === "hud") fn = teardownHud;
11833
12345
  else {
11834
12346
  console.error(
11835
- import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
12347
+ import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
11836
12348
  );
11837
12349
  process.exit(1);
11838
12350
  }
11839
- console.log(import_chalk19.default.cyan(`
12351
+ console.log(import_chalk20.default.cyan(`
11840
12352
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
11841
12353
  `));
11842
12354
  try {
11843
12355
  fn();
11844
12356
  } catch (err2) {
11845
- console.error(import_chalk19.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
12357
+ console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
11846
12358
  process.exit(1);
11847
12359
  }
11848
- console.log(import_chalk19.default.gray("\n Restart the agent for changes to take effect."));
12360
+ console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
11849
12361
  });
11850
12362
  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..."));
12363
+ console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
12364
+ console.log(import_chalk20.default.bold("Stopping daemon..."));
11853
12365
  try {
11854
12366
  stopDaemon();
11855
- console.log(import_chalk19.default.green(" \u2705 Daemon stopped"));
12367
+ console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
11856
12368
  } catch {
11857
- console.log(import_chalk19.default.blue(" \u2139\uFE0F Daemon was not running"));
12369
+ console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
11858
12370
  }
11859
- console.log(import_chalk19.default.bold("\nRemoving hooks..."));
12371
+ console.log(import_chalk20.default.bold("\nRemoving hooks..."));
11860
12372
  let teardownFailed = false;
11861
12373
  for (const [label, fn] of [
11862
12374
  ["Claude", teardownClaude],
@@ -11868,45 +12380,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
11868
12380
  } catch (err2) {
11869
12381
  teardownFailed = true;
11870
12382
  console.error(
11871
- import_chalk19.default.red(
12383
+ import_chalk20.default.red(
11872
12384
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
11873
12385
  )
11874
12386
  );
11875
12387
  }
11876
12388
  }
11877
12389
  if (options.purge) {
11878
- const node9Dir = import_path31.default.join(import_os24.default.homedir(), ".node9");
11879
- if (import_fs28.default.existsSync(node9Dir)) {
12390
+ const node9Dir = import_path33.default.join(import_os26.default.homedir(), ".node9");
12391
+ if (import_fs30.default.existsSync(node9Dir)) {
11880
12392
  const confirmed = await (0, import_prompts2.confirm)({
11881
12393
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
11882
12394
  default: false
11883
12395
  });
11884
12396
  if (confirmed) {
11885
- import_fs28.default.rmSync(node9Dir, { recursive: true });
11886
- if (import_fs28.default.existsSync(node9Dir)) {
12397
+ import_fs30.default.rmSync(node9Dir, { recursive: true });
12398
+ if (import_fs30.default.existsSync(node9Dir)) {
11887
12399
  console.error(
11888
- import_chalk19.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12400
+ import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
11889
12401
  );
11890
12402
  } else {
11891
- console.log(import_chalk19.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
12403
+ console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
11892
12404
  }
11893
12405
  } else {
11894
- console.log(import_chalk19.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12406
+ console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
11895
12407
  }
11896
12408
  } else {
11897
- console.log(import_chalk19.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
12409
+ console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
11898
12410
  }
11899
12411
  } else {
11900
12412
  console.log(
11901
- import_chalk19.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
12413
+ import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
11902
12414
  );
11903
12415
  }
11904
12416
  if (teardownFailed) {
11905
- console.error(import_chalk19.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
12417
+ console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
11906
12418
  process.exit(1);
11907
12419
  }
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"));
12420
+ console.log(import_chalk20.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
12421
+ console.log(import_chalk20.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
11910
12422
  });
11911
12423
  registerDoctorCommand(program, version);
11912
12424
  program.command("explain").description(
@@ -11919,7 +12431,7 @@ program.command("explain").description(
11919
12431
  try {
11920
12432
  args = JSON.parse(trimmed);
11921
12433
  } catch {
11922
- console.error(import_chalk19.default.red(`
12434
+ console.error(import_chalk20.default.red(`
11923
12435
  \u274C Invalid JSON: ${trimmed}
11924
12436
  `));
11925
12437
  process.exit(1);
@@ -11930,60 +12442,61 @@ program.command("explain").description(
11930
12442
  }
11931
12443
  const result = await explainPolicy(tool, args);
11932
12444
  console.log("");
11933
- console.log(import_chalk19.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12445
+ console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
11934
12446
  console.log("");
11935
- console.log(` ${import_chalk19.default.bold("Tool:")} ${import_chalk19.default.white(result.tool)}`);
12447
+ console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
11936
12448
  if (argsRaw) {
11937
12449
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
11938
- console.log(` ${import_chalk19.default.bold("Input:")} ${import_chalk19.default.gray(preview)}`);
12450
+ console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
11939
12451
  }
11940
12452
  console.log("");
11941
- console.log(import_chalk19.default.bold("Config Sources (Waterfall):"));
12453
+ console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
11942
12454
  for (const tier of result.waterfall) {
11943
- const num = import_chalk19.default.gray(` ${tier.tier}.`);
12455
+ const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
11944
12456
  const label = tier.label.padEnd(16);
11945
12457
  let statusStr;
11946
12458
  if (tier.tier === 1) {
11947
- statusStr = import_chalk19.default.gray(tier.note ?? "");
12459
+ statusStr = import_chalk20.default.gray(tier.note ?? "");
11948
12460
  } 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 : "");
12461
+ const loc = tier.path ? import_chalk20.default.gray(tier.path) : "";
12462
+ const note = tier.note ? import_chalk20.default.gray(`(${tier.note})`) : "";
12463
+ statusStr = import_chalk20.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
11952
12464
  } else {
11953
- statusStr = import_chalk19.default.gray("\u25CB " + (tier.note ?? "not found"));
12465
+ statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
11954
12466
  }
11955
- console.log(`${num} ${import_chalk19.default.white(label)} ${statusStr}`);
12467
+ console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
11956
12468
  }
11957
12469
  console.log("");
11958
- console.log(import_chalk19.default.bold("Policy Evaluation:"));
12470
+ console.log(import_chalk20.default.bold("Policy Evaluation:"));
11959
12471
  for (const step of result.steps) {
11960
12472
  const isFinal = step.isFinal;
11961
12473
  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 ");
12474
+ if (step.outcome === "allow") icon = import_chalk20.default.green(" \u2705");
12475
+ else if (step.outcome === "review") icon = import_chalk20.default.red(" \u{1F534}");
12476
+ else if (step.outcome === "skip") icon = import_chalk20.default.gray(" \u2500 ");
12477
+ else icon = import_chalk20.default.gray(" \u25CB ");
11966
12478
  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") : "";
12479
+ const nameStr = isFinal ? import_chalk20.default.white.bold(name) : import_chalk20.default.white(name);
12480
+ const detail = isFinal ? import_chalk20.default.white(step.detail) : import_chalk20.default.gray(step.detail);
12481
+ const arrow = isFinal ? import_chalk20.default.yellow(" \u2190 STOP") : "";
11970
12482
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
11971
12483
  }
11972
12484
  console.log("");
11973
12485
  if (result.decision === "allow") {
11974
- console.log(import_chalk19.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk19.default.gray(" \u2014 no approval needed"));
12486
+ console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
11975
12487
  } else {
11976
12488
  console.log(
11977
- import_chalk19.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk19.default.gray(" \u2014 human approval required")
12489
+ import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
11978
12490
  );
11979
12491
  if (result.blockedByLabel) {
11980
- console.log(import_chalk19.default.gray(` Reason: ${result.blockedByLabel}`));
12492
+ console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
11981
12493
  }
11982
12494
  }
11983
12495
  console.log("");
11984
12496
  });
11985
12497
  registerInitCommand(program);
11986
12498
  registerAuditCommand(program);
12499
+ registerReportCommand(program);
11987
12500
  registerStatusCommand(program);
11988
12501
  registerDaemonCommand(program);
11989
12502
  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 +12504,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
11991
12504
  try {
11992
12505
  await startTail2(options);
11993
12506
  } catch (err2) {
11994
- console.error(import_chalk19.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
12507
+ console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
11995
12508
  process.exit(1);
11996
12509
  }
11997
12510
  });
@@ -12023,14 +12536,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12023
12536
  Run "node9 addto claude" to register it as the statusLine.`
12024
12537
  ).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
12538
  if (subcommand === "debug") {
12026
- const flagFile = import_path31.default.join(import_os24.default.homedir(), ".node9", "hud-debug");
12539
+ const flagFile = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug");
12027
12540
  if (state === "on") {
12028
- import_fs28.default.mkdirSync(import_path31.default.dirname(flagFile), { recursive: true });
12029
- import_fs28.default.writeFileSync(flagFile, "");
12541
+ import_fs30.default.mkdirSync(import_path33.default.dirname(flagFile), { recursive: true });
12542
+ import_fs30.default.writeFileSync(flagFile, "");
12030
12543
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12031
12544
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12032
12545
  } else if (state === "off") {
12033
- if (import_fs28.default.existsSync(flagFile)) import_fs28.default.unlinkSync(flagFile);
12546
+ if (import_fs30.default.existsSync(flagFile)) import_fs30.default.unlinkSync(flagFile);
12034
12547
  console.log("HUD debug logging disabled.");
12035
12548
  } else {
12036
12549
  console.error("Usage: node9 hud debug on|off");
@@ -12045,7 +12558,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12045
12558
  const ms = parseDuration(options.duration);
12046
12559
  if (ms === null) {
12047
12560
  console.error(
12048
- import_chalk19.default.red(`
12561
+ import_chalk20.default.red(`
12049
12562
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12050
12563
  `)
12051
12564
  );
@@ -12053,20 +12566,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12053
12566
  }
12054
12567
  pauseNode9(ms, options.duration);
12055
12568
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12056
- console.log(import_chalk19.default.yellow(`
12569
+ console.log(import_chalk20.default.yellow(`
12057
12570
  \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.
12571
+ console.log(import_chalk20.default.gray(` All tool calls will be allowed without review.`));
12572
+ console.log(import_chalk20.default.gray(` Run "node9 resume" to re-enable early.
12060
12573
  `));
12061
12574
  });
12062
12575
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12063
12576
  const { paused } = checkPause();
12064
12577
  if (!paused) {
12065
- console.log(import_chalk19.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12578
+ console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12066
12579
  return;
12067
12580
  }
12068
12581
  resumeNode9();
12069
- console.log(import_chalk19.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12582
+ console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12070
12583
  });
12071
12584
  var HOOK_BASED_AGENTS = {
12072
12585
  claude: "claude",
@@ -12079,15 +12592,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12079
12592
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12080
12593
  const target = HOOK_BASED_AGENTS[firstArg2];
12081
12594
  console.error(
12082
- import_chalk19.default.yellow(`
12595
+ import_chalk20.default.yellow(`
12083
12596
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12084
12597
  );
12085
- console.error(import_chalk19.default.white(`
12598
+ console.error(import_chalk20.default.white(`
12086
12599
  "${target}" uses its own hook system. Use:`));
12087
12600
  console.error(
12088
- import_chalk19.default.green(` node9 addto ${target} `) + import_chalk19.default.gray("# one-time setup")
12601
+ import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
12089
12602
  );
12090
- console.error(import_chalk19.default.green(` ${target} `) + import_chalk19.default.gray("# run normally"));
12603
+ console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
12091
12604
  process.exit(1);
12092
12605
  }
12093
12606
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12104,7 +12617,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12104
12617
  }
12105
12618
  );
12106
12619
  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..."));
12620
+ console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12108
12621
  const daemonReady = await autoStartDaemonAndWait();
12109
12622
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12110
12623
  }
@@ -12117,12 +12630,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12117
12630
  }
12118
12631
  if (!result.approved) {
12119
12632
  console.error(
12120
- import_chalk19.default.red(`
12633
+ import_chalk20.default.red(`
12121
12634
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12122
12635
  );
12123
12636
  process.exit(1);
12124
12637
  }
12125
- console.error(import_chalk19.default.green("\n\u2705 Approved \u2014 running command...\n"));
12638
+ console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
12126
12639
  await runProxy(fullCommand);
12127
12640
  } else {
12128
12641
  program.help();
@@ -12137,9 +12650,9 @@ if (process.argv[2] !== "daemon") {
12137
12650
  const isCheckHook = process.argv[2] === "check";
12138
12651
  if (isCheckHook) {
12139
12652
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12140
- const logPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log");
12653
+ const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hook-debug.log");
12141
12654
  const msg = reason instanceof Error ? reason.message : String(reason);
12142
- import_fs28.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12655
+ import_fs30.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12143
12656
  `);
12144
12657
  }
12145
12658
  process.exit(0);