@node9/proxy 1.9.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.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,
@@ -214,6 +222,7 @@ var init_config_schema = __esm({
214
222
  errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
215
223
  }),
216
224
  reason: import_zod.z.string().optional(),
225
+ description: import_zod.z.string().optional(),
217
226
  // Unknown predicate names are filtered out rather than failing the whole rule.
218
227
  // Failing the whole z.array() would cause sanitizeConfig to drop the entire
219
228
  // `policy` top-level key, silently disabling ALL smart rules in the config.
@@ -260,6 +269,11 @@ var init_config_schema = __esm({
260
269
  dlp: import_zod.z.object({
261
270
  enabled: import_zod.z.boolean().optional(),
262
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()
263
277
  }).optional()
264
278
  }).optional(),
265
279
  environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
@@ -554,7 +568,8 @@ function getConfig(cwd) {
554
568
  onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
555
569
  ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
556
570
  },
557
- dlp: { ...DEFAULT_CONFIG.policy.dlp }
571
+ dlp: { ...DEFAULT_CONFIG.policy.dlp },
572
+ loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
558
573
  };
559
574
  const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
560
575
  const applyLayer = (source) => {
@@ -593,6 +608,13 @@ function getConfig(cwd) {
593
608
  if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
594
609
  if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
595
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
+ }
596
618
  const envs = source.environments || {};
597
619
  for (const [envName, envConfig] of Object.entries(envs)) {
598
620
  if (envConfig && typeof envConfig === "object") {
@@ -791,7 +813,8 @@ var init_config = __esm({
791
813
  }
792
814
  ],
793
815
  verdict: "block",
794
- reason: "Recursive delete of home directory is irreversible"
816
+ reason: "Recursive delete of home directory is irreversible",
817
+ description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
795
818
  },
796
819
  // ── SQL safety ────────────────────────────────────────────────────────
797
820
  {
@@ -803,7 +826,8 @@ var init_config = __esm({
803
826
  ],
804
827
  conditionMode: "all",
805
828
  verdict: "review",
806
- reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
829
+ reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
830
+ description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
807
831
  },
808
832
  {
809
833
  name: "review-drop-truncate-shell",
@@ -818,7 +842,8 @@ var init_config = __esm({
818
842
  ],
819
843
  conditionMode: "all",
820
844
  verdict: "review",
821
- reason: "SQL DDL destructive statement inside a shell command"
845
+ reason: "SQL DDL destructive statement inside a shell command",
846
+ description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
822
847
  },
823
848
  // ── Git safety ────────────────────────────────────────────────────────
824
849
  {
@@ -834,7 +859,8 @@ var init_config = __esm({
834
859
  ],
835
860
  conditionMode: "all",
836
861
  verdict: "block",
837
- reason: "Force push overwrites remote history and cannot be undone"
862
+ reason: "Force push overwrites remote history and cannot be undone",
863
+ description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
838
864
  },
839
865
  {
840
866
  name: "review-git-push",
@@ -849,7 +875,8 @@ var init_config = __esm({
849
875
  ],
850
876
  conditionMode: "all",
851
877
  verdict: "review",
852
- reason: "git push sends changes to a shared remote"
878
+ reason: "git push sends changes to a shared remote",
879
+ description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
853
880
  },
854
881
  {
855
882
  name: "review-git-destructive",
@@ -864,7 +891,8 @@ var init_config = __esm({
864
891
  ],
865
892
  conditionMode: "all",
866
893
  verdict: "review",
867
- reason: "Destructive git operation \u2014 discards history or working-tree changes"
894
+ reason: "Destructive git operation \u2014 discards history or working-tree changes",
895
+ description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
868
896
  },
869
897
  // ── Shell safety ──────────────────────────────────────────────────────
870
898
  {
@@ -873,7 +901,8 @@ var init_config = __esm({
873
901
  conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
874
902
  conditionMode: "all",
875
903
  verdict: "review",
876
- reason: "Command requires elevated privileges"
904
+ reason: "Command requires elevated privileges",
905
+ description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
877
906
  },
878
907
  {
879
908
  name: "review-curl-pipe-shell",
@@ -888,10 +917,12 @@ var init_config = __esm({
888
917
  ],
889
918
  conditionMode: "all",
890
919
  verdict: "block",
891
- reason: "Piping remote script into a shell is a supply-chain attack vector"
920
+ reason: "Piping remote script into a shell is a supply-chain attack vector",
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."
892
922
  }
893
923
  ],
894
- dlp: { enabled: true, scanIgnoredTools: true }
924
+ dlp: { enabled: true, scanIgnoredTools: true },
925
+ loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
895
926
  },
896
927
  environments: {}
897
928
  };
@@ -921,7 +952,8 @@ var init_config = __esm({
921
952
  tool: "*",
922
953
  conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
923
954
  verdict: "review",
924
- reason: "rm can permanently delete files \u2014 confirm the target path"
955
+ reason: "rm can permanently delete files \u2014 confirm the target path",
956
+ description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
925
957
  },
926
958
  // ── SQL safety (Safe by Default) ──────────────────────────────────────────
927
959
  // These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
@@ -933,14 +965,16 @@ var init_config = __esm({
933
965
  tool: "*",
934
966
  conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
935
967
  verdict: "review",
936
- reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
968
+ reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
969
+ description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
937
970
  },
938
971
  {
939
972
  name: "review-truncate-sql",
940
973
  tool: "*",
941
974
  conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
942
975
  verdict: "review",
943
- reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
976
+ reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
977
+ description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
944
978
  },
945
979
  {
946
980
  name: "review-drop-column-sql",
@@ -949,7 +983,8 @@ var init_config = __esm({
949
983
  { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
950
984
  ],
951
985
  verdict: "review",
952
- reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
986
+ reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
987
+ description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
953
988
  }
954
989
  ];
955
990
  cachedConfig = null;
@@ -1691,9 +1726,9 @@ function matchesPattern(text, patterns) {
1691
1726
  const withoutDotSlash = text.replace(/^\.\//, "");
1692
1727
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1693
1728
  }
1694
- function getNestedValue(obj, path32) {
1729
+ function getNestedValue(obj, path34) {
1695
1730
  if (!obj || typeof obj !== "object") return null;
1696
- return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
1731
+ return path34.split(".").reduce((prev, curr) => prev?.[curr], obj);
1697
1732
  }
1698
1733
  function shouldSnapshot(toolName, args, config) {
1699
1734
  if (!config.settings.enableUndo) return false;
@@ -1844,6 +1879,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1844
1879
  reason: matchedRule.reason,
1845
1880
  tier: 2,
1846
1881
  ruleName: matchedRule.name ?? matchedRule.tool,
1882
+ ...(matchedRule.description ?? matchedRule.reason) && {
1883
+ ruleDescription: matchedRule.description ?? matchedRule.reason
1884
+ },
1847
1885
  ...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
1848
1886
  dependsOnStatePredicates: matchedRule.dependsOnState
1849
1887
  },
@@ -3063,6 +3101,58 @@ var init_cloud = __esm({
3063
3101
  }
3064
3102
  });
3065
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
+
3066
3156
  // src/auth/orchestrator.ts
3067
3157
  function isWriteTool(toolName) {
3068
3158
  const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
@@ -3103,7 +3193,7 @@ function notifyActivity(data) {
3103
3193
  }
3104
3194
  async function authorizeHeadless(toolName, args, meta, options) {
3105
3195
  if (!options?.calledFromDaemon) {
3106
- const actId = (0, import_crypto3.randomUUID)();
3196
+ const actId = (0, import_crypto4.randomUUID)();
3107
3197
  const actTs = Date.now();
3108
3198
  await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
3109
3199
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
@@ -3150,6 +3240,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3150
3240
  let explainableLabel = "Local Config";
3151
3241
  let policyMatchedField;
3152
3242
  let policyMatchedWord;
3243
+ let policyRuleDescription;
3153
3244
  let riskMetadata;
3154
3245
  let statefulRecoveryCommand;
3155
3246
  let localSmartRuleMatched = false;
@@ -3243,6 +3334,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3243
3334
  return { approved: true, checkedBy: "audit" };
3244
3335
  }
3245
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
+ }
3246
3352
  if (getActiveTrustSession(toolName)) {
3247
3353
  if (approvers.cloud && creds?.apiKey)
3248
3354
  await auditLocalAllow(toolName, args, "trust", creds, meta);
@@ -3289,7 +3395,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3289
3395
  blockedBy: "local-config",
3290
3396
  blockedByLabel: policyResult.blockedByLabel,
3291
3397
  ruleHit: policyResult.ruleName,
3292
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
3398
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
3399
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
3293
3400
  };
3294
3401
  }
3295
3402
  }
@@ -3297,6 +3404,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3297
3404
  policyMatchedField = policyResult.matchedField;
3298
3405
  policyMatchedWord = policyResult.matchedWord;
3299
3406
  if (policyResult.ruleName) localSmartRuleMatched = true;
3407
+ if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
3408
+ else if (policyResult.reason) policyRuleDescription = policyResult.reason;
3300
3409
  riskMetadata = computeRiskMetadata(
3301
3410
  args,
3302
3411
  policyResult.tier ?? 6,
@@ -3560,13 +3669,14 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
3560
3669
  hashAuditArgs
3561
3670
  );
3562
3671
  }
3563
- return finalResult;
3672
+ const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
3673
+ return enrichedResult;
3564
3674
  }
3565
- var import_crypto3, WRITE_TOOLS;
3675
+ var import_crypto4, WRITE_TOOLS;
3566
3676
  var init_orchestrator = __esm({
3567
3677
  "src/auth/orchestrator.ts"() {
3568
3678
  "use strict";
3569
- import_crypto3 = require("crypto");
3679
+ import_crypto4 = require("crypto");
3570
3680
  init_native();
3571
3681
  init_context_sniper();
3572
3682
  init_dlp();
@@ -3576,6 +3686,7 @@ var init_orchestrator = __esm({
3576
3686
  init_state();
3577
3687
  init_daemon();
3578
3688
  init_cloud();
3689
+ init_loop_detector();
3579
3690
  WRITE_TOOLS = /* @__PURE__ */ new Set([
3580
3691
  "write",
3581
3692
  "write_file",
@@ -5249,11 +5360,11 @@ function commonPathPrefix(paths) {
5249
5360
  const prefix = common.join("/").replace(/\/?$/, "/");
5250
5361
  return prefix.length > 1 ? prefix : null;
5251
5362
  }
5252
- var import_crypto4, SuggestionTracker;
5363
+ var import_crypto5, SuggestionTracker;
5253
5364
  var init_suggestion_tracker = __esm({
5254
5365
  "src/daemon/suggestion-tracker.ts"() {
5255
5366
  "use strict";
5256
- import_crypto4 = require("crypto");
5367
+ import_crypto5 = require("crypto");
5257
5368
  SuggestionTracker = class {
5258
5369
  events = /* @__PURE__ */ new Map();
5259
5370
  threshold;
@@ -5299,7 +5410,7 @@ var init_suggestion_tracker = __esm({
5299
5410
  }
5300
5411
  } : { type: "ignoredTool", toolName };
5301
5412
  return {
5302
- id: (0, import_crypto4.randomUUID)(),
5413
+ id: (0, import_crypto5.randomUUID)(),
5303
5414
  toolName,
5304
5415
  allowCount: events.length,
5305
5416
  suggestedRule,
@@ -5313,12 +5424,12 @@ var init_suggestion_tracker = __esm({
5313
5424
  });
5314
5425
 
5315
5426
  // src/daemon/taint-store.ts
5316
- var import_fs12, import_path15, DEFAULT_TTL_MS, TaintStore;
5427
+ var import_fs13, import_path16, DEFAULT_TTL_MS, TaintStore;
5317
5428
  var init_taint_store = __esm({
5318
5429
  "src/daemon/taint-store.ts"() {
5319
5430
  "use strict";
5320
- import_fs12 = __toESM(require("fs"));
5321
- import_path15 = __toESM(require("path"));
5431
+ import_fs13 = __toESM(require("fs"));
5432
+ import_path16 = __toESM(require("path"));
5322
5433
  DEFAULT_TTL_MS = 60 * 60 * 1e3;
5323
5434
  TaintStore = class {
5324
5435
  records = /* @__PURE__ */ new Map();
@@ -5383,9 +5494,9 @@ var init_taint_store = __esm({
5383
5494
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
5384
5495
  _resolve(filePath) {
5385
5496
  try {
5386
- return import_fs12.default.realpathSync.native(import_path15.default.resolve(filePath));
5497
+ return import_fs13.default.realpathSync.native(import_path16.default.resolve(filePath));
5387
5498
  } catch {
5388
- return import_path15.default.resolve(filePath);
5499
+ return import_path16.default.resolve(filePath);
5389
5500
  }
5390
5501
  }
5391
5502
  };
@@ -5503,8 +5614,8 @@ var init_session_history = __esm({
5503
5614
  // src/daemon/state.ts
5504
5615
  function loadInsightCounts() {
5505
5616
  try {
5506
- if (!import_fs13.default.existsSync(INSIGHT_COUNTS_FILE)) return;
5507
- 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"));
5508
5619
  for (const [tool, count] of Object.entries(data)) {
5509
5620
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
5510
5621
  }
@@ -5543,23 +5654,23 @@ function markRejectionHandlerRegistered() {
5543
5654
  daemonRejectionHandlerRegistered = true;
5544
5655
  }
5545
5656
  function atomicWriteSync2(filePath, data, options) {
5546
- const dir = import_path16.default.dirname(filePath);
5547
- if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
5548
- 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`;
5549
5660
  try {
5550
- import_fs13.default.writeFileSync(tmpPath, data, options);
5661
+ import_fs14.default.writeFileSync(tmpPath, data, options);
5551
5662
  } catch (err2) {
5552
5663
  try {
5553
- import_fs13.default.unlinkSync(tmpPath);
5664
+ import_fs14.default.unlinkSync(tmpPath);
5554
5665
  } catch {
5555
5666
  }
5556
5667
  throw err2;
5557
5668
  }
5558
5669
  try {
5559
- import_fs13.default.renameSync(tmpPath, filePath);
5670
+ import_fs14.default.renameSync(tmpPath, filePath);
5560
5671
  } catch (err2) {
5561
5672
  try {
5562
- import_fs13.default.unlinkSync(tmpPath);
5673
+ import_fs14.default.unlinkSync(tmpPath);
5563
5674
  } catch {
5564
5675
  }
5565
5676
  throw err2;
@@ -5583,16 +5694,16 @@ function appendAuditLog(data) {
5583
5694
  decision: data.decision,
5584
5695
  source: "daemon"
5585
5696
  };
5586
- const dir = import_path16.default.dirname(AUDIT_LOG_FILE);
5587
- if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
5588
- 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");
5589
5700
  } catch {
5590
5701
  }
5591
5702
  }
5592
5703
  function getAuditHistory(limit = 20) {
5593
5704
  try {
5594
- if (!import_fs13.default.existsSync(AUDIT_LOG_FILE)) return [];
5595
- 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");
5596
5707
  if (lines.length === 1 && lines[0] === "") return [];
5597
5708
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
5598
5709
  } catch {
@@ -5601,19 +5712,19 @@ function getAuditHistory(limit = 20) {
5601
5712
  }
5602
5713
  function getOrgName() {
5603
5714
  try {
5604
- if (import_fs13.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5715
+ if (import_fs14.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
5605
5716
  } catch {
5606
5717
  }
5607
5718
  return null;
5608
5719
  }
5609
5720
  function hasStoredSlackKey() {
5610
- return import_fs13.default.existsSync(CREDENTIALS_FILE);
5721
+ return import_fs14.default.existsSync(CREDENTIALS_FILE);
5611
5722
  }
5612
5723
  function writeGlobalSetting(key, value) {
5613
5724
  let config = {};
5614
5725
  try {
5615
- if (import_fs13.default.existsSync(GLOBAL_CONFIG_FILE)) {
5616
- 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"));
5617
5728
  }
5618
5729
  } catch {
5619
5730
  }
@@ -5625,8 +5736,8 @@ function writeTrustEntry(toolName, durationMs) {
5625
5736
  try {
5626
5737
  let trust = { entries: [] };
5627
5738
  try {
5628
- if (import_fs13.default.existsSync(TRUST_FILE2))
5629
- 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"));
5630
5741
  } catch {
5631
5742
  }
5632
5743
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -5637,8 +5748,8 @@ function writeTrustEntry(toolName, durationMs) {
5637
5748
  }
5638
5749
  function readPersistentDecisions() {
5639
5750
  try {
5640
- if (import_fs13.default.existsSync(DECISIONS_FILE)) {
5641
- 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"));
5642
5753
  }
5643
5754
  } catch {
5644
5755
  }
@@ -5675,7 +5786,7 @@ function estimateToolCost(tool, args) {
5675
5786
  const filePath = a.file_path ?? a.path;
5676
5787
  if (filePath) {
5677
5788
  try {
5678
- const bytes = import_fs13.default.statSync(filePath).size;
5789
+ const bytes = import_fs14.default.statSync(filePath).size;
5679
5790
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
5680
5791
  } catch {
5681
5792
  }
@@ -5733,7 +5844,7 @@ function abandonPending() {
5733
5844
  });
5734
5845
  if (autoStarted) {
5735
5846
  try {
5736
- import_fs13.default.unlinkSync(DAEMON_PID_FILE);
5847
+ import_fs14.default.unlinkSync(DAEMON_PID_FILE);
5737
5848
  } catch {
5738
5849
  }
5739
5850
  setTimeout(() => {
@@ -5744,7 +5855,7 @@ function abandonPending() {
5744
5855
  }
5745
5856
  function startActivitySocket() {
5746
5857
  try {
5747
- import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5858
+ import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5748
5859
  } catch {
5749
5860
  }
5750
5861
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -5825,34 +5936,34 @@ function startActivitySocket() {
5825
5936
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
5826
5937
  process.on("exit", () => {
5827
5938
  try {
5828
- import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5939
+ import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
5829
5940
  } catch {
5830
5941
  }
5831
5942
  });
5832
5943
  }
5833
- 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;
5834
5945
  var init_state2 = __esm({
5835
5946
  "src/daemon/state.ts"() {
5836
5947
  "use strict";
5837
5948
  import_net2 = __toESM(require("net"));
5838
- import_fs13 = __toESM(require("fs"));
5839
- import_path16 = __toESM(require("path"));
5840
- import_os11 = __toESM(require("os"));
5949
+ import_fs14 = __toESM(require("fs"));
5950
+ import_path17 = __toESM(require("path"));
5951
+ import_os12 = __toESM(require("os"));
5841
5952
  import_child_process3 = require("child_process");
5842
- import_crypto5 = require("crypto");
5953
+ import_crypto6 = require("crypto");
5843
5954
  init_daemon();
5844
5955
  init_suggestion_tracker();
5845
5956
  init_taint_store();
5846
5957
  init_session_counters();
5847
5958
  init_session_history();
5848
- homeDir = import_os11.default.homedir();
5849
- DAEMON_PID_FILE = import_path16.default.join(homeDir, ".node9", "daemon.pid");
5850
- DECISIONS_FILE = import_path16.default.join(homeDir, ".node9", "decisions.json");
5851
- AUDIT_LOG_FILE = import_path16.default.join(homeDir, ".node9", "audit.log");
5852
- TRUST_FILE2 = import_path16.default.join(homeDir, ".node9", "trust.json");
5853
- GLOBAL_CONFIG_FILE = import_path16.default.join(homeDir, ".node9", "config.json");
5854
- CREDENTIALS_FILE = import_path16.default.join(homeDir, ".node9", "credentials.json");
5855
- 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");
5856
5967
  pending = /* @__PURE__ */ new Map();
5857
5968
  sseClients = /* @__PURE__ */ new Set();
5858
5969
  suggestionTracker = new SuggestionTracker(3);
@@ -5870,7 +5981,7 @@ var init_state2 = __esm({
5870
5981
  "2h": 2 * 60 * 6e4
5871
5982
  };
5872
5983
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
5873
- 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");
5874
5985
  ACTIVITY_RING_SIZE = 100;
5875
5986
  activityRing = [];
5876
5987
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -5895,8 +6006,8 @@ var init_state2 = __esm({
5895
6006
  function patchConfig(configPath, patch) {
5896
6007
  let config = {};
5897
6008
  try {
5898
- if (import_fs14.default.existsSync(configPath)) {
5899
- 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"));
5900
6011
  }
5901
6012
  } catch {
5902
6013
  throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
@@ -5915,44 +6026,44 @@ function patchConfig(configPath, patch) {
5915
6026
  ignored.push(patch.toolName);
5916
6027
  }
5917
6028
  }
5918
- const dir = import_path17.default.dirname(configPath);
5919
- import_fs14.default.mkdirSync(dir, { recursive: true });
6029
+ const dir = import_path18.default.dirname(configPath);
6030
+ import_fs15.default.mkdirSync(dir, { recursive: true });
5920
6031
  const tmp = configPath + ".node9-tmp";
5921
6032
  try {
5922
- 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 });
5923
6034
  } catch (err2) {
5924
6035
  try {
5925
- import_fs14.default.unlinkSync(tmp);
6036
+ import_fs15.default.unlinkSync(tmp);
5926
6037
  } catch {
5927
6038
  }
5928
6039
  throw err2;
5929
6040
  }
5930
6041
  try {
5931
- import_fs14.default.renameSync(tmp, configPath);
6042
+ import_fs15.default.renameSync(tmp, configPath);
5932
6043
  } catch (err2) {
5933
6044
  try {
5934
- import_fs14.default.unlinkSync(tmp);
6045
+ import_fs15.default.unlinkSync(tmp);
5935
6046
  } catch {
5936
6047
  }
5937
6048
  throw err2;
5938
6049
  }
5939
6050
  }
5940
- var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
6051
+ var import_fs15, import_path18, import_os13, GLOBAL_CONFIG_PATH;
5941
6052
  var init_patch = __esm({
5942
6053
  "src/config/patch.ts"() {
5943
6054
  "use strict";
5944
- import_fs14 = __toESM(require("fs"));
5945
- import_path17 = __toESM(require("path"));
5946
- import_os12 = __toESM(require("os"));
5947
- 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");
5948
6059
  }
5949
6060
  });
5950
6061
 
5951
6062
  // src/daemon/server.ts
5952
6063
  function startDaemon() {
5953
6064
  loadInsightCounts();
5954
- const csrfToken = (0, import_crypto6.randomUUID)();
5955
- const internalToken = (0, import_crypto6.randomUUID)();
6065
+ const csrfToken = (0, import_crypto7.randomUUID)();
6066
+ const internalToken = (0, import_crypto7.randomUUID)();
5956
6067
  const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
5957
6068
  const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
5958
6069
  const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
@@ -5965,7 +6076,7 @@ function startDaemon() {
5965
6076
  idleTimer = setTimeout(() => {
5966
6077
  if (autoStarted) {
5967
6078
  try {
5968
- import_fs15.default.unlinkSync(DAEMON_PID_FILE);
6079
+ import_fs16.default.unlinkSync(DAEMON_PID_FILE);
5969
6080
  } catch {
5970
6081
  }
5971
6082
  }
@@ -6086,7 +6197,7 @@ data: ${JSON.stringify(item.data)}
6086
6197
  cwd,
6087
6198
  localSmartRuleMatched = false
6088
6199
  } = JSON.parse(body);
6089
- const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto6.randomUUID)();
6200
+ const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto7.randomUUID)();
6090
6201
  const entry = {
6091
6202
  id,
6092
6203
  toolName,
@@ -6128,7 +6239,7 @@ data: ${JSON.stringify(item.data)}
6128
6239
  status: "pending"
6129
6240
  });
6130
6241
  }
6131
- 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;
6132
6243
  const projectConfig = getConfig(projectCwd);
6133
6244
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6134
6245
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6518,8 +6629,8 @@ data: ${JSON.stringify(item.data)}
6518
6629
  const body = await readBody(req);
6519
6630
  const data = body ? JSON.parse(body) : {};
6520
6631
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6521
- const node9Dir = import_path18.default.dirname(GLOBAL_CONFIG_PATH);
6522
- 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)) {
6523
6634
  res.writeHead(400, { "Content-Type": "application/json" });
6524
6635
  return res.end(
6525
6636
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6630,14 +6741,14 @@ data: ${JSON.stringify(item.data)}
6630
6741
  server.on("error", (e) => {
6631
6742
  if (e.code === "EADDRINUSE") {
6632
6743
  try {
6633
- if (import_fs15.default.existsSync(DAEMON_PID_FILE)) {
6634
- 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"));
6635
6746
  process.kill(pid, 0);
6636
6747
  return process.exit(0);
6637
6748
  }
6638
6749
  } catch {
6639
6750
  try {
6640
- import_fs15.default.unlinkSync(DAEMON_PID_FILE);
6751
+ import_fs16.default.unlinkSync(DAEMON_PID_FILE);
6641
6752
  } catch {
6642
6753
  }
6643
6754
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -6696,14 +6807,14 @@ data: ${JSON.stringify(item.data)}
6696
6807
  }
6697
6808
  startActivitySocket();
6698
6809
  }
6699
- 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;
6700
6811
  var init_server = __esm({
6701
6812
  "src/daemon/server.ts"() {
6702
6813
  "use strict";
6703
6814
  import_http = __toESM(require("http"));
6704
- import_fs15 = __toESM(require("fs"));
6705
- import_path18 = __toESM(require("path"));
6706
- import_crypto6 = require("crypto");
6815
+ import_fs16 = __toESM(require("fs"));
6816
+ import_path19 = __toESM(require("path"));
6817
+ import_crypto7 = require("crypto");
6707
6818
  import_child_process4 = require("child_process");
6708
6819
  import_chalk2 = __toESM(require("chalk"));
6709
6820
  init_core();
@@ -6717,24 +6828,24 @@ var init_server = __esm({
6717
6828
 
6718
6829
  // src/daemon/index.ts
6719
6830
  function stopDaemon() {
6720
- 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."));
6721
6832
  try {
6722
- 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"));
6723
6834
  process.kill(pid, "SIGTERM");
6724
6835
  console.log(import_chalk3.default.green("\u2705 Stopped."));
6725
6836
  } catch {
6726
6837
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
6727
6838
  } finally {
6728
6839
  try {
6729
- import_fs16.default.unlinkSync(DAEMON_PID_FILE);
6840
+ import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6730
6841
  } catch {
6731
6842
  }
6732
6843
  }
6733
6844
  }
6734
6845
  function daemonStatus() {
6735
- if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
6846
+ if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
6736
6847
  try {
6737
- 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"));
6738
6849
  process.kill(pid, 0);
6739
6850
  console.log(import_chalk3.default.green("Node9 daemon: running"));
6740
6851
  return;
@@ -6753,11 +6864,11 @@ function daemonStatus() {
6753
6864
  console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
6754
6865
  }
6755
6866
  }
6756
- var import_fs16, import_chalk3, import_child_process5;
6867
+ var import_fs17, import_chalk3, import_child_process5;
6757
6868
  var init_daemon2 = __esm({
6758
6869
  "src/daemon/index.ts"() {
6759
6870
  "use strict";
6760
- import_fs16 = __toESM(require("fs"));
6871
+ import_fs17 = __toESM(require("fs"));
6761
6872
  import_chalk3 = __toESM(require("chalk"));
6762
6873
  import_child_process5 = require("child_process");
6763
6874
  init_server();
@@ -6778,44 +6889,64 @@ function getIcon(tool) {
6778
6889
  }
6779
6890
  return "\u{1F6E0}\uFE0F";
6780
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
+ }
6781
6901
  function formatBase(activity) {
6782
6902
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
6783
6903
  const icon = getIcon(activity.tool);
6784
6904
  const toolName = activity.tool.slice(0, 16).padEnd(16);
6785
- 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(), "~");
6786
6906
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
6787
- 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)}`;
6788
6908
  }
6789
6909
  function renderResult(activity, result) {
6790
6910
  const base = formatBase(activity);
6791
6911
  let status;
6792
6912
  if (result.status === "allow") {
6793
- status = import_chalk18.default.green("\u2713 ALLOW");
6913
+ status = import_chalk19.default.green("\u2713 ALLOW");
6794
6914
  } else if (result.status === "dlp") {
6795
- status = import_chalk18.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6915
+ status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
6796
6916
  } else {
6797
- status = import_chalk18.default.red("\u2717 BLOCK");
6917
+ status = import_chalk19.default.red("\u2717 BLOCK");
6798
6918
  }
6799
6919
  const cost = result.costEstimate ?? activity.costEstimate;
6800
- 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"}`);
6801
6921
  if (process.stdout.isTTY) {
6802
- import_readline5.default.clearLine(process.stdout, 0);
6803
- 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;
6804
6932
  }
6805
6933
  console.log(`${base} ${status}${costSuffix}`);
6806
6934
  }
6807
6935
  function renderPending(activity) {
6808
6936
  if (!process.stdout.isTTY) return;
6809
- 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`);
6810
6941
  }
6811
6942
  async function ensureDaemon() {
6812
6943
  let pidPort = null;
6813
- if (import_fs26.default.existsSync(PID_FILE)) {
6944
+ if (import_fs28.default.existsSync(PID_FILE)) {
6814
6945
  try {
6815
- 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"));
6816
6947
  pidPort = port;
6817
6948
  } catch {
6818
- 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."));
6819
6950
  }
6820
6951
  }
6821
6952
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -6826,7 +6957,7 @@ async function ensureDaemon() {
6826
6957
  if (res.ok) return checkPort;
6827
6958
  } catch {
6828
6959
  }
6829
- 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..."));
6830
6961
  const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
6831
6962
  detached: true,
6832
6963
  stdio: "ignore",
@@ -6843,7 +6974,7 @@ async function ensureDaemon() {
6843
6974
  } catch {
6844
6975
  }
6845
6976
  }
6846
- 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"));
6847
6978
  process.exit(1);
6848
6979
  }
6849
6980
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -6932,9 +7063,9 @@ function buildRecoveryCardLines(req) {
6932
7063
  ];
6933
7064
  }
6934
7065
  function readApproversFromDisk() {
6935
- 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");
6936
7067
  try {
6937
- const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
7068
+ const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
6938
7069
  const settings = raw.settings ?? {};
6939
7070
  return settings.approvers ?? {};
6940
7071
  } catch {
@@ -6945,20 +7076,20 @@ function approverStatusLine() {
6945
7076
  const a = readApproversFromDisk();
6946
7077
  const fmt = (label, key) => {
6947
7078
  const on = a[key] !== false;
6948
- 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")}`;
6949
7080
  };
6950
7081
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
6951
7082
  }
6952
7083
  function toggleApprover(channel) {
6953
- 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");
6954
7085
  try {
6955
- const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
7086
+ const raw = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
6956
7087
  const settings = raw.settings ?? {};
6957
7088
  const approvers = settings.approvers ?? {};
6958
7089
  approvers[channel] = approvers[channel] === false;
6959
7090
  settings.approvers = approvers;
6960
7091
  raw.settings = settings;
6961
- import_fs26.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7092
+ import_fs28.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
6962
7093
  } catch (err2) {
6963
7094
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
6964
7095
  `);
@@ -6990,7 +7121,7 @@ async function startTail(options = {}) {
6990
7121
  req2.end();
6991
7122
  });
6992
7123
  if (result.ok) {
6993
- console.log(import_chalk18.default.green("\u2713 Flight Recorder buffer cleared."));
7124
+ console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
6994
7125
  } else if (result.code === "ECONNREFUSED") {
6995
7126
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
6996
7127
  } else if (result.code === "ETIMEDOUT") {
@@ -7034,7 +7165,7 @@ async function startTail(options = {}) {
7034
7165
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7035
7166
  if (channel) {
7036
7167
  toggleApprover(channel);
7037
- console.log(import_chalk18.default.dim(` Approvers: ${approverStatusLine()}`));
7168
+ console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
7038
7169
  }
7039
7170
  };
7040
7171
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7100,7 +7231,7 @@ async function startTail(options = {}) {
7100
7231
  localAllowCounts.get(req2.toolName) ?? 0
7101
7232
  )
7102
7233
  );
7103
- 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");
7104
7235
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7105
7236
  for (const line of stampedLines) process.stdout.write(line + "\n");
7106
7237
  process.stdout.write(SHOW_CURSOR);
@@ -7128,8 +7259,8 @@ async function startTail(options = {}) {
7128
7259
  }
7129
7260
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7130
7261
  try {
7131
- import_fs26.default.appendFileSync(
7132
- 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"),
7133
7264
  `[tail] POST /decision failed: ${String(err2)}
7134
7265
  `
7135
7266
  );
@@ -7151,7 +7282,7 @@ async function startTail(options = {}) {
7151
7282
  );
7152
7283
  const stampedLines = buildCardLines(req2, priorCount);
7153
7284
  if (externalDecision) {
7154
- 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");
7155
7286
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7156
7287
  }
7157
7288
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7210,16 +7341,16 @@ async function startTail(options = {}) {
7210
7341
  }
7211
7342
  } catch {
7212
7343
  }
7213
- console.log(import_chalk18.default.cyan.bold(`
7214
- \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}`));
7215
7346
  if (canApprove) {
7216
- console.log(import_chalk18.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7217
- 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`));
7218
7349
  }
7219
7350
  if (options.history) {
7220
- console.log(import_chalk18.default.dim("Showing history + live events.\n"));
7351
+ console.log(import_chalk19.default.dim("Showing history + live events.\n"));
7221
7352
  } else {
7222
- 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"));
7223
7354
  }
7224
7355
  process.on("SIGINT", () => {
7225
7356
  exitIdleMode();
@@ -7229,13 +7360,13 @@ async function startTail(options = {}) {
7229
7360
  import_readline5.default.clearLine(process.stdout, 0);
7230
7361
  import_readline5.default.cursorTo(process.stdout, 0);
7231
7362
  }
7232
- console.log(import_chalk18.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7363
+ console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7233
7364
  process.exit(0);
7234
7365
  });
7235
7366
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7236
7367
  const req = import_http2.default.get(sseUrl, (res) => {
7237
7368
  if (res.statusCode !== 200) {
7238
- 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}`));
7239
7370
  process.exit(1);
7240
7371
  }
7241
7372
  if (canApprove) enterIdleMode();
@@ -7266,7 +7397,7 @@ async function startTail(options = {}) {
7266
7397
  import_readline5.default.clearLine(process.stdout, 0);
7267
7398
  import_readline5.default.cursorTo(process.stdout, 0);
7268
7399
  }
7269
- console.log(import_chalk18.default.red("\n\u274C Daemon disconnected."));
7400
+ console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
7270
7401
  process.exit(1);
7271
7402
  });
7272
7403
  });
@@ -7358,9 +7489,9 @@ async function startTail(options = {}) {
7358
7489
  const hash = data.hash ?? "";
7359
7490
  const summary = data.argsSummary ?? data.tool;
7360
7491
  const fileCount = data.fileCount ?? 0;
7361
- 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"}`) : "";
7362
7493
  process.stdout.write(
7363
- `${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}
7364
7495
  `
7365
7496
  );
7366
7497
  return;
@@ -7377,26 +7508,26 @@ async function startTail(options = {}) {
7377
7508
  }
7378
7509
  req.on("error", (err2) => {
7379
7510
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7380
- console.error(import_chalk18.default.red(`
7511
+ console.error(import_chalk19.default.red(`
7381
7512
  \u274C ${msg}`));
7382
7513
  process.exit(1);
7383
7514
  });
7384
7515
  }
7385
- 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;
7386
7517
  var init_tail = __esm({
7387
7518
  "src/tui/tail.ts"() {
7388
7519
  "use strict";
7389
7520
  import_http2 = __toESM(require("http"));
7390
- import_chalk18 = __toESM(require("chalk"));
7391
- import_fs26 = __toESM(require("fs"));
7392
- import_os22 = __toESM(require("os"));
7393
- 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"));
7394
7525
  import_readline5 = __toESM(require("readline"));
7395
7526
  import_child_process14 = require("child_process");
7396
7527
  init_daemon2();
7397
7528
  init_daemon();
7398
7529
  init_core();
7399
- 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");
7400
7531
  ICONS = {
7401
7532
  bash: "\u{1F4BB}",
7402
7533
  shell: "\u{1F4BB}",
@@ -7424,6 +7555,8 @@ var init_tail = __esm({
7424
7555
  HIDE_CURSOR = "\x1B[?25l";
7425
7556
  SHOW_CURSOR = "\x1B[?25h";
7426
7557
  ERASE_DOWN = "\x1B[J";
7558
+ pendingShownForId = null;
7559
+ pendingWrappedLines = 0;
7427
7560
  DIVIDER = "\u2500".repeat(60);
7428
7561
  }
7429
7562
  });
@@ -7492,10 +7625,10 @@ function bold(s) {
7492
7625
  function color(c, s) {
7493
7626
  return `${c}${s}${RESET3}`;
7494
7627
  }
7495
- function progressBar(pct, warnAt = 70, critAt = 85) {
7496
- 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);
7497
7630
  const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
7498
- const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
7631
+ const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
7499
7632
  return `${c}${bar}${RESET3}`;
7500
7633
  }
7501
7634
  function formatTimeLeft(resetsAt) {
@@ -7509,9 +7642,9 @@ function formatTimeLeft(resetsAt) {
7509
7642
  return ` (${m}m left)`;
7510
7643
  }
7511
7644
  function safeReadJson(filePath) {
7512
- if (!import_fs27.default.existsSync(filePath)) return null;
7645
+ if (!import_fs29.default.existsSync(filePath)) return null;
7513
7646
  try {
7514
- return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
7647
+ return JSON.parse(import_fs29.default.readFileSync(filePath, "utf-8"));
7515
7648
  } catch {
7516
7649
  return null;
7517
7650
  }
@@ -7532,12 +7665,12 @@ function countHooksInFile(filePath) {
7532
7665
  return Object.keys(cfg.hooks).length;
7533
7666
  }
7534
7667
  function countRulesInDir(rulesDir) {
7535
- if (!import_fs27.default.existsSync(rulesDir)) return 0;
7668
+ if (!import_fs29.default.existsSync(rulesDir)) return 0;
7536
7669
  let count = 0;
7537
7670
  try {
7538
- for (const entry of import_fs27.default.readdirSync(rulesDir, { withFileTypes: true })) {
7671
+ for (const entry of import_fs29.default.readdirSync(rulesDir, { withFileTypes: true })) {
7539
7672
  if (entry.isDirectory()) {
7540
- count += countRulesInDir(import_path30.default.join(rulesDir, entry.name));
7673
+ count += countRulesInDir(import_path32.default.join(rulesDir, entry.name));
7541
7674
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7542
7675
  count++;
7543
7676
  }
@@ -7548,46 +7681,46 @@ function countRulesInDir(rulesDir) {
7548
7681
  }
7549
7682
  function isSamePath(a, b) {
7550
7683
  try {
7551
- return import_path30.default.resolve(a) === import_path30.default.resolve(b);
7684
+ return import_path32.default.resolve(a) === import_path32.default.resolve(b);
7552
7685
  } catch {
7553
7686
  return false;
7554
7687
  }
7555
7688
  }
7556
7689
  function countConfigs(cwd) {
7557
- const homeDir2 = import_os23.default.homedir();
7558
- const claudeDir = import_path30.default.join(homeDir2, ".claude");
7690
+ const homeDir2 = import_os25.default.homedir();
7691
+ const claudeDir = import_path32.default.join(homeDir2, ".claude");
7559
7692
  let claudeMdCount = 0;
7560
7693
  let rulesCount = 0;
7561
7694
  let hooksCount = 0;
7562
7695
  const userMcpServers = /* @__PURE__ */ new Set();
7563
7696
  const projectMcpServers = /* @__PURE__ */ new Set();
7564
- if (import_fs27.default.existsSync(import_path30.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7565
- rulesCount += countRulesInDir(import_path30.default.join(claudeDir, "rules"));
7566
- 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");
7567
7700
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7568
7701
  hooksCount += countHooksInFile(userSettings);
7569
- const userClaudeJson = import_path30.default.join(homeDir2, ".claude.json");
7702
+ const userClaudeJson = import_path32.default.join(homeDir2, ".claude.json");
7570
7703
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7571
7704
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7572
7705
  userMcpServers.delete(name);
7573
7706
  }
7574
7707
  if (cwd) {
7575
- if (import_fs27.default.existsSync(import_path30.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7576
- if (import_fs27.default.existsSync(import_path30.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7577
- 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");
7578
7711
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7579
7712
  if (!overlapsUserScope) {
7580
- if (import_fs27.default.existsSync(import_path30.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7581
- rulesCount += countRulesInDir(import_path30.default.join(projectClaudeDir, "rules"));
7582
- 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");
7583
7716
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7584
7717
  hooksCount += countHooksInFile(projSettings);
7585
7718
  }
7586
- if (import_fs27.default.existsSync(import_path30.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7587
- 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");
7588
7721
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7589
7722
  hooksCount += countHooksInFile(localSettings);
7590
- const mcpJsonServers = getMcpServerNames(import_path30.default.join(cwd, ".mcp.json"));
7723
+ const mcpJsonServers = getMcpServerNames(import_path32.default.join(cwd, ".mcp.json"));
7591
7724
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7592
7725
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7593
7726
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7620,12 +7753,12 @@ function readActiveShieldsHud() {
7620
7753
  return shieldsCache.value;
7621
7754
  }
7622
7755
  try {
7623
- const shieldsPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "shields.json");
7624
- 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)) {
7625
7758
  shieldsCache = { value: [], ts: now };
7626
7759
  return [];
7627
7760
  }
7628
- const parsed = JSON.parse(import_fs27.default.readFileSync(shieldsPath, "utf-8"));
7761
+ const parsed = JSON.parse(import_fs29.default.readFileSync(shieldsPath, "utf-8"));
7629
7762
  if (!Array.isArray(parsed.active)) {
7630
7763
  shieldsCache = { value: [], ts: now };
7631
7764
  return [];
@@ -7711,15 +7844,15 @@ function renderContextLine(stdin) {
7711
7844
  }
7712
7845
  const rl = stdin.rate_limits;
7713
7846
  if (rl?.five_hour?.used_percentage !== void 0) {
7714
- const pct = Math.round(rl.five_hour.used_percentage);
7715
- const bar = progressBar(pct, 60, 80);
7847
+ const pct2 = Math.round(rl.five_hour.used_percentage);
7848
+ const bar = progressBar(pct2, 60, 80);
7716
7849
  const left = formatTimeLeft(rl.five_hour.resets_at);
7717
- parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
7850
+ parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
7718
7851
  }
7719
7852
  if (rl?.seven_day?.used_percentage !== void 0) {
7720
- const pct = Math.round(rl.seven_day.used_percentage);
7721
- const bar = progressBar(pct, 60, 80);
7722
- 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}%`);
7723
7856
  }
7724
7857
  if (parts.length === 0) return null;
7725
7858
  return parts.join(" ");
@@ -7727,17 +7860,17 @@ function renderContextLine(stdin) {
7727
7860
  async function main() {
7728
7861
  try {
7729
7862
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
7730
- 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"))) {
7731
7864
  try {
7732
- 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");
7733
7866
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
7734
7867
  let size = 0;
7735
7868
  try {
7736
- size = import_fs27.default.statSync(logPath).size;
7869
+ size = import_fs29.default.statSync(logPath).size;
7737
7870
  } catch {
7738
7871
  }
7739
7872
  if (size < MAX_LOG_SIZE) {
7740
- import_fs27.default.appendFileSync(
7873
+ import_fs29.default.appendFileSync(
7741
7874
  logPath,
7742
7875
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
7743
7876
  );
@@ -7758,11 +7891,11 @@ async function main() {
7758
7891
  try {
7759
7892
  const cwd = stdin.cwd ?? process.cwd();
7760
7893
  for (const configPath of [
7761
- import_path30.default.join(cwd, "node9.config.json"),
7762
- 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")
7763
7896
  ]) {
7764
- if (!import_fs27.default.existsSync(configPath)) continue;
7765
- 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"));
7766
7899
  const hud = cfg.settings?.hud;
7767
7900
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
7768
7901
  }
@@ -7780,13 +7913,13 @@ async function main() {
7780
7913
  renderOffline();
7781
7914
  }
7782
7915
  }
7783
- 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;
7784
7917
  var init_hud = __esm({
7785
7918
  "src/cli/hud.ts"() {
7786
7919
  "use strict";
7787
- import_fs27 = __toESM(require("fs"));
7788
- import_path30 = __toESM(require("path"));
7789
- import_os23 = __toESM(require("os"));
7920
+ import_fs29 = __toESM(require("fs"));
7921
+ import_path32 = __toESM(require("path"));
7922
+ import_os25 = __toESM(require("os"));
7790
7923
  import_http3 = __toESM(require("http"));
7791
7924
  init_daemon();
7792
7925
  RESET3 = "\x1B[0m";
@@ -7812,9 +7945,9 @@ var import_commander = require("commander");
7812
7945
  init_core();
7813
7946
 
7814
7947
  // src/setup.ts
7815
- var import_fs11 = __toESM(require("fs"));
7816
- var import_path14 = __toESM(require("path"));
7817
- 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"));
7818
7951
  var import_chalk = __toESM(require("chalk"));
7819
7952
  var import_prompts = require("@inquirer/prompts");
7820
7953
  var import_smol_toml = require("smol-toml");
@@ -7842,26 +7975,26 @@ function fullPathCommand(subcommand) {
7842
7975
  }
7843
7976
  function readJson(filePath) {
7844
7977
  try {
7845
- if (import_fs11.default.existsSync(filePath)) {
7846
- 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"));
7847
7980
  }
7848
7981
  } catch {
7849
7982
  }
7850
7983
  return null;
7851
7984
  }
7852
7985
  function writeJson(filePath, data) {
7853
- const dir = import_path14.default.dirname(filePath);
7854
- if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
7855
- 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");
7856
7989
  }
7857
7990
  function isNode9Hook(cmd) {
7858
7991
  if (!cmd) return false;
7859
7992
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
7860
7993
  }
7861
7994
  function teardownClaude() {
7862
- const homeDir2 = import_os10.default.homedir();
7863
- const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
7864
- 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");
7865
7998
  let changed = false;
7866
7999
  const settings = readJson(hooksPath);
7867
8000
  if (settings?.hooks) {
@@ -7909,8 +8042,8 @@ function teardownClaude() {
7909
8042
  }
7910
8043
  }
7911
8044
  function teardownGemini() {
7912
- const homeDir2 = import_os10.default.homedir();
7913
- 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");
7914
8047
  const settings = readJson(settingsPath);
7915
8048
  if (!settings) {
7916
8049
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -7953,8 +8086,8 @@ function teardownGemini() {
7953
8086
  }
7954
8087
  }
7955
8088
  function teardownCursor() {
7956
- const homeDir2 = import_os10.default.homedir();
7957
- 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");
7958
8091
  const mcpConfig = readJson(mcpPath);
7959
8092
  if (!mcpConfig?.mcpServers) {
7960
8093
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -7985,9 +8118,9 @@ function teardownCursor() {
7985
8118
  }
7986
8119
  }
7987
8120
  async function setupClaude() {
7988
- const homeDir2 = import_os10.default.homedir();
7989
- const mcpPath = import_path14.default.join(homeDir2, ".claude", ".mcp.json");
7990
- 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");
7991
8124
  const claudeConfig = readJson(mcpPath) ?? {};
7992
8125
  const settings = readJson(hooksPath) ?? {};
7993
8126
  const servers = claudeConfig.mcpServers ?? {};
@@ -8084,8 +8217,8 @@ async function setupClaude() {
8084
8217
  }
8085
8218
  }
8086
8219
  async function setupGemini() {
8087
- const homeDir2 = import_os10.default.homedir();
8088
- 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");
8089
8222
  const settings = readJson(settingsPath) ?? {};
8090
8223
  const servers = settings.mcpServers ?? {};
8091
8224
  let hooksChanged = false;
@@ -8180,10 +8313,10 @@ async function setupGemini() {
8180
8313
  printDaemonTip();
8181
8314
  }
8182
8315
  }
8183
- function detectAgents(homeDir2 = import_os10.default.homedir()) {
8316
+ function detectAgents(homeDir2 = import_os11.default.homedir()) {
8184
8317
  const exists = (p) => {
8185
8318
  try {
8186
- return import_fs11.default.existsSync(p);
8319
+ return import_fs12.default.existsSync(p);
8187
8320
  } catch (err2) {
8188
8321
  const code = err2.code;
8189
8322
  if (code !== "ENOENT") {
@@ -8194,15 +8327,15 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
8194
8327
  }
8195
8328
  };
8196
8329
  return {
8197
- claude: exists(import_path14.default.join(homeDir2, ".claude")) || exists(import_path14.default.join(homeDir2, ".claude.json")),
8198
- gemini: exists(import_path14.default.join(homeDir2, ".gemini")),
8199
- cursor: exists(import_path14.default.join(homeDir2, ".cursor")),
8200
- 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"))
8201
8334
  };
8202
8335
  }
8203
8336
  async function setupCursor() {
8204
- const homeDir2 = import_os10.default.homedir();
8205
- 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");
8206
8339
  const mcpConfig = readJson(mcpPath) ?? {};
8207
8340
  const servers = mcpConfig.mcpServers ?? {};
8208
8341
  let anythingChanged = false;
@@ -8268,21 +8401,21 @@ async function setupCursor() {
8268
8401
  }
8269
8402
  function readToml(filePath) {
8270
8403
  try {
8271
- if (import_fs11.default.existsSync(filePath)) {
8272
- 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"));
8273
8406
  }
8274
8407
  } catch {
8275
8408
  }
8276
8409
  return null;
8277
8410
  }
8278
8411
  function writeToml(filePath, data) {
8279
- const dir = import_path14.default.dirname(filePath);
8280
- if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
8281
- 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));
8282
8415
  }
8283
8416
  async function setupCodex() {
8284
- const homeDir2 = import_os10.default.homedir();
8285
- 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");
8286
8419
  const config = readToml(configPath) ?? {};
8287
8420
  const servers = config.mcp_servers ?? {};
8288
8421
  let anythingChanged = false;
@@ -8347,8 +8480,8 @@ async function setupCodex() {
8347
8480
  }
8348
8481
  }
8349
8482
  function setupHud() {
8350
- const homeDir2 = import_os10.default.homedir();
8351
- 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");
8352
8485
  const settings = readJson(hooksPath) ?? {};
8353
8486
  const hudCommand = fullPathCommand("hud");
8354
8487
  const statusLineObj = { type: "command", command: hudCommand };
@@ -8374,8 +8507,8 @@ function setupHud() {
8374
8507
  console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
8375
8508
  }
8376
8509
  function teardownHud() {
8377
- const homeDir2 = import_os10.default.homedir();
8378
- 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");
8379
8512
  const settings = readJson(hooksPath);
8380
8513
  if (!settings) {
8381
8514
  console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
@@ -8395,10 +8528,10 @@ function teardownHud() {
8395
8528
 
8396
8529
  // src/cli.ts
8397
8530
  init_daemon2();
8398
- var import_chalk19 = __toESM(require("chalk"));
8399
- var import_fs28 = __toESM(require("fs"));
8400
- var import_path31 = __toESM(require("path"));
8401
- 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"));
8402
8535
  var import_prompts2 = require("@inquirer/prompts");
8403
8536
 
8404
8537
  // src/utils/duration.ts
@@ -8623,10 +8756,10 @@ async function autoStartDaemonAndWait() {
8623
8756
 
8624
8757
  // src/cli/commands/check.ts
8625
8758
  var import_chalk5 = __toESM(require("chalk"));
8626
- var import_fs18 = __toESM(require("fs"));
8759
+ var import_fs19 = __toESM(require("fs"));
8627
8760
  var import_child_process9 = require("child_process");
8628
- var import_path20 = __toESM(require("path"));
8629
- var import_os14 = __toESM(require("os"));
8761
+ var import_path21 = __toESM(require("path"));
8762
+ var import_os15 = __toESM(require("os"));
8630
8763
  init_orchestrator();
8631
8764
  init_daemon();
8632
8765
  init_config();
@@ -8634,12 +8767,12 @@ init_policy();
8634
8767
 
8635
8768
  // src/undo.ts
8636
8769
  var import_child_process8 = require("child_process");
8637
- var import_crypto7 = __toESM(require("crypto"));
8638
- var import_fs17 = __toESM(require("fs"));
8770
+ var import_crypto8 = __toESM(require("crypto"));
8771
+ var import_fs18 = __toESM(require("fs"));
8639
8772
  var import_net3 = __toESM(require("net"));
8640
- var import_path19 = __toESM(require("path"));
8641
- var import_os13 = __toESM(require("os"));
8642
- 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");
8643
8776
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8644
8777
  try {
8645
8778
  const payload = JSON.stringify({
@@ -8659,22 +8792,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8659
8792
  } catch {
8660
8793
  }
8661
8794
  }
8662
- var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
8663
- 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");
8664
8797
  var MAX_SNAPSHOTS = 10;
8665
8798
  var GIT_TIMEOUT = 15e3;
8666
8799
  function readStack() {
8667
8800
  try {
8668
- if (import_fs17.default.existsSync(SNAPSHOT_STACK_PATH))
8669
- 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"));
8670
8803
  } catch {
8671
8804
  }
8672
8805
  return [];
8673
8806
  }
8674
8807
  function writeStack(stack) {
8675
- const dir = import_path19.default.dirname(SNAPSHOT_STACK_PATH);
8676
- if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
8677
- 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));
8678
8811
  }
8679
8812
  function extractFilePath(args) {
8680
8813
  if (!args || typeof args !== "object") return null;
@@ -8694,12 +8827,12 @@ function buildArgsSummary(tool, args) {
8694
8827
  return "";
8695
8828
  }
8696
8829
  function findProjectRoot(filePath) {
8697
- let dir = import_path19.default.dirname(filePath);
8830
+ let dir = import_path20.default.dirname(filePath);
8698
8831
  while (true) {
8699
- 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"))) {
8700
8833
  return dir;
8701
8834
  }
8702
- const parent = import_path19.default.dirname(dir);
8835
+ const parent = import_path20.default.dirname(dir);
8703
8836
  if (parent === dir) return process.cwd();
8704
8837
  dir = parent;
8705
8838
  }
@@ -8707,7 +8840,7 @@ function findProjectRoot(filePath) {
8707
8840
  function normalizeCwdForHash(cwd) {
8708
8841
  let normalized;
8709
8842
  try {
8710
- normalized = import_fs17.default.realpathSync(cwd);
8843
+ normalized = import_fs18.default.realpathSync(cwd);
8711
8844
  } catch {
8712
8845
  normalized = cwd;
8713
8846
  }
@@ -8716,17 +8849,17 @@ function normalizeCwdForHash(cwd) {
8716
8849
  return normalized;
8717
8850
  }
8718
8851
  function getShadowRepoDir(cwd) {
8719
- const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
8720
- 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);
8721
8854
  }
8722
8855
  function cleanOrphanedIndexFiles(shadowDir) {
8723
8856
  try {
8724
8857
  const cutoff = Date.now() - 6e4;
8725
- for (const f of import_fs17.default.readdirSync(shadowDir)) {
8858
+ for (const f of import_fs18.default.readdirSync(shadowDir)) {
8726
8859
  if (f.startsWith("index_")) {
8727
- const fp = import_path19.default.join(shadowDir, f);
8860
+ const fp = import_path20.default.join(shadowDir, f);
8728
8861
  try {
8729
- 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);
8730
8863
  } catch {
8731
8864
  }
8732
8865
  }
@@ -8738,7 +8871,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
8738
8871
  const hardcoded = [".git", ".node9"];
8739
8872
  const lines = [...hardcoded, ...ignorePaths].join("\n");
8740
8873
  try {
8741
- 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");
8742
8875
  } catch {
8743
8876
  }
8744
8877
  }
@@ -8751,25 +8884,25 @@ function ensureShadowRepo(shadowDir, cwd) {
8751
8884
  timeout: 3e3
8752
8885
  });
8753
8886
  if (check.status === 0) {
8754
- const ptPath = import_path19.default.join(shadowDir, "project-path.txt");
8887
+ const ptPath = import_path20.default.join(shadowDir, "project-path.txt");
8755
8888
  try {
8756
- const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
8889
+ const stored = import_fs18.default.readFileSync(ptPath, "utf8").trim();
8757
8890
  if (stored === normalizedCwd) return true;
8758
8891
  if (process.env.NODE9_DEBUG === "1")
8759
8892
  console.error(
8760
8893
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
8761
8894
  );
8762
- import_fs17.default.rmSync(shadowDir, { recursive: true, force: true });
8895
+ import_fs18.default.rmSync(shadowDir, { recursive: true, force: true });
8763
8896
  } catch {
8764
8897
  try {
8765
- import_fs17.default.writeFileSync(ptPath, normalizedCwd, "utf8");
8898
+ import_fs18.default.writeFileSync(ptPath, normalizedCwd, "utf8");
8766
8899
  } catch {
8767
8900
  }
8768
8901
  return true;
8769
8902
  }
8770
8903
  }
8771
8904
  try {
8772
- import_fs17.default.mkdirSync(shadowDir, { recursive: true });
8905
+ import_fs18.default.mkdirSync(shadowDir, { recursive: true });
8773
8906
  } catch {
8774
8907
  }
8775
8908
  const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -8778,7 +8911,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8778
8911
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
8779
8912
  return false;
8780
8913
  }
8781
- const configFile = import_path19.default.join(shadowDir, "config");
8914
+ const configFile = import_path20.default.join(shadowDir, "config");
8782
8915
  (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
8783
8916
  timeout: 3e3
8784
8917
  });
@@ -8786,7 +8919,7 @@ function ensureShadowRepo(shadowDir, cwd) {
8786
8919
  timeout: 3e3
8787
8920
  });
8788
8921
  try {
8789
- 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");
8790
8923
  } catch {
8791
8924
  }
8792
8925
  return true;
@@ -8806,12 +8939,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8806
8939
  let indexFile = null;
8807
8940
  try {
8808
8941
  const rawFilePath = extractFilePath(args);
8809
- const absFilePath = rawFilePath && import_path19.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8942
+ const absFilePath = rawFilePath && import_path20.default.isAbsolute(rawFilePath) ? rawFilePath : null;
8810
8943
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
8811
8944
  const shadowDir = getShadowRepoDir(cwd);
8812
8945
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
8813
8946
  writeShadowExcludes(shadowDir, ignorePaths);
8814
- indexFile = import_path19.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8947
+ indexFile = import_path20.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
8815
8948
  const shadowEnv = {
8816
8949
  ...process.env,
8817
8950
  GIT_DIR: shadowDir,
@@ -8883,7 +9016,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8883
9016
  writeStack(stack);
8884
9017
  const entry = stack[stack.length - 1];
8885
9018
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
8886
- import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9019
+ import_fs18.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
8887
9020
  if (shouldGc) {
8888
9021
  (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
8889
9022
  }
@@ -8894,7 +9027,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
8894
9027
  } finally {
8895
9028
  if (indexFile) {
8896
9029
  try {
8897
- import_fs17.default.unlinkSync(indexFile);
9030
+ import_fs18.default.unlinkSync(indexFile);
8898
9031
  } catch {
8899
9032
  }
8900
9033
  }
@@ -8970,9 +9103,9 @@ function applyUndo(hash, cwd) {
8970
9103
  timeout: GIT_TIMEOUT
8971
9104
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
8972
9105
  for (const file of [...tracked, ...untracked]) {
8973
- const fullPath = import_path19.default.join(dir, file);
8974
- if (!snapshotFiles.has(file) && import_fs17.default.existsSync(fullPath)) {
8975
- 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);
8976
9109
  }
8977
9110
  }
8978
9111
  return true;
@@ -8996,9 +9129,9 @@ function registerCheckCommand(program2) {
8996
9129
  } catch (err2) {
8997
9130
  const tempConfig = getConfig();
8998
9131
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
8999
- 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");
9000
9133
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9001
- import_fs18.default.appendFileSync(
9134
+ import_fs19.default.appendFileSync(
9002
9135
  logPath,
9003
9136
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9004
9137
  RAW: ${raw}
@@ -9011,10 +9144,10 @@ RAW: ${raw}
9011
9144
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9012
9145
  try {
9013
9146
  const scriptPath = process.argv[1];
9014
- if (typeof scriptPath !== "string" || !import_path20.default.isAbsolute(scriptPath))
9147
+ if (typeof scriptPath !== "string" || !import_path21.default.isAbsolute(scriptPath))
9015
9148
  throw new Error("node9: argv[1] is not an absolute path");
9016
- const resolvedScript = import_fs18.default.realpathSync(scriptPath);
9017
- 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"));
9018
9151
  if (resolvedScript !== expectedCli)
9019
9152
  throw new Error(
9020
9153
  "node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
@@ -9040,10 +9173,10 @@ RAW: ${raw}
9040
9173
  }
9041
9174
  }
9042
9175
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9043
- const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
9044
- if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
9045
- import_fs18.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
9046
- 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}
9047
9180
  `);
9048
9181
  }
9049
9182
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9056,8 +9189,8 @@ RAW: ${raw}
9056
9189
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9057
9190
  let ttyFd = null;
9058
9191
  try {
9059
- ttyFd = import_fs18.default.openSync("/dev/tty", "w");
9060
- 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");
9061
9194
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9062
9195
  writeTty(import_chalk5.default.bgRed.white.bold(`
9063
9196
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9066,6 +9199,7 @@ RAW: ${raw}
9066
9199
  writeTty(import_chalk5.default.red(`
9067
9200
  \u{1F6D1} Node9 blocked "${toolName}"`));
9068
9201
  }
9202
+ if (result2?.ruleDescription) writeTty(import_chalk5.default.white(` ${result2.ruleDescription}`));
9069
9203
  writeTty(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
9070
9204
  if (result2?.changeHint) writeTty(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
9071
9205
  if (result2?.recoveryCommand)
@@ -9075,7 +9209,7 @@ RAW: ${raw}
9075
9209
  } finally {
9076
9210
  if (ttyFd !== null)
9077
9211
  try {
9078
- import_fs18.default.closeSync(ttyFd);
9212
+ import_fs19.default.closeSync(ttyFd);
9079
9213
  } catch {
9080
9214
  }
9081
9215
  }
@@ -9107,7 +9241,7 @@ RAW: ${raw}
9107
9241
  if (shouldSnapshot(toolName, toolInput, config)) {
9108
9242
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9109
9243
  }
9110
- 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;
9111
9245
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9112
9246
  cwd: safeCwdForAuth
9113
9247
  });
@@ -9119,12 +9253,12 @@ RAW: ${raw}
9119
9253
  }
9120
9254
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9121
9255
  try {
9122
- const tty = import_fs18.default.openSync("/dev/tty", "w");
9123
- import_fs18.default.writeSync(
9256
+ const tty = import_fs19.default.openSync("/dev/tty", "w");
9257
+ import_fs19.default.writeSync(
9124
9258
  tty,
9125
9259
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9126
9260
  );
9127
- import_fs18.default.closeSync(tty);
9261
+ import_fs19.default.closeSync(tty);
9128
9262
  } catch {
9129
9263
  }
9130
9264
  const daemonReady = await autoStartDaemonAndWait();
@@ -9151,9 +9285,9 @@ RAW: ${raw}
9151
9285
  });
9152
9286
  } catch (err2) {
9153
9287
  if (process.env.NODE9_DEBUG === "1") {
9154
- 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");
9155
9289
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9156
- import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9290
+ import_fs19.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9157
9291
  `);
9158
9292
  }
9159
9293
  process.exit(0);
@@ -9187,9 +9321,9 @@ RAW: ${raw}
9187
9321
  }
9188
9322
 
9189
9323
  // src/cli/commands/log.ts
9190
- var import_fs19 = __toESM(require("fs"));
9191
- var import_path21 = __toESM(require("path"));
9192
- 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"));
9193
9327
  init_audit();
9194
9328
  init_config();
9195
9329
  init_policy();
@@ -9233,9 +9367,9 @@ function containsShellMetachar(token) {
9233
9367
  }
9234
9368
 
9235
9369
  // src/cli/commands/log.ts
9236
- 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;
9237
9371
  function detectTestResult(command, output) {
9238
- if (!TEST_COMMAND_RE.test(command)) return null;
9372
+ if (!TEST_COMMAND_RE2.test(command)) return null;
9239
9373
  const out = output.toLowerCase();
9240
9374
  if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
9241
9375
  out
@@ -9265,10 +9399,10 @@ function registerLogCommand(program2) {
9265
9399
  decision: "allowed",
9266
9400
  source: "post-hook"
9267
9401
  };
9268
- const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "audit.log");
9269
- if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
9270
- import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
9271
- 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");
9272
9406
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9273
9407
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9274
9408
  if (command) {
@@ -9284,16 +9418,24 @@ function registerLogCommand(program2) {
9284
9418
  if (bashCommand && output) {
9285
9419
  const testResult = detectTestResult(bashCommand, output);
9286
9420
  if (testResult) {
9287
- await notifyActivitySocket({
9288
- id: "test-result",
9289
- ts: Date.now(),
9421
+ appendToLog(LOCAL_AUDIT_LOG, {
9422
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
9290
9423
  tool,
9291
- status: testResult === "pass" ? "test_pass" : "test_fail"
9424
+ testResult,
9425
+ source: "test-result"
9292
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
+ }
9293
9435
  }
9294
9436
  }
9295
9437
  }
9296
- 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;
9297
9439
  const config = getConfig(safeCwd);
9298
9440
  if (shouldSnapshot(tool, {}, config)) {
9299
9441
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9302,9 +9444,9 @@ function registerLogCommand(program2) {
9302
9444
  const msg = err2 instanceof Error ? err2.message : String(err2);
9303
9445
  process.stderr.write(`[Node9] audit log error: ${msg}
9304
9446
  `);
9305
- 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");
9306
9448
  try {
9307
- 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}
9308
9450
  `);
9309
9451
  } catch {
9310
9452
  }
@@ -9704,14 +9846,14 @@ function registerConfigShowCommand(program2) {
9704
9846
 
9705
9847
  // src/cli/commands/doctor.ts
9706
9848
  var import_chalk7 = __toESM(require("chalk"));
9707
- var import_fs20 = __toESM(require("fs"));
9708
- var import_path22 = __toESM(require("path"));
9709
- 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"));
9710
9852
  var import_child_process10 = require("child_process");
9711
9853
  init_daemon();
9712
9854
  function registerDoctorCommand(program2, version2) {
9713
9855
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
9714
- const homeDir2 = import_os16.default.homedir();
9856
+ const homeDir2 = import_os17.default.homedir();
9715
9857
  let failures = 0;
9716
9858
  function pass(msg) {
9717
9859
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -9760,10 +9902,10 @@ function registerDoctorCommand(program2, version2) {
9760
9902
  );
9761
9903
  }
9762
9904
  section("Configuration");
9763
- const globalConfigPath = import_path22.default.join(homeDir2, ".node9", "config.json");
9764
- if (import_fs20.default.existsSync(globalConfigPath)) {
9905
+ const globalConfigPath = import_path23.default.join(homeDir2, ".node9", "config.json");
9906
+ if (import_fs21.default.existsSync(globalConfigPath)) {
9765
9907
  try {
9766
- JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
9908
+ JSON.parse(import_fs21.default.readFileSync(globalConfigPath, "utf-8"));
9767
9909
  pass("~/.node9/config.json found and valid");
9768
9910
  } catch {
9769
9911
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -9771,10 +9913,10 @@ function registerDoctorCommand(program2, version2) {
9771
9913
  } else {
9772
9914
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
9773
9915
  }
9774
- const projectConfigPath = import_path22.default.join(process.cwd(), "node9.config.json");
9775
- 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)) {
9776
9918
  try {
9777
- JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
9919
+ JSON.parse(import_fs21.default.readFileSync(projectConfigPath, "utf-8"));
9778
9920
  pass("node9.config.json found and valid (project)");
9779
9921
  } catch {
9780
9922
  fail(
@@ -9783,8 +9925,8 @@ function registerDoctorCommand(program2, version2) {
9783
9925
  );
9784
9926
  }
9785
9927
  }
9786
- const credsPath = import_path22.default.join(homeDir2, ".node9", "credentials.json");
9787
- if (import_fs20.default.existsSync(credsPath)) {
9928
+ const credsPath = import_path23.default.join(homeDir2, ".node9", "credentials.json");
9929
+ if (import_fs21.default.existsSync(credsPath)) {
9788
9930
  pass("Cloud credentials found (~/.node9/credentials.json)");
9789
9931
  } else {
9790
9932
  warn(
@@ -9793,10 +9935,10 @@ function registerDoctorCommand(program2, version2) {
9793
9935
  );
9794
9936
  }
9795
9937
  section("Agent Hooks");
9796
- const claudeSettingsPath = import_path22.default.join(homeDir2, ".claude", "settings.json");
9797
- if (import_fs20.default.existsSync(claudeSettingsPath)) {
9938
+ const claudeSettingsPath = import_path23.default.join(homeDir2, ".claude", "settings.json");
9939
+ if (import_fs21.default.existsSync(claudeSettingsPath)) {
9798
9940
  try {
9799
- const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
9941
+ const cs = JSON.parse(import_fs21.default.readFileSync(claudeSettingsPath, "utf-8"));
9800
9942
  const hasHook = cs.hooks?.PreToolUse?.some(
9801
9943
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9802
9944
  );
@@ -9812,10 +9954,10 @@ function registerDoctorCommand(program2, version2) {
9812
9954
  } else {
9813
9955
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
9814
9956
  }
9815
- const geminiSettingsPath = import_path22.default.join(homeDir2, ".gemini", "settings.json");
9816
- if (import_fs20.default.existsSync(geminiSettingsPath)) {
9957
+ const geminiSettingsPath = import_path23.default.join(homeDir2, ".gemini", "settings.json");
9958
+ if (import_fs21.default.existsSync(geminiSettingsPath)) {
9817
9959
  try {
9818
- const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
9960
+ const gs = JSON.parse(import_fs21.default.readFileSync(geminiSettingsPath, "utf-8"));
9819
9961
  const hasHook = gs.hooks?.BeforeTool?.some(
9820
9962
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
9821
9963
  );
@@ -9831,10 +9973,10 @@ function registerDoctorCommand(program2, version2) {
9831
9973
  } else {
9832
9974
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
9833
9975
  }
9834
- const cursorHooksPath = import_path22.default.join(homeDir2, ".cursor", "hooks.json");
9835
- if (import_fs20.default.existsSync(cursorHooksPath)) {
9976
+ const cursorHooksPath = import_path23.default.join(homeDir2, ".cursor", "hooks.json");
9977
+ if (import_fs21.default.existsSync(cursorHooksPath)) {
9836
9978
  try {
9837
- const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
9979
+ const cur = JSON.parse(import_fs21.default.readFileSync(cursorHooksPath, "utf-8"));
9838
9980
  const hasHook = cur.hooks?.preToolUse?.some(
9839
9981
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
9840
9982
  );
@@ -9872,9 +10014,9 @@ function registerDoctorCommand(program2, version2) {
9872
10014
 
9873
10015
  // src/cli/commands/audit.ts
9874
10016
  var import_chalk8 = __toESM(require("chalk"));
9875
- var import_fs21 = __toESM(require("fs"));
9876
- var import_path23 = __toESM(require("path"));
9877
- 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"));
9878
10020
  function formatRelativeTime(timestamp) {
9879
10021
  const diff = Date.now() - new Date(timestamp).getTime();
9880
10022
  const sec = Math.floor(diff / 1e3);
@@ -9887,14 +10029,14 @@ function formatRelativeTime(timestamp) {
9887
10029
  }
9888
10030
  function registerAuditCommand(program2) {
9889
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) => {
9890
- const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9891
- 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)) {
9892
10034
  console.log(
9893
10035
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
9894
10036
  );
9895
10037
  return;
9896
10038
  }
9897
- const raw = import_fs21.default.readFileSync(logPath, "utf-8");
10039
+ const raw = import_fs22.default.readFileSync(logPath, "utf-8");
9898
10040
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
9899
10041
  let entries = lines.flatMap((line) => {
9900
10042
  try {
@@ -9946,8 +10088,396 @@ function registerAuditCommand(program2) {
9946
10088
  });
9947
10089
  }
9948
10090
 
9949
- // src/cli/commands/daemon-cmd.ts
10091
+ // src/cli/commands/report.ts
9950
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"));
9951
10481
  var import_child_process11 = require("child_process");
9952
10482
  init_daemon2();
9953
10483
  init_daemon();
@@ -9962,7 +10492,7 @@ function registerDaemonCommand(program2) {
9962
10492
  if (cmd === "status") return daemonStatus();
9963
10493
  if (cmd !== "start" && action !== void 0) {
9964
10494
  console.error(
9965
- 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`)
9966
10496
  );
9967
10497
  process.exit(1);
9968
10498
  }
@@ -9970,7 +10500,7 @@ function registerDaemonCommand(program2) {
9970
10500
  process.env.NODE9_WATCH_MODE = "1";
9971
10501
  setTimeout(() => {
9972
10502
  openBrowserLocal();
9973
- 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}/`));
9974
10504
  }, 600);
9975
10505
  startDaemon();
9976
10506
  return;
@@ -9978,7 +10508,7 @@ function registerDaemonCommand(program2) {
9978
10508
  if (options.openui) {
9979
10509
  if (isDaemonRunning()) {
9980
10510
  openBrowserLocal();
9981
- 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}/`));
9982
10512
  process.exit(0);
9983
10513
  }
9984
10514
  const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
@@ -9991,7 +10521,7 @@ function registerDaemonCommand(program2) {
9991
10521
  if (isDaemonRunning()) break;
9992
10522
  }
9993
10523
  openBrowserLocal();
9994
- console.log(import_chalk9.default.green(`
10524
+ console.log(import_chalk10.default.green(`
9995
10525
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
9996
10526
  process.exit(0);
9997
10527
  }
@@ -10001,7 +10531,7 @@ function registerDaemonCommand(program2) {
10001
10531
  stdio: "ignore"
10002
10532
  });
10003
10533
  child.unref();
10004
- console.log(import_chalk9.default.green(`
10534
+ console.log(import_chalk10.default.green(`
10005
10535
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
10006
10536
  process.exit(0);
10007
10537
  }
@@ -10011,15 +10541,15 @@ function registerDaemonCommand(program2) {
10011
10541
  }
10012
10542
 
10013
10543
  // src/cli/commands/status.ts
10014
- var import_chalk10 = __toESM(require("chalk"));
10015
- var import_fs22 = __toESM(require("fs"));
10016
- var import_path24 = __toESM(require("path"));
10017
- 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"));
10018
10548
  init_core();
10019
10549
  init_daemon();
10020
10550
  function readJson2(filePath) {
10021
10551
  try {
10022
- 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"));
10023
10553
  } catch {
10024
10554
  }
10025
10555
  return null;
@@ -10033,21 +10563,21 @@ function wrappedMcpServers(servers) {
10033
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(" ")}`);
10034
10564
  }
10035
10565
  function printAgentSection(label, hookPairs, wrapped) {
10036
- console.log(import_chalk10.default.bold(` ${label}`));
10566
+ console.log(import_chalk11.default.bold(` ${label}`));
10037
10567
  for (const { name, present } of hookPairs) {
10038
10568
  if (present) {
10039
- console.log(import_chalk10.default.green(` \u2713 ${name}`));
10569
+ console.log(import_chalk11.default.green(` \u2713 ${name}`));
10040
10570
  } else {
10041
- 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)"));
10042
10572
  }
10043
10573
  }
10044
10574
  if (wrapped.length > 0) {
10045
- console.log(import_chalk10.default.cyan(` MCP proxied:`));
10575
+ console.log(import_chalk11.default.cyan(` MCP proxied:`));
10046
10576
  for (const entry of wrapped) {
10047
- console.log(import_chalk10.default.gray(` \u2022 ${entry}`));
10577
+ console.log(import_chalk11.default.gray(` \u2022 ${entry}`));
10048
10578
  }
10049
10579
  } else {
10050
- console.log(import_chalk10.default.gray(` MCP proxied: none`));
10580
+ console.log(import_chalk11.default.gray(` MCP proxied: none`));
10051
10581
  }
10052
10582
  }
10053
10583
  function registerStatusCommand(program2) {
@@ -10058,58 +10588,58 @@ function registerStatusCommand(program2) {
10058
10588
  const settings = mergedConfig.settings;
10059
10589
  console.log("");
10060
10590
  if (creds && settings.approvers.cloud) {
10061
- 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"));
10062
10592
  } else if (creds && !settings.approvers.cloud) {
10063
10593
  console.log(
10064
- 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")
10065
10595
  );
10066
10596
  } else {
10067
10597
  console.log(
10068
- 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)")
10069
10599
  );
10070
10600
  }
10071
10601
  console.log("");
10072
10602
  if (daemonRunning) {
10073
10603
  console.log(
10074
- 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}/`)
10075
10605
  );
10076
10606
  } else {
10077
- console.log(import_chalk10.default.gray(" \u25CB Daemon stopped"));
10607
+ console.log(import_chalk11.default.gray(" \u25CB Daemon stopped"));
10078
10608
  }
10079
10609
  if (settings.enableUndo) {
10080
10610
  console.log(
10081
- 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`)
10082
10612
  );
10083
10613
  }
10084
10614
  console.log("");
10085
- 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");
10086
10616
  console.log(` Mode: ${modeLabel}`);
10087
- const projectConfig = import_path24.default.join(process.cwd(), "node9.config.json");
10088
- 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");
10089
10619
  console.log(
10090
- ` 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")}`
10091
10621
  );
10092
10622
  console.log(
10093
- ` 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")}`
10094
10624
  );
10095
10625
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10096
10626
  console.log(
10097
- ` 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`)}`
10098
10628
  );
10099
10629
  }
10100
- const homeDir2 = import_os18.default.homedir();
10630
+ const homeDir2 = import_os20.default.homedir();
10101
10631
  const claudeSettings = readJson2(
10102
- import_path24.default.join(homeDir2, ".claude", "settings.json")
10632
+ import_path26.default.join(homeDir2, ".claude", "settings.json")
10103
10633
  );
10104
- const claudeConfig = readJson2(import_path24.default.join(homeDir2, ".claude.json"));
10634
+ const claudeConfig = readJson2(import_path26.default.join(homeDir2, ".claude.json"));
10105
10635
  const geminiSettings = readJson2(
10106
- import_path24.default.join(homeDir2, ".gemini", "settings.json")
10636
+ import_path26.default.join(homeDir2, ".gemini", "settings.json")
10107
10637
  );
10108
- const cursorConfig = readJson2(import_path24.default.join(homeDir2, ".cursor", "mcp.json"));
10638
+ const cursorConfig = readJson2(import_path26.default.join(homeDir2, ".cursor", "mcp.json"));
10109
10639
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10110
10640
  if (agentFound) {
10111
10641
  console.log("");
10112
- console.log(import_chalk10.default.bold(" Agent Wiring:"));
10642
+ console.log(import_chalk11.default.bold(" Agent Wiring:"));
10113
10643
  console.log("");
10114
10644
  if (claudeSettings || claudeConfig) {
10115
10645
  const preHook = claudeSettings?.hooks?.PreToolUse?.some(
@@ -10155,7 +10685,7 @@ function registerStatusCommand(program2) {
10155
10685
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
10156
10686
  console.log("");
10157
10687
  console.log(
10158
- 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")
10159
10689
  );
10160
10690
  }
10161
10691
  console.log("");
@@ -10163,10 +10693,10 @@ function registerStatusCommand(program2) {
10163
10693
  }
10164
10694
 
10165
10695
  // src/cli/commands/init.ts
10166
- var import_chalk11 = __toESM(require("chalk"));
10167
- var import_fs23 = __toESM(require("fs"));
10168
- var import_path25 = __toESM(require("path"));
10169
- 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"));
10170
10700
  var import_https2 = __toESM(require("https"));
10171
10701
  init_core();
10172
10702
  init_shields();
@@ -10202,7 +10732,7 @@ function fireTelemetryPing(agents) {
10202
10732
  }
10203
10733
  function registerInitCommand(program2) {
10204
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) => {
10205
- 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"));
10206
10736
  let chosenMode = options.mode.toLowerCase();
10207
10737
  if (!["standard", "strict", "audit"].includes(chosenMode)) {
10208
10738
  chosenMode = DEFAULT_CONFIG.settings.mode;
@@ -10221,37 +10751,37 @@ function registerInitCommand(program2) {
10221
10751
  const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
10222
10752
  if (hasNewShields) writeActiveShields(merged);
10223
10753
  } catch (err2) {
10224
- 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)}`));
10225
10755
  }
10226
10756
  }
10227
10757
  console.log("");
10228
10758
  }
10229
- const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
10230
- 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) {
10231
10761
  try {
10232
- const existing = JSON.parse(import_fs23.default.readFileSync(configPath, "utf-8"));
10762
+ const existing = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
10233
10763
  const settings = existing.settings ?? {};
10234
10764
  if (settings.mode !== chosenMode) {
10235
10765
  settings.mode = chosenMode;
10236
10766
  existing.settings = settings;
10237
- import_fs23.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
10238
- 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}`));
10239
10769
  } else {
10240
- 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}`));
10241
10771
  }
10242
10772
  } catch {
10243
- 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}`));
10244
10774
  }
10245
10775
  } else {
10246
10776
  const configToSave = {
10247
10777
  ...DEFAULT_CONFIG,
10248
10778
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
10249
10779
  };
10250
- const dir = import_path25.default.dirname(configPath);
10251
- if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
10252
- import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
10253
- console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
10254
- 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}`));
10255
10785
  }
10256
10786
  if (options.skipSetup) return;
10257
10787
  console.log("");
@@ -10261,18 +10791,18 @@ function registerInitCommand(program2) {
10261
10791
  );
10262
10792
  if (found.length === 0) {
10263
10793
  console.log(
10264
- 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")
10265
10795
  );
10266
- 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>"));
10267
10797
  return;
10268
10798
  }
10269
- console.log(import_chalk11.default.bold("Detected agents:"));
10799
+ console.log(import_chalk12.default.bold("Detected agents:"));
10270
10800
  for (const agent of found) {
10271
- console.log(import_chalk11.default.green(` \u2713 ${agent}`));
10801
+ console.log(import_chalk12.default.green(` \u2713 ${agent}`));
10272
10802
  }
10273
10803
  console.log("");
10274
10804
  for (const agent of found) {
10275
- console.log(import_chalk11.default.bold(`Wiring ${agent}...`));
10805
+ console.log(import_chalk12.default.bold(`Wiring ${agent}...`));
10276
10806
  if (agent === "claude") await setupClaude();
10277
10807
  else if (agent === "gemini") await setupGemini();
10278
10808
  else if (agent === "cursor") await setupCursor();
@@ -10289,26 +10819,26 @@ function registerInitCommand(program2) {
10289
10819
  console.log("");
10290
10820
  }
10291
10821
  const agentList = found.join(", ");
10292
- 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}!`));
10293
10823
  console.log("");
10294
- console.log(import_chalk11.default.white(" Watch live: ") + import_chalk11.default.cyan("node9 tail"));
10295
- 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"));
10296
10826
  console.log("");
10297
- 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"));
10298
10828
  console.log(
10299
- 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")
10300
10830
  );
10301
- 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"));
10302
10832
  });
10303
10833
  }
10304
10834
 
10305
10835
  // src/cli/commands/undo.ts
10306
- var import_path26 = __toESM(require("path"));
10307
- var import_chalk13 = __toESM(require("chalk"));
10836
+ var import_path28 = __toESM(require("path"));
10837
+ var import_chalk14 = __toESM(require("chalk"));
10308
10838
 
10309
10839
  // src/tui/undo-navigator.ts
10310
10840
  var import_readline2 = __toESM(require("readline"));
10311
- var import_chalk12 = __toESM(require("chalk"));
10841
+ var import_chalk13 = __toESM(require("chalk"));
10312
10842
  var RESET = "\x1B[0m";
10313
10843
  var BOLD = "\x1B[1m";
10314
10844
  var CLEAR_SCREEN = "\x1B[2J\x1B[H";
@@ -10326,15 +10856,15 @@ function renderDiff(raw) {
10326
10856
  );
10327
10857
  for (const line of lines) {
10328
10858
  if (line.startsWith("+++") || line.startsWith("---")) {
10329
- process.stdout.write(import_chalk12.default.bold(line) + "\n");
10859
+ process.stdout.write(import_chalk13.default.bold(line) + "\n");
10330
10860
  } else if (line.startsWith("+")) {
10331
- process.stdout.write(import_chalk12.default.green(line) + "\n");
10861
+ process.stdout.write(import_chalk13.default.green(line) + "\n");
10332
10862
  } else if (line.startsWith("-")) {
10333
- process.stdout.write(import_chalk12.default.red(line) + "\n");
10863
+ process.stdout.write(import_chalk13.default.red(line) + "\n");
10334
10864
  } else if (line.startsWith("@@")) {
10335
- process.stdout.write(import_chalk12.default.cyan(line) + "\n");
10865
+ process.stdout.write(import_chalk13.default.cyan(line) + "\n");
10336
10866
  } else {
10337
- process.stdout.write(import_chalk12.default.gray(line) + "\n");
10867
+ process.stdout.write(import_chalk13.default.gray(line) + "\n");
10338
10868
  }
10339
10869
  }
10340
10870
  }
@@ -10353,23 +10883,23 @@ function render(entries, idx) {
10353
10883
  const step = idx + 1;
10354
10884
  process.stdout.write(CLEAR_SCREEN);
10355
10885
  process.stdout.write(
10356
- 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(
10357
10887
  ` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
10358
10888
  ) : "") + "\n\n"
10359
10889
  );
10360
10890
  process.stdout.write(
10361
- ` ${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"
10362
10892
  );
10363
- 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))}
10364
10894
  `);
10365
- 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)}
10366
10896
  `);
10367
10897
  if (entry.files && entry.files.length > 0) {
10368
- 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(", "))}
10369
10899
  `);
10370
10900
  }
10371
10901
  if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
10372
- 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"));
10373
10903
  }
10374
10904
  process.stdout.write("\n");
10375
10905
  const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
@@ -10377,12 +10907,12 @@ function render(entries, idx) {
10377
10907
  renderDiff(diff);
10378
10908
  } else {
10379
10909
  process.stdout.write(
10380
- 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")
10381
10911
  );
10382
10912
  }
10383
10913
  process.stdout.write("\n");
10384
10914
  process.stdout.write(
10385
- 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"
10386
10916
  );
10387
10917
  }
10388
10918
  async function runUndoNavigator(entries) {
@@ -10436,19 +10966,19 @@ async function runUndoNavigator(entries) {
10436
10966
  cleanup();
10437
10967
  process.stdout.write(CLEAR_SCREEN);
10438
10968
  const entry = display[idx];
10439
- 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"));
10440
10970
  if (applyUndo(entry.hash, entry.cwd)) {
10441
- 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"));
10442
10972
  resolve({ restored: true });
10443
10973
  } else {
10444
- 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"));
10445
10975
  resolve({ restored: false });
10446
10976
  }
10447
10977
  } else if (name === "q" || key?.ctrl && name === "c") {
10448
10978
  done = true;
10449
10979
  cleanup();
10450
10980
  process.stdout.write(CLEAR_SCREEN);
10451
- process.stdout.write(import_chalk12.default.gray("\nCancelled.\n\n"));
10981
+ process.stdout.write(import_chalk13.default.gray("\nCancelled.\n\n"));
10452
10982
  resolve({ restored: false });
10453
10983
  }
10454
10984
  };
@@ -10462,7 +10992,7 @@ function findMatchingCwd(startDir, history) {
10462
10992
  let dir = startDir;
10463
10993
  while (true) {
10464
10994
  if (cwds.has(dir)) return dir;
10465
- const parent = import_path26.default.dirname(dir);
10995
+ const parent = import_path28.default.dirname(dir);
10466
10996
  if (parent === dir) return null;
10467
10997
  dir = parent;
10468
10998
  }
@@ -10484,39 +11014,39 @@ function registerUndoCommand(program2) {
10484
11014
  if (history.length === 0) {
10485
11015
  if (!options.all && allHistory.length > 0) {
10486
11016
  console.log(
10487
- import_chalk13.default.yellow(
11017
+ import_chalk14.default.yellow(
10488
11018
  `
10489
11019
  \u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
10490
- 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.
10491
11021
  `
10492
11022
  )
10493
11023
  );
10494
11024
  } else {
10495
- 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"));
10496
11026
  }
10497
11027
  return;
10498
11028
  }
10499
11029
  if (options.list) {
10500
- 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"));
10501
11031
  console.log(
10502
- import_chalk13.default.gray(
11032
+ import_chalk14.default.gray(
10503
11033
  ` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
10504
11034
  )
10505
11035
  );
10506
- console.log(import_chalk13.default.gray(" " + "\u2500".repeat(80)));
11036
+ console.log(import_chalk14.default.gray(" " + "\u2500".repeat(80)));
10507
11037
  const display = [...history].reverse();
10508
11038
  let prevTs = null;
10509
11039
  for (let i = 0; i < display.length; i++) {
10510
11040
  const e = display[i];
10511
11041
  const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
10512
- 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"));
10513
11043
  const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
10514
11044
  const tool = e.tool.slice(0, 8).padEnd(8);
10515
11045
  const when = formatAge2(e.timestamp).padEnd(10);
10516
11046
  const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
10517
11047
  console.log(
10518
- import_chalk13.default.white(
10519
- ` ${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)}`
10520
11050
  )
10521
11051
  );
10522
11052
  prevTs = e.timestamp;
@@ -10529,7 +11059,7 @@ function registerUndoCommand(program2) {
10529
11059
  const idx = history.length - steps;
10530
11060
  if (idx < 0) {
10531
11061
  console.log(
10532
- import_chalk13.default.yellow(
11062
+ import_chalk14.default.yellow(
10533
11063
  `
10534
11064
  \u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
10535
11065
  `
@@ -10540,47 +11070,47 @@ function registerUndoCommand(program2) {
10540
11070
  const snapshot = history[idx];
10541
11071
  const ageStr = formatAge2(snapshot.timestamp);
10542
11072
  console.log(
10543
- import_chalk13.default.magenta.bold(`
11073
+ import_chalk14.default.magenta.bold(`
10544
11074
  \u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
10545
11075
  );
10546
11076
  console.log(
10547
- import_chalk13.default.white(
10548
- ` 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) : ""}`
10549
11079
  )
10550
11080
  );
10551
- console.log(import_chalk13.default.white(` When: ${import_chalk13.default.gray(ageStr)}`));
10552
- 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)}`));
10553
11083
  if (steps > 1)
10554
11084
  console.log(
10555
- 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.`)
10556
11086
  );
10557
11087
  console.log("");
10558
11088
  const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
10559
11089
  if (diff) {
10560
11090
  const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
10561
11091
  for (const line of lines) {
10562
- if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk13.default.bold(line));
10563
- else if (line.startsWith("+")) console.log(import_chalk13.default.green(line));
10564
- else if (line.startsWith("-")) console.log(import_chalk13.default.red(line));
10565
- else if (line.startsWith("@@")) console.log(import_chalk13.default.cyan(line));
10566
- 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));
10567
11097
  }
10568
11098
  console.log("");
10569
11099
  } else {
10570
11100
  console.log(
10571
- 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")
10572
11102
  );
10573
11103
  }
10574
11104
  const { confirm: confirm3 } = await import("@inquirer/prompts");
10575
11105
  const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
10576
11106
  if (proceed) {
10577
11107
  if (applyUndo(snapshot.hash, snapshot.cwd)) {
10578
- console.log(import_chalk13.default.green("\n\u2705 Reverted successfully.\n"));
11108
+ console.log(import_chalk14.default.green("\n\u2705 Reverted successfully.\n"));
10579
11109
  } else {
10580
- 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"));
10581
11111
  }
10582
11112
  } else {
10583
- console.log(import_chalk13.default.gray("\nCancelled.\n"));
11113
+ console.log(import_chalk14.default.gray("\nCancelled.\n"));
10584
11114
  }
10585
11115
  return;
10586
11116
  }
@@ -10589,7 +11119,7 @@ function registerUndoCommand(program2) {
10589
11119
  }
10590
11120
 
10591
11121
  // src/cli/commands/watch.ts
10592
- var import_chalk14 = __toESM(require("chalk"));
11122
+ var import_chalk15 = __toESM(require("chalk"));
10593
11123
  var import_child_process12 = require("child_process");
10594
11124
  init_daemon();
10595
11125
  function registerWatchCommand(program2) {
@@ -10606,7 +11136,7 @@ function registerWatchCommand(program2) {
10606
11136
  throw new Error("not running");
10607
11137
  }
10608
11138
  } catch {
10609
- 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)..."));
10610
11140
  const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10611
11141
  detached: true,
10612
11142
  stdio: "ignore",
@@ -10628,12 +11158,12 @@ function registerWatchCommand(program2) {
10628
11158
  }
10629
11159
  }
10630
11160
  if (!ready) {
10631
- 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"));
10632
11162
  process.exit(1);
10633
11163
  }
10634
11164
  }
10635
11165
  console.error(
10636
- 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(
10637
11167
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
10638
11168
  )
10639
11169
  );
@@ -10642,7 +11172,7 @@ function registerWatchCommand(program2) {
10642
11172
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
10643
11173
  });
10644
11174
  if (result.error) {
10645
- 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}`));
10646
11176
  process.exit(1);
10647
11177
  }
10648
11178
  process.exit(result.status ?? 0);
@@ -10651,19 +11181,19 @@ function registerWatchCommand(program2) {
10651
11181
 
10652
11182
  // src/mcp-gateway/index.ts
10653
11183
  var import_readline3 = __toESM(require("readline"));
10654
- var import_chalk15 = __toESM(require("chalk"));
11184
+ var import_chalk16 = __toESM(require("chalk"));
10655
11185
  var import_child_process13 = require("child_process");
10656
11186
  var import_execa3 = require("execa");
10657
11187
  init_orchestrator();
10658
11188
  init_provenance();
10659
11189
 
10660
11190
  // src/mcp-pin.ts
10661
- var import_fs24 = __toESM(require("fs"));
10662
- var import_path27 = __toESM(require("path"));
10663
- var import_os20 = __toESM(require("os"));
10664
- 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"));
10665
11195
  function getPinsFilePath() {
10666
- 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");
10667
11197
  }
10668
11198
  function hashToolDefinitions(tools) {
10669
11199
  const sorted = [...tools].sort((a, b) => {
@@ -10672,15 +11202,15 @@ function hashToolDefinitions(tools) {
10672
11202
  return nameA.localeCompare(nameB);
10673
11203
  });
10674
11204
  const canonical = JSON.stringify(sorted);
10675
- return import_crypto8.default.createHash("sha256").update(canonical).digest("hex");
11205
+ return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
10676
11206
  }
10677
11207
  function getServerKey(upstreamCommand) {
10678
- 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);
10679
11209
  }
10680
11210
  function readMcpPinsSafe() {
10681
11211
  const filePath = getPinsFilePath();
10682
11212
  try {
10683
- const raw = import_fs24.default.readFileSync(filePath, "utf-8");
11213
+ const raw = import_fs26.default.readFileSync(filePath, "utf-8");
10684
11214
  if (!raw.trim()) {
10685
11215
  return { ok: false, reason: "corrupt", detail: "empty file" };
10686
11216
  }
@@ -10704,10 +11234,10 @@ function readMcpPins() {
10704
11234
  }
10705
11235
  function writeMcpPins(data) {
10706
11236
  const filePath = getPinsFilePath();
10707
- import_fs24.default.mkdirSync(import_path27.default.dirname(filePath), { recursive: true });
10708
- const tmp = `${filePath}.${import_crypto8.default.randomBytes(6).toString("hex")}.tmp`;
10709
- import_fs24.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10710
- 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);
10711
11241
  }
10712
11242
  function checkPin(serverKey, currentHash) {
10713
11243
  const result = readMcpPinsSafe();
@@ -10799,13 +11329,13 @@ async function runMcpGateway(upstreamCommand) {
10799
11329
  const prov = checkProvenance(executable);
10800
11330
  if (prov.trustLevel === "suspect") {
10801
11331
  console.error(
10802
- import_chalk15.default.red(
11332
+ import_chalk16.default.red(
10803
11333
  `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
10804
11334
  )
10805
11335
  );
10806
- 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."));
10807
11337
  }
10808
- 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}]`));
10809
11339
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
10810
11340
  "NODE_OPTIONS",
10811
11341
  "NODE_PATH",
@@ -10908,10 +11438,10 @@ async function runMcpGateway(upstreamCommand) {
10908
11438
  mcpServer
10909
11439
  });
10910
11440
  if (!result.approved) {
10911
- console.error(import_chalk15.default.red(`
11441
+ console.error(import_chalk16.default.red(`
10912
11442
  \u{1F6D1} Node9 MCP Gateway: Action Blocked`));
10913
- console.error(import_chalk15.default.gray(` Tool: ${toolName}`));
10914
- 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"}
10915
11445
  `));
10916
11446
  const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
10917
11447
  const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
@@ -10990,7 +11520,7 @@ async function runMcpGateway(upstreamCommand) {
10990
11520
  updatePin(serverKey, upstreamCommand, currentHash, toolNames);
10991
11521
  pinState = "validated";
10992
11522
  console.error(
10993
- import_chalk15.default.green(
11523
+ import_chalk16.default.green(
10994
11524
  `\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
10995
11525
  )
10996
11526
  );
@@ -11003,11 +11533,11 @@ async function runMcpGateway(upstreamCommand) {
11003
11533
  } else if (pinStatus === "corrupt") {
11004
11534
  pinState = "quarantined";
11005
11535
  console.error(
11006
- 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!")
11007
11537
  );
11008
- 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."));
11009
11539
  console.error(
11010
- 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)
11011
11541
  `)
11012
11542
  );
11013
11543
  const errorResponse = {
@@ -11024,13 +11554,13 @@ async function runMcpGateway(upstreamCommand) {
11024
11554
  } else {
11025
11555
  pinState = "quarantined";
11026
11556
  console.error(
11027
- 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!")
11028
11558
  );
11029
11559
  console.error(
11030
- 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).")
11031
11561
  );
11032
- console.error(import_chalk15.default.red(" Session quarantined \u2014 all tool calls blocked."));
11033
- 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}
11034
11564
  `));
11035
11565
  const errorResponse = {
11036
11566
  jsonrpc: "2.0",
@@ -11079,9 +11609,9 @@ function registerMcpGatewayCommand(program2) {
11079
11609
 
11080
11610
  // src/mcp-server/index.ts
11081
11611
  var import_readline4 = __toESM(require("readline"));
11082
- var import_fs25 = __toESM(require("fs"));
11083
- var import_os21 = __toESM(require("os"));
11084
- 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"));
11085
11615
  init_core();
11086
11616
  init_daemon();
11087
11617
  init_shields();
@@ -11256,13 +11786,13 @@ function handleStatus() {
11256
11786
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
11257
11787
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
11258
11788
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
11259
- const projectConfig = import_path28.default.join(process.cwd(), "node9.config.json");
11260
- 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");
11261
11791
  lines.push(
11262
- `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"}`
11263
11793
  );
11264
11794
  lines.push(
11265
- `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"}`
11266
11796
  );
11267
11797
  return lines.join("\n");
11268
11798
  }
@@ -11336,21 +11866,21 @@ function handleShieldDisable(args) {
11336
11866
  writeActiveShields(active.filter((s) => s !== name));
11337
11867
  return `Shield "${name}" disabled.`;
11338
11868
  }
11339
- 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");
11340
11870
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
11341
11871
  function readGlobalConfigRaw() {
11342
11872
  try {
11343
- if (import_fs25.default.existsSync(GLOBAL_CONFIG_PATH2)) {
11344
- 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"));
11345
11875
  }
11346
11876
  } catch {
11347
11877
  }
11348
11878
  return {};
11349
11879
  }
11350
11880
  function writeGlobalConfigRaw(data) {
11351
- const dir = import_path28.default.dirname(GLOBAL_CONFIG_PATH2);
11352
- if (!import_fs25.default.existsSync(dir)) import_fs25.default.mkdirSync(dir, { recursive: true });
11353
- 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");
11354
11884
  }
11355
11885
  function handleApproverList() {
11356
11886
  const config = getConfig();
@@ -11393,9 +11923,9 @@ function handleApproverSet(args) {
11393
11923
  }
11394
11924
  function handleAuditGet(args) {
11395
11925
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
11396
- const auditPath = import_path28.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11397
- if (!import_fs25.default.existsSync(auditPath)) return "No audit log found.";
11398
- 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);
11399
11929
  const recent = lines.slice(-limit);
11400
11930
  const entries = recent.map((line) => {
11401
11931
  try {
@@ -11582,7 +12112,7 @@ function registerMcpServerCommand(program2) {
11582
12112
  }
11583
12113
 
11584
12114
  // src/cli/commands/trust.ts
11585
- var import_chalk16 = __toESM(require("chalk"));
12115
+ var import_chalk17 = __toESM(require("chalk"));
11586
12116
  init_trusted_hosts();
11587
12117
  function isValidHost(host) {
11588
12118
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
@@ -11593,51 +12123,51 @@ function registerTrustCommand(program2) {
11593
12123
  const normalized = normalizeHost(host.trim());
11594
12124
  if (!isValidHost(normalized)) {
11595
12125
  console.error(
11596
- import_chalk16.default.red(`
12126
+ import_chalk17.default.red(`
11597
12127
  \u274C Invalid host: "${host}"
11598
- `) + 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")
11599
12129
  );
11600
12130
  process.exit(1);
11601
12131
  }
11602
12132
  addTrustedHost(normalized);
11603
- console.log(import_chalk16.default.green(`
12133
+ console.log(import_chalk17.default.green(`
11604
12134
  \u2705 ${normalized} added to trusted hosts.`));
11605
12135
  console.log(
11606
- 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")
11607
12137
  );
11608
12138
  });
11609
12139
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
11610
12140
  const normalized = normalizeHost(host.trim());
11611
12141
  const removed = removeTrustedHost(normalized);
11612
12142
  if (!removed) {
11613
- console.error(import_chalk16.default.yellow(`
12143
+ console.error(import_chalk17.default.yellow(`
11614
12144
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
11615
12145
  `));
11616
12146
  process.exit(1);
11617
12147
  }
11618
- console.log(import_chalk16.default.green(`
12148
+ console.log(import_chalk17.default.green(`
11619
12149
  \u2705 ${normalized} removed from trusted hosts.
11620
12150
  `));
11621
12151
  });
11622
12152
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
11623
12153
  const hosts = readTrustedHosts();
11624
12154
  if (hosts.length === 0) {
11625
- console.log(import_chalk16.default.gray("\n No trusted hosts configured.\n"));
11626
- 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")}
11627
12157
  `);
11628
12158
  return;
11629
12159
  }
11630
- 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"));
11631
12161
  for (const entry of hosts) {
11632
12162
  const date = new Date(entry.addedAt).toLocaleDateString();
11633
- 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}`)}`);
11634
12164
  }
11635
12165
  console.log("");
11636
12166
  });
11637
12167
  }
11638
12168
 
11639
12169
  // src/cli/commands/mcp-pin.ts
11640
- var import_chalk17 = __toESM(require("chalk"));
12170
+ var import_chalk18 = __toESM(require("chalk"));
11641
12171
  function registerMcpPinCommand(program2) {
11642
12172
  const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
11643
12173
  const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
@@ -11645,31 +12175,31 @@ function registerMcpPinCommand(program2) {
11645
12175
  const result = readMcpPinsSafe();
11646
12176
  if (!result.ok) {
11647
12177
  if (result.reason === "missing") {
11648
- 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."));
11649
12179
  console.log(
11650
- 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")
11651
12181
  );
11652
12182
  return;
11653
12183
  }
11654
- console.error(import_chalk17.default.red(`
12184
+ console.error(import_chalk18.default.red(`
11655
12185
  \u274C Pin file is corrupt: ${result.detail}`));
11656
- 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"));
11657
12187
  process.exit(1);
11658
12188
  }
11659
12189
  const entries = Object.entries(result.pins.servers);
11660
12190
  if (entries.length === 0) {
11661
- 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."));
11662
12192
  console.log(
11663
- 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")
11664
12194
  );
11665
12195
  return;
11666
12196
  }
11667
- 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"));
11668
12198
  for (const [key, entry] of entries) {
11669
- console.log(` ${import_chalk17.default.cyan(key)} ${import_chalk17.default.gray(entry.label)}`);
11670
- console.log(` Tools (${entry.toolCount}): ${import_chalk17.default.white(entry.toolNames.join(", "))}`);
11671
- console.log(` Hash: ${import_chalk17.default.gray(entry.toolsHash.slice(0, 16))}...`);
11672
- 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)}`);
11673
12203
  console.log("");
11674
12204
  }
11675
12205
  });
@@ -11680,55 +12210,55 @@ function registerMcpPinCommand(program2) {
11680
12210
  try {
11681
12211
  pins = readMcpPins();
11682
12212
  } catch {
11683
- console.error(import_chalk17.default.red("\n\u274C Pin file is corrupt."));
11684
- 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"));
11685
12215
  process.exit(1);
11686
12216
  }
11687
12217
  if (!pins.servers[serverKey]) {
11688
- console.error(import_chalk17.default.red(`
12218
+ console.error(import_chalk18.default.red(`
11689
12219
  \u274C No pin found for server key "${serverKey}"
11690
12220
  `));
11691
- 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.
11692
12222
  `);
11693
12223
  process.exit(1);
11694
12224
  }
11695
12225
  const label = pins.servers[serverKey].label;
11696
12226
  removePin(serverKey);
11697
- console.log(import_chalk17.default.green(`
11698
- \u{1F513} Pin removed for ${import_chalk17.default.cyan(serverKey)}`));
11699
- console.log(import_chalk17.default.gray(` Server: ${label}`));
11700
- 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"));
11701
12231
  });
11702
12232
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
11703
12233
  const result = readMcpPinsSafe();
11704
12234
  if (!result.ok && result.reason === "missing") {
11705
- console.log(import_chalk17.default.gray("\nNo pins to clear.\n"));
12235
+ console.log(import_chalk18.default.gray("\nNo pins to clear.\n"));
11706
12236
  return;
11707
12237
  }
11708
12238
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
11709
12239
  clearAllPins();
11710
- console.log(import_chalk17.default.green(`
12240
+ console.log(import_chalk18.default.green(`
11711
12241
  \u{1F513} Cleared ${count} MCP pin(s).`));
11712
- 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"));
11713
12243
  });
11714
12244
  }
11715
12245
 
11716
12246
  // src/cli.ts
11717
12247
  var { version } = JSON.parse(
11718
- 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")
11719
12249
  );
11720
12250
  var program = new import_commander.Command();
11721
12251
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
11722
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) => {
11723
12253
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
11724
- const credPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "credentials.json");
11725
- if (!import_fs28.default.existsSync(import_path31.default.dirname(credPath)))
11726
- 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 });
11727
12257
  const profileName = options.profile || "default";
11728
12258
  let existingCreds = {};
11729
12259
  try {
11730
- if (import_fs28.default.existsSync(credPath)) {
11731
- 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"));
11732
12262
  if (raw.apiKey) {
11733
12263
  existingCreds = {
11734
12264
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -11740,13 +12270,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11740
12270
  } catch {
11741
12271
  }
11742
12272
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
11743
- 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 });
11744
12274
  if (profileName === "default") {
11745
- 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");
11746
12276
  let config = {};
11747
12277
  try {
11748
- if (import_fs28.default.existsSync(configPath))
11749
- 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"));
11750
12280
  } catch {
11751
12281
  }
11752
12282
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -11761,19 +12291,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
11761
12291
  approvers.cloud = false;
11762
12292
  }
11763
12293
  s.approvers = approvers;
11764
- if (!import_fs28.default.existsSync(import_path31.default.dirname(configPath)))
11765
- import_fs28.default.mkdirSync(import_path31.default.dirname(configPath), { recursive: true });
11766
- 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 });
11767
12297
  }
11768
12298
  if (options.profile && profileName !== "default") {
11769
- console.log(import_chalk19.default.green(`\u2705 Profile "${profileName}" saved`));
11770
- 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`));
11771
12301
  } else if (options.local) {
11772
- console.log(import_chalk19.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
11773
- 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.`));
11774
12304
  } else {
11775
- console.log(import_chalk19.default.green(`\u2705 Logged in \u2014 agent mode`));
11776
- 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.`));
11777
12307
  }
11778
12308
  });
11779
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) => {
@@ -11781,19 +12311,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
11781
12311
  if (target === "claude") return await setupClaude();
11782
12312
  if (target === "cursor") return await setupCursor();
11783
12313
  if (target === "hud") return setupHud();
11784
- 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`));
11785
12315
  process.exit(1);
11786
12316
  });
11787
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) => {
11788
12318
  if (!target) {
11789
- console.log(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
11790
- 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");
11791
12321
  console.log(" Targets:");
11792
- console.log(" " + import_chalk19.default.green("claude") + " \u2014 Claude Code (hook mode)");
11793
- console.log(" " + import_chalk19.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
11794
- 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)");
11795
12325
  process.stdout.write(
11796
- " " + import_chalk19.default.green("hud") + " \u2014 Claude Code security statusline\n"
12326
+ " " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
11797
12327
  );
11798
12328
  console.log("");
11799
12329
  return;
@@ -11803,7 +12333,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
11803
12333
  if (t === "claude") return await setupClaude();
11804
12334
  if (t === "cursor") return await setupCursor();
11805
12335
  if (t === "hud") return setupHud();
11806
- 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`));
11807
12337
  process.exit(1);
11808
12338
  });
11809
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) => {
@@ -11814,31 +12344,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
11814
12344
  else if (target === "hud") fn = teardownHud;
11815
12345
  else {
11816
12346
  console.error(
11817
- 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`)
11818
12348
  );
11819
12349
  process.exit(1);
11820
12350
  }
11821
- console.log(import_chalk19.default.cyan(`
12351
+ console.log(import_chalk20.default.cyan(`
11822
12352
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
11823
12353
  `));
11824
12354
  try {
11825
12355
  fn();
11826
12356
  } catch (err2) {
11827
- 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)}`));
11828
12358
  process.exit(1);
11829
12359
  }
11830
- 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."));
11831
12361
  });
11832
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) => {
11833
- console.log(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
11834
- 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..."));
11835
12365
  try {
11836
12366
  stopDaemon();
11837
- console.log(import_chalk19.default.green(" \u2705 Daemon stopped"));
12367
+ console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
11838
12368
  } catch {
11839
- 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"));
11840
12370
  }
11841
- console.log(import_chalk19.default.bold("\nRemoving hooks..."));
12371
+ console.log(import_chalk20.default.bold("\nRemoving hooks..."));
11842
12372
  let teardownFailed = false;
11843
12373
  for (const [label, fn] of [
11844
12374
  ["Claude", teardownClaude],
@@ -11850,45 +12380,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
11850
12380
  } catch (err2) {
11851
12381
  teardownFailed = true;
11852
12382
  console.error(
11853
- import_chalk19.default.red(
12383
+ import_chalk20.default.red(
11854
12384
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
11855
12385
  )
11856
12386
  );
11857
12387
  }
11858
12388
  }
11859
12389
  if (options.purge) {
11860
- const node9Dir = import_path31.default.join(import_os24.default.homedir(), ".node9");
11861
- 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)) {
11862
12392
  const confirmed = await (0, import_prompts2.confirm)({
11863
12393
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
11864
12394
  default: false
11865
12395
  });
11866
12396
  if (confirmed) {
11867
- import_fs28.default.rmSync(node9Dir, { recursive: true });
11868
- if (import_fs28.default.existsSync(node9Dir)) {
12397
+ import_fs30.default.rmSync(node9Dir, { recursive: true });
12398
+ if (import_fs30.default.existsSync(node9Dir)) {
11869
12399
  console.error(
11870
- 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.")
11871
12401
  );
11872
12402
  } else {
11873
- 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)"));
11874
12404
  }
11875
12405
  } else {
11876
- 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."));
11877
12407
  }
11878
12408
  } else {
11879
- 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"));
11880
12410
  }
11881
12411
  } else {
11882
12412
  console.log(
11883
- 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")
11884
12414
  );
11885
12415
  }
11886
12416
  if (teardownFailed) {
11887
- 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."));
11888
12418
  process.exit(1);
11889
12419
  }
11890
- console.log(import_chalk19.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
11891
- 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"));
11892
12422
  });
11893
12423
  registerDoctorCommand(program, version);
11894
12424
  program.command("explain").description(
@@ -11901,7 +12431,7 @@ program.command("explain").description(
11901
12431
  try {
11902
12432
  args = JSON.parse(trimmed);
11903
12433
  } catch {
11904
- console.error(import_chalk19.default.red(`
12434
+ console.error(import_chalk20.default.red(`
11905
12435
  \u274C Invalid JSON: ${trimmed}
11906
12436
  `));
11907
12437
  process.exit(1);
@@ -11912,60 +12442,61 @@ program.command("explain").description(
11912
12442
  }
11913
12443
  const result = await explainPolicy(tool, args);
11914
12444
  console.log("");
11915
- 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"));
11916
12446
  console.log("");
11917
- 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)}`);
11918
12448
  if (argsRaw) {
11919
12449
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
11920
- 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)}`);
11921
12451
  }
11922
12452
  console.log("");
11923
- console.log(import_chalk19.default.bold("Config Sources (Waterfall):"));
12453
+ console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
11924
12454
  for (const tier of result.waterfall) {
11925
- const num = import_chalk19.default.gray(` ${tier.tier}.`);
12455
+ const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
11926
12456
  const label = tier.label.padEnd(16);
11927
12457
  let statusStr;
11928
12458
  if (tier.tier === 1) {
11929
- statusStr = import_chalk19.default.gray(tier.note ?? "");
12459
+ statusStr = import_chalk20.default.gray(tier.note ?? "");
11930
12460
  } else if (tier.status === "active") {
11931
- const loc = tier.path ? import_chalk19.default.gray(tier.path) : "";
11932
- const note = tier.note ? import_chalk19.default.gray(`(${tier.note})`) : "";
11933
- 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 : "");
11934
12464
  } else {
11935
- statusStr = import_chalk19.default.gray("\u25CB " + (tier.note ?? "not found"));
12465
+ statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
11936
12466
  }
11937
- console.log(`${num} ${import_chalk19.default.white(label)} ${statusStr}`);
12467
+ console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
11938
12468
  }
11939
12469
  console.log("");
11940
- console.log(import_chalk19.default.bold("Policy Evaluation:"));
12470
+ console.log(import_chalk20.default.bold("Policy Evaluation:"));
11941
12471
  for (const step of result.steps) {
11942
12472
  const isFinal = step.isFinal;
11943
12473
  let icon;
11944
- if (step.outcome === "allow") icon = import_chalk19.default.green(" \u2705");
11945
- else if (step.outcome === "review") icon = import_chalk19.default.red(" \u{1F534}");
11946
- else if (step.outcome === "skip") icon = import_chalk19.default.gray(" \u2500 ");
11947
- 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 ");
11948
12478
  const name = step.name.padEnd(18);
11949
- const nameStr = isFinal ? import_chalk19.default.white.bold(name) : import_chalk19.default.white(name);
11950
- const detail = isFinal ? import_chalk19.default.white(step.detail) : import_chalk19.default.gray(step.detail);
11951
- 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") : "";
11952
12482
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
11953
12483
  }
11954
12484
  console.log("");
11955
12485
  if (result.decision === "allow") {
11956
- 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"));
11957
12487
  } else {
11958
12488
  console.log(
11959
- 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")
11960
12490
  );
11961
12491
  if (result.blockedByLabel) {
11962
- console.log(import_chalk19.default.gray(` Reason: ${result.blockedByLabel}`));
12492
+ console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
11963
12493
  }
11964
12494
  }
11965
12495
  console.log("");
11966
12496
  });
11967
12497
  registerInitCommand(program);
11968
12498
  registerAuditCommand(program);
12499
+ registerReportCommand(program);
11969
12500
  registerStatusCommand(program);
11970
12501
  registerDaemonCommand(program);
11971
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) => {
@@ -11973,7 +12504,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
11973
12504
  try {
11974
12505
  await startTail2(options);
11975
12506
  } catch (err2) {
11976
- 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)}`));
11977
12508
  process.exit(1);
11978
12509
  }
11979
12510
  });
@@ -12005,14 +12536,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12005
12536
  Run "node9 addto claude" to register it as the statusLine.`
12006
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) => {
12007
12538
  if (subcommand === "debug") {
12008
- 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");
12009
12540
  if (state === "on") {
12010
- import_fs28.default.mkdirSync(import_path31.default.dirname(flagFile), { recursive: true });
12011
- import_fs28.default.writeFileSync(flagFile, "");
12541
+ import_fs30.default.mkdirSync(import_path33.default.dirname(flagFile), { recursive: true });
12542
+ import_fs30.default.writeFileSync(flagFile, "");
12012
12543
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12013
12544
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12014
12545
  } else if (state === "off") {
12015
- if (import_fs28.default.existsSync(flagFile)) import_fs28.default.unlinkSync(flagFile);
12546
+ if (import_fs30.default.existsSync(flagFile)) import_fs30.default.unlinkSync(flagFile);
12016
12547
  console.log("HUD debug logging disabled.");
12017
12548
  } else {
12018
12549
  console.error("Usage: node9 hud debug on|off");
@@ -12027,7 +12558,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12027
12558
  const ms = parseDuration(options.duration);
12028
12559
  if (ms === null) {
12029
12560
  console.error(
12030
- import_chalk19.default.red(`
12561
+ import_chalk20.default.red(`
12031
12562
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12032
12563
  `)
12033
12564
  );
@@ -12035,20 +12566,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12035
12566
  }
12036
12567
  pauseNode9(ms, options.duration);
12037
12568
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12038
- console.log(import_chalk19.default.yellow(`
12569
+ console.log(import_chalk20.default.yellow(`
12039
12570
  \u23F8 Node9 paused until ${expiresAt}`));
12040
- console.log(import_chalk19.default.gray(` All tool calls will be allowed without review.`));
12041
- 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.
12042
12573
  `));
12043
12574
  });
12044
12575
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12045
12576
  const { paused } = checkPause();
12046
12577
  if (!paused) {
12047
- 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"));
12048
12579
  return;
12049
12580
  }
12050
12581
  resumeNode9();
12051
- 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"));
12052
12583
  });
12053
12584
  var HOOK_BASED_AGENTS = {
12054
12585
  claude: "claude",
@@ -12061,15 +12592,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12061
12592
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12062
12593
  const target = HOOK_BASED_AGENTS[firstArg2];
12063
12594
  console.error(
12064
- import_chalk19.default.yellow(`
12595
+ import_chalk20.default.yellow(`
12065
12596
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12066
12597
  );
12067
- console.error(import_chalk19.default.white(`
12598
+ console.error(import_chalk20.default.white(`
12068
12599
  "${target}" uses its own hook system. Use:`));
12069
12600
  console.error(
12070
- 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")
12071
12602
  );
12072
- 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"));
12073
12604
  process.exit(1);
12074
12605
  }
12075
12606
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12086,7 +12617,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12086
12617
  }
12087
12618
  );
12088
12619
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12089
- 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..."));
12090
12621
  const daemonReady = await autoStartDaemonAndWait();
12091
12622
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12092
12623
  }
@@ -12099,12 +12630,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12099
12630
  }
12100
12631
  if (!result.approved) {
12101
12632
  console.error(
12102
- import_chalk19.default.red(`
12633
+ import_chalk20.default.red(`
12103
12634
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12104
12635
  );
12105
12636
  process.exit(1);
12106
12637
  }
12107
- 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"));
12108
12639
  await runProxy(fullCommand);
12109
12640
  } else {
12110
12641
  program.help();
@@ -12119,9 +12650,9 @@ if (process.argv[2] !== "daemon") {
12119
12650
  const isCheckHook = process.argv[2] === "check";
12120
12651
  if (isCheckHook) {
12121
12652
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12122
- 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");
12123
12654
  const msg = reason instanceof Error ? reason.message : String(reason);
12124
- import_fs28.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12655
+ import_fs30.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12125
12656
  `);
12126
12657
  }
12127
12658
  process.exit(0);