@node9/proxy 1.11.0 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
147
147
  }
148
148
  }
149
149
  const lines = result.error.issues.map((issue) => {
150
- const path41 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
- return ` \u2022 ${path41}: ${issue.message}`;
150
+ const path43 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
+ return ` \u2022 ${path43}: ${issue.message}`;
152
152
  });
153
153
  return {
154
154
  sanitized,
@@ -820,9 +820,8 @@ var init_config = __esm({
820
820
  {
821
821
  field: "command",
822
822
  op: "matches",
823
- // Require the recursive flag to be preceded by whitespace so that
824
- // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
825
- value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
823
+ // Anchor rm as a shell command (not inside a string arg like a git commit message).
824
+ value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
826
825
  },
827
826
  {
828
827
  field: "command",
@@ -851,6 +850,13 @@ var init_config = __esm({
851
850
  name: "review-drop-truncate-shell",
852
851
  tool: "bash",
853
852
  conditions: [
853
+ {
854
+ field: "command",
855
+ op: "matches",
856
+ // Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
857
+ value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
858
+ flags: "i"
859
+ },
854
860
  {
855
861
  field: "command",
856
862
  op: "matches",
@@ -871,7 +877,9 @@ var init_config = __esm({
871
877
  {
872
878
  field: "command",
873
879
  op: "matches",
874
- value: "\\bgit\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
880
+ // Anchor git as a shell command so node -e / python -c scripts containing
881
+ // "git push --force" as a string don't false-positive.
882
+ value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
875
883
  flags: "i"
876
884
  }
877
885
  ],
@@ -881,29 +889,20 @@ var init_config = __esm({
881
889
  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."
882
890
  },
883
891
  {
884
- name: "review-git-push",
892
+ name: "review-git-destructive",
885
893
  tool: "bash",
886
894
  conditions: [
887
895
  {
888
896
  field: "command",
889
897
  op: "matches",
890
- value: "\\bgit\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
898
+ value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
891
899
  flags: "i"
892
- }
893
- ],
894
- conditionMode: "all",
895
- verdict: "review",
896
- reason: "git push sends changes to a shared remote",
897
- description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
898
- },
899
- {
900
- name: "review-git-destructive",
901
- tool: "bash",
902
- conditions: [
900
+ },
903
901
  {
904
902
  field: "command",
905
- op: "matches",
906
- value: "\\bgit\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
903
+ op: "notMatches",
904
+ // Exclude recovery ops — these resolve a conflict, not start a destructive action.
905
+ value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
907
906
  flags: "i"
908
907
  }
909
908
  ],
@@ -929,7 +928,9 @@ var init_config = __esm({
929
928
  {
930
929
  field: "command",
931
930
  op: "matches",
932
- value: "(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
931
+ // Anchor curl/wget as a shell command so node -e scripts testing this
932
+ // regex pattern don't self-match as a false positive.
933
+ value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
933
934
  flags: "i"
934
935
  }
935
936
  ],
@@ -1153,6 +1154,20 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1153
1154
  }
1154
1155
  return null;
1155
1156
  }
1157
+ function scanText(text) {
1158
+ const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
1159
+ for (const pattern of DLP_PATTERNS) {
1160
+ if (pattern.regex.test(t)) {
1161
+ return {
1162
+ patternName: pattern.name,
1163
+ fieldPath: "response-text",
1164
+ redactedSample: maskSecret(t, pattern.regex),
1165
+ severity: pattern.severity
1166
+ };
1167
+ }
1168
+ }
1169
+ return null;
1170
+ }
1156
1171
  var DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
1157
1172
  var init_dlp = __esm({
1158
1173
  "src/dlp.ts"() {
@@ -1182,7 +1197,7 @@ var init_dlp = __esm({
1182
1197
  regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
1183
1198
  severity: "block"
1184
1199
  },
1185
- { name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]+=*/i, severity: "review" }
1200
+ { name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
1186
1201
  ];
1187
1202
  SENSITIVE_PATH_PATTERNS = [
1188
1203
  /[/\\]\.ssh[/\\]/i,
@@ -1750,9 +1765,21 @@ function matchesPattern(text, patterns) {
1750
1765
  const withoutDotSlash = text.replace(/^\.\//, "");
1751
1766
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1752
1767
  }
1753
- function getNestedValue(obj, path41) {
1768
+ function getNestedValue(obj, path43) {
1754
1769
  if (!obj || typeof obj !== "object") return null;
1755
- return path41.split(".").reduce((prev, curr) => prev?.[curr], obj);
1770
+ return path43.split(".").reduce((prev, curr) => prev?.[curr], obj);
1771
+ }
1772
+ function stripStringArguments(cmd) {
1773
+ let result = cmd;
1774
+ result = result.replace(
1775
+ /\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
1776
+ '$1 $2 ""'
1777
+ );
1778
+ result = result.replace(
1779
+ /\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
1780
+ ' $1 ""'
1781
+ );
1782
+ return result;
1756
1783
  }
1757
1784
  function shouldSnapshot(toolName, args, config) {
1758
1785
  if (!config.settings.enableUndo) return false;
@@ -1771,7 +1798,8 @@ function evaluateSmartConditions(args, rule) {
1771
1798
  const mode = rule.conditionMode ?? "all";
1772
1799
  const results = rule.conditions.map((cond) => {
1773
1800
  const rawVal = getNestedValue(args, cond.field);
1774
- const val = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
1801
+ const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
1802
+ const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
1775
1803
  switch (cond.op) {
1776
1804
  case "exists":
1777
1805
  return val !== null && val !== "";
@@ -2877,13 +2905,30 @@ ${smartTruncate(str, 500)}`
2877
2905
  }
2878
2906
  return { intent: "EXEC", message: smartTruncate(JSON.stringify(parsed), 200) };
2879
2907
  }
2908
+ function sendDesktopNotification(title, body) {
2909
+ if (isTestEnv()) return;
2910
+ try {
2911
+ if (process.platform === "darwin") {
2912
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2913
+ const script = `display notification "${esc(body)}" with title "${esc(title)}"`;
2914
+ spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
2915
+ } else if (process.platform === "linux") {
2916
+ spawn("notify-send", [title, body, "--icon=dialog-warning"], {
2917
+ detached: true,
2918
+ stdio: "ignore"
2919
+ }).unref();
2920
+ }
2921
+ } catch {
2922
+ }
2923
+ }
2880
2924
  function escapePango(text) {
2881
2925
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2882
2926
  }
2883
2927
  function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2884
2928
  const lines = [];
2885
2929
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2886
- lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2930
+ const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
2931
+ lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
2887
2932
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2888
2933
  if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2889
2934
  lines.push("");
@@ -3268,7 +3313,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
3268
3313
  if (!options?.calledFromDaemon) {
3269
3314
  const actId = randomUUID();
3270
3315
  const actTs = Date.now();
3271
- await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
3316
+ await notifyActivity({
3317
+ id: actId,
3318
+ ts: actTs,
3319
+ tool: toolName,
3320
+ args,
3321
+ status: "pending",
3322
+ // Strip ANSI escape sequences — agent name comes from caller-supplied metadata
3323
+ // and may be displayed in a terminal (node9 tail/watch), enabling injection.
3324
+ agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
3325
+ });
3272
3326
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
3273
3327
  ...options,
3274
3328
  activityId: actId
@@ -5990,7 +6044,8 @@ function startActivitySocket() {
5990
6044
  ts: data.ts,
5991
6045
  tool: data.tool,
5992
6046
  args: redactArgs(data.args),
5993
- status: "pending"
6047
+ status: "pending",
6048
+ agent: data.agent
5994
6049
  });
5995
6050
  } else {
5996
6051
  if (data.status === "allow") {
@@ -6448,16 +6503,167 @@ var init_sync = __esm({
6448
6503
  }
6449
6504
  });
6450
6505
 
6451
- // src/daemon/server.ts
6452
- import http from "http";
6506
+ // src/daemon/dlp-scanner.ts
6453
6507
  import fs18 from "fs";
6454
6508
  import path21 from "path";
6509
+ import os16 from "os";
6510
+ function loadIndex() {
6511
+ try {
6512
+ return JSON.parse(fs18.readFileSync(INDEX_FILE, "utf-8"));
6513
+ } catch {
6514
+ return {};
6515
+ }
6516
+ }
6517
+ function saveIndex(index) {
6518
+ try {
6519
+ fs18.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
6520
+ } catch {
6521
+ }
6522
+ }
6523
+ function appendAuditEntry(entry) {
6524
+ try {
6525
+ fs18.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
6526
+ } catch {
6527
+ }
6528
+ }
6529
+ function runDlpScan() {
6530
+ if (!fs18.existsSync(PROJECTS_DIR)) return;
6531
+ const index = loadIndex();
6532
+ let updated = false;
6533
+ let projDirs;
6534
+ try {
6535
+ projDirs = fs18.readdirSync(PROJECTS_DIR);
6536
+ } catch {
6537
+ return;
6538
+ }
6539
+ for (const proj of projDirs) {
6540
+ const projPath = path21.join(PROJECTS_DIR, proj);
6541
+ try {
6542
+ if (!fs18.lstatSync(projPath).isDirectory()) continue;
6543
+ const real = fs18.realpathSync(projPath);
6544
+ if (!real.startsWith(PROJECTS_DIR + path21.sep) && real !== PROJECTS_DIR) continue;
6545
+ } catch {
6546
+ continue;
6547
+ }
6548
+ let files;
6549
+ try {
6550
+ files = fs18.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
6551
+ } catch {
6552
+ continue;
6553
+ }
6554
+ for (const file of files) {
6555
+ const filePath = path21.join(projPath, file);
6556
+ const lastOffset = index[filePath] ?? 0;
6557
+ let size;
6558
+ try {
6559
+ size = fs18.statSync(filePath).size;
6560
+ } catch {
6561
+ continue;
6562
+ }
6563
+ if (size <= lastOffset) continue;
6564
+ let fd;
6565
+ try {
6566
+ fd = fs18.openSync(filePath, "r");
6567
+ } catch {
6568
+ continue;
6569
+ }
6570
+ try {
6571
+ const chunkSize = size - lastOffset;
6572
+ const buf = Buffer.alloc(chunkSize);
6573
+ fs18.readSync(fd, buf, 0, chunkSize, lastOffset);
6574
+ const chunk = buf.toString("utf-8");
6575
+ for (const line of chunk.split("\n")) {
6576
+ if (!line.trim()) continue;
6577
+ let entry;
6578
+ try {
6579
+ entry = JSON.parse(line);
6580
+ } catch {
6581
+ continue;
6582
+ }
6583
+ if (entry.type !== "assistant") continue;
6584
+ const content = entry.message?.content;
6585
+ if (!Array.isArray(content)) continue;
6586
+ for (const block of content) {
6587
+ if (typeof block !== "object" || block === null || block.type !== "text")
6588
+ continue;
6589
+ const text = block.text;
6590
+ if (typeof text !== "string") continue;
6591
+ const match = scanText(text);
6592
+ if (!match) continue;
6593
+ const projLabel = decodeURIComponent(proj).replace(os16.homedir(), "~").slice(0, 40);
6594
+ const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
6595
+ appendAuditEntry({
6596
+ ts,
6597
+ tool: "response-text",
6598
+ decision: "dlp",
6599
+ checkedBy: "response-dlp",
6600
+ source: "response-dlp",
6601
+ dlpPattern: match.patternName,
6602
+ dlpSample: match.redactedSample,
6603
+ project: projLabel
6604
+ });
6605
+ sendDesktopNotification(
6606
+ "\u26A0\uFE0F node9 DLP Alert",
6607
+ `${match.patternName} found in Claude response
6608
+ Sample: ${match.redactedSample}
6609
+ Project: ${projLabel}
6610
+ Run: node9 report --period 30d`
6611
+ );
6612
+ }
6613
+ }
6614
+ index[filePath] = size;
6615
+ updated = true;
6616
+ } finally {
6617
+ try {
6618
+ fs18.closeSync(fd);
6619
+ } catch {
6620
+ }
6621
+ }
6622
+ }
6623
+ }
6624
+ if (updated) saveIndex(index);
6625
+ }
6626
+ function startDlpScanner() {
6627
+ setImmediate(() => {
6628
+ try {
6629
+ runDlpScan();
6630
+ } catch {
6631
+ }
6632
+ });
6633
+ const timer = setInterval(
6634
+ () => {
6635
+ try {
6636
+ runDlpScan();
6637
+ } catch {
6638
+ }
6639
+ },
6640
+ 60 * 60 * 1e3
6641
+ );
6642
+ timer.unref();
6643
+ }
6644
+ var INDEX_FILE, PROJECTS_DIR;
6645
+ var init_dlp_scanner = __esm({
6646
+ "src/daemon/dlp-scanner.ts"() {
6647
+ "use strict";
6648
+ init_dlp();
6649
+ init_native();
6650
+ init_state2();
6651
+ INDEX_FILE = path21.join(os16.homedir(), ".node9", "dlp-index.json");
6652
+ PROJECTS_DIR = path21.join(os16.homedir(), ".claude", "projects");
6653
+ }
6654
+ });
6655
+
6656
+ // src/daemon/server.ts
6657
+ import http from "http";
6658
+ import fs19 from "fs";
6659
+ import path22 from "path";
6455
6660
  import { randomUUID as randomUUID4 } from "crypto";
6456
6661
  import { spawnSync as spawnSync2 } from "child_process";
6457
6662
  import chalk2 from "chalk";
6458
6663
  function startDaemon() {
6459
6664
  startCostSync();
6460
6665
  startCloudSync();
6666
+ startDlpScanner();
6461
6667
  loadInsightCounts();
6462
6668
  const csrfToken = randomUUID4();
6463
6669
  const internalToken = randomUUID4();
@@ -6473,7 +6679,7 @@ function startDaemon() {
6473
6679
  idleTimer = setTimeout(() => {
6474
6680
  if (autoStarted) {
6475
6681
  try {
6476
- fs18.unlinkSync(DAEMON_PID_FILE);
6682
+ fs19.unlinkSync(DAEMON_PID_FILE);
6477
6683
  } catch {
6478
6684
  }
6479
6685
  }
@@ -6636,7 +6842,7 @@ data: ${JSON.stringify(item.data)}
6636
6842
  status: "pending"
6637
6843
  });
6638
6844
  }
6639
- const projectCwd = typeof cwd === "string" && path21.isAbsolute(cwd) ? cwd : void 0;
6845
+ const projectCwd = typeof cwd === "string" && path22.isAbsolute(cwd) ? cwd : void 0;
6640
6846
  const projectConfig = getConfig(projectCwd);
6641
6847
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6642
6848
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -7027,8 +7233,8 @@ data: ${JSON.stringify(item.data)}
7027
7233
  const body = await readBody(req);
7028
7234
  const data = body ? JSON.parse(body) : {};
7029
7235
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
7030
- const node9Dir = path21.dirname(GLOBAL_CONFIG_PATH);
7031
- if (!path21.resolve(configPath).startsWith(node9Dir + path21.sep)) {
7236
+ const node9Dir = path22.dirname(GLOBAL_CONFIG_PATH);
7237
+ if (!path22.resolve(configPath).startsWith(node9Dir + path22.sep)) {
7032
7238
  res.writeHead(400, { "Content-Type": "application/json" });
7033
7239
  return res.end(
7034
7240
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -7139,14 +7345,14 @@ data: ${JSON.stringify(item.data)}
7139
7345
  server.on("error", (e) => {
7140
7346
  if (e.code === "EADDRINUSE") {
7141
7347
  try {
7142
- if (fs18.existsSync(DAEMON_PID_FILE)) {
7143
- const { pid } = JSON.parse(fs18.readFileSync(DAEMON_PID_FILE, "utf-8"));
7348
+ if (fs19.existsSync(DAEMON_PID_FILE)) {
7349
+ const { pid } = JSON.parse(fs19.readFileSync(DAEMON_PID_FILE, "utf-8"));
7144
7350
  process.kill(pid, 0);
7145
7351
  return process.exit(0);
7146
7352
  }
7147
7353
  } catch {
7148
7354
  try {
7149
- fs18.unlinkSync(DAEMON_PID_FILE);
7355
+ fs19.unlinkSync(DAEMON_PID_FILE);
7150
7356
  } catch {
7151
7357
  }
7152
7358
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -7217,19 +7423,20 @@ var init_server = __esm({
7217
7423
  init_config_schema();
7218
7424
  init_costSync();
7219
7425
  init_sync();
7426
+ init_dlp_scanner();
7220
7427
  }
7221
7428
  });
7222
7429
 
7223
7430
  // src/daemon/service.ts
7224
- import fs19 from "fs";
7225
- import path22 from "path";
7226
- import os16 from "os";
7431
+ import fs20 from "fs";
7432
+ import path23 from "path";
7433
+ import os17 from "os";
7227
7434
  import { spawnSync as spawnSync3, execFileSync } from "child_process";
7228
7435
  function resolveNode9Binary() {
7229
7436
  try {
7230
7437
  const script = process.argv[1];
7231
- if (typeof script === "string" && path22.isAbsolute(script) && fs19.existsSync(script)) {
7232
- return fs19.realpathSync(script);
7438
+ if (typeof script === "string" && path23.isAbsolute(script) && fs20.existsSync(script)) {
7439
+ return fs20.realpathSync(script);
7233
7440
  }
7234
7441
  } catch {
7235
7442
  }
@@ -7247,11 +7454,11 @@ function xmlEscape(s) {
7247
7454
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7248
7455
  }
7249
7456
  function launchdPlist(binaryPath) {
7250
- const logDir = path22.join(os16.homedir(), ".node9");
7457
+ const logDir = path23.join(os17.homedir(), ".node9");
7251
7458
  const nodePath = xmlEscape(process.execPath);
7252
7459
  const scriptPath = xmlEscape(binaryPath);
7253
- const outLog = xmlEscape(path22.join(logDir, "daemon.log"));
7254
- const errLog = xmlEscape(path22.join(logDir, "daemon-error.log"));
7460
+ const outLog = xmlEscape(path23.join(logDir, "daemon.log"));
7461
+ const errLog = xmlEscape(path23.join(logDir, "daemon-error.log"));
7255
7462
  return `<?xml version="1.0" encoding="UTF-8"?>
7256
7463
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7257
7464
  <plist version="1.0">
@@ -7286,9 +7493,9 @@ function launchdPlist(binaryPath) {
7286
7493
  `;
7287
7494
  }
7288
7495
  function installLaunchd(binaryPath) {
7289
- const dir = path22.dirname(LAUNCHD_PLIST);
7290
- if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
7291
- fs19.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
7496
+ const dir = path23.dirname(LAUNCHD_PLIST);
7497
+ if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
7498
+ fs20.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
7292
7499
  spawnSync3("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
7293
7500
  const r = spawnSync3("launchctl", ["load", "-w", LAUNCHD_PLIST], {
7294
7501
  encoding: "utf8",
@@ -7299,13 +7506,13 @@ function installLaunchd(binaryPath) {
7299
7506
  }
7300
7507
  }
7301
7508
  function uninstallLaunchd() {
7302
- if (fs19.existsSync(LAUNCHD_PLIST)) {
7509
+ if (fs20.existsSync(LAUNCHD_PLIST)) {
7303
7510
  spawnSync3("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
7304
- fs19.unlinkSync(LAUNCHD_PLIST);
7511
+ fs20.unlinkSync(LAUNCHD_PLIST);
7305
7512
  }
7306
7513
  }
7307
7514
  function isLaunchdInstalled() {
7308
- return fs19.existsSync(LAUNCHD_PLIST);
7515
+ return fs20.existsSync(LAUNCHD_PLIST);
7309
7516
  }
7310
7517
  function systemdUnit(binaryPath) {
7311
7518
  return `[Unit]
@@ -7325,12 +7532,12 @@ WantedBy=default.target
7325
7532
  `;
7326
7533
  }
7327
7534
  function installSystemd(binaryPath) {
7328
- if (!fs19.existsSync(SYSTEMD_UNIT_DIR)) {
7329
- fs19.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7535
+ if (!fs20.existsSync(SYSTEMD_UNIT_DIR)) {
7536
+ fs20.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7330
7537
  }
7331
- fs19.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7538
+ fs20.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7332
7539
  try {
7333
- execFileSync("loginctl", ["enable-linger", os16.userInfo().username], { timeout: 3e3 });
7540
+ execFileSync("loginctl", ["enable-linger", os17.userInfo().username], { timeout: 3e3 });
7334
7541
  } catch {
7335
7542
  }
7336
7543
  const reload = spawnSync3("systemctl", ["--user", "daemon-reload"], {
@@ -7350,23 +7557,23 @@ function installSystemd(binaryPath) {
7350
7557
  }
7351
7558
  }
7352
7559
  function uninstallSystemd() {
7353
- if (fs19.existsSync(SYSTEMD_UNIT)) {
7560
+ if (fs20.existsSync(SYSTEMD_UNIT)) {
7354
7561
  spawnSync3("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
7355
7562
  encoding: "utf8",
7356
7563
  timeout: 5e3
7357
7564
  });
7358
7565
  spawnSync3("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
7359
- fs19.unlinkSync(SYSTEMD_UNIT);
7566
+ fs20.unlinkSync(SYSTEMD_UNIT);
7360
7567
  }
7361
7568
  }
7362
7569
  function isSystemdInstalled() {
7363
- return fs19.existsSync(SYSTEMD_UNIT);
7570
+ return fs20.existsSync(SYSTEMD_UNIT);
7364
7571
  }
7365
7572
  function stopRunningDaemon() {
7366
- const pidFile = path22.join(os16.homedir(), ".node9", "daemon.pid");
7367
- if (!fs19.existsSync(pidFile)) return;
7573
+ const pidFile = path23.join(os17.homedir(), ".node9", "daemon.pid");
7574
+ if (!fs20.existsSync(pidFile)) return;
7368
7575
  try {
7369
- const data = JSON.parse(fs19.readFileSync(pidFile, "utf-8"));
7576
+ const data = JSON.parse(fs20.readFileSync(pidFile, "utf-8"));
7370
7577
  const pid = data.pid;
7371
7578
  const MAX_PID2 = 4194304;
7372
7579
  if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
@@ -7386,7 +7593,7 @@ function stopRunningDaemon() {
7386
7593
  }
7387
7594
  }
7388
7595
  try {
7389
- fs19.unlinkSync(pidFile);
7596
+ fs20.unlinkSync(pidFile);
7390
7597
  } catch {
7391
7598
  }
7392
7599
  } catch {
@@ -7461,20 +7668,20 @@ var init_service = __esm({
7461
7668
  "src/daemon/service.ts"() {
7462
7669
  "use strict";
7463
7670
  LAUNCHD_LABEL = "ai.node9.daemon";
7464
- LAUNCHD_PLIST = path22.join(os16.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7465
- SYSTEMD_UNIT_DIR = path22.join(os16.homedir(), ".config", "systemd", "user");
7466
- SYSTEMD_UNIT = path22.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
7671
+ LAUNCHD_PLIST = path23.join(os17.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7672
+ SYSTEMD_UNIT_DIR = path23.join(os17.homedir(), ".config", "systemd", "user");
7673
+ SYSTEMD_UNIT = path23.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
7467
7674
  }
7468
7675
  });
7469
7676
 
7470
7677
  // src/daemon/index.ts
7471
- import fs20 from "fs";
7678
+ import fs21 from "fs";
7472
7679
  import chalk3 from "chalk";
7473
7680
  import { spawnSync as spawnSync4 } from "child_process";
7474
7681
  function stopDaemon() {
7475
- if (!fs20.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
7682
+ if (!fs21.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
7476
7683
  try {
7477
- const data = JSON.parse(fs20.readFileSync(DAEMON_PID_FILE, "utf-8"));
7684
+ const data = JSON.parse(fs21.readFileSync(DAEMON_PID_FILE, "utf-8"));
7478
7685
  const pid = data.pid;
7479
7686
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
7480
7687
  console.log(chalk3.gray("Cleaned up invalid PID file."));
@@ -7486,7 +7693,7 @@ function stopDaemon() {
7486
7693
  console.log(chalk3.gray("Cleaned up stale PID file."));
7487
7694
  } finally {
7488
7695
  try {
7489
- fs20.unlinkSync(DAEMON_PID_FILE);
7696
+ fs21.unlinkSync(DAEMON_PID_FILE);
7490
7697
  } catch {
7491
7698
  }
7492
7699
  }
@@ -7495,9 +7702,9 @@ function daemonStatus() {
7495
7702
  const serviceInstalled = isDaemonServiceInstalled();
7496
7703
  const serviceLabel = serviceInstalled ? chalk3.green("installed (starts on login)") : chalk3.yellow("not installed \u2014 run: node9 daemon install");
7497
7704
  let processStatus;
7498
- if (fs20.existsSync(DAEMON_PID_FILE)) {
7705
+ if (fs21.existsSync(DAEMON_PID_FILE)) {
7499
7706
  try {
7500
- const data = JSON.parse(fs20.readFileSync(DAEMON_PID_FILE, "utf-8"));
7707
+ const data = JSON.parse(fs21.readFileSync(DAEMON_PID_FILE, "utf-8"));
7501
7708
  const pid = data.pid;
7502
7709
  const port = data.port;
7503
7710
  if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
@@ -7546,10 +7753,10 @@ __export(tail_exports, {
7546
7753
  startTail: () => startTail
7547
7754
  });
7548
7755
  import http2 from "http";
7549
- import chalk24 from "chalk";
7550
- import fs35 from "fs";
7551
- import os31 from "os";
7552
- import path38 from "path";
7756
+ import chalk25 from "chalk";
7757
+ import fs37 from "fs";
7758
+ import os33 from "os";
7759
+ import path40 from "path";
7553
7760
  import readline5 from "readline";
7554
7761
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
7555
7762
  function getIcon(tool) {
@@ -7559,6 +7766,74 @@ function getIcon(tool) {
7559
7766
  }
7560
7767
  return "\u{1F6E0}\uFE0F";
7561
7768
  }
7769
+ function getModelContextLimit(model) {
7770
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
7771
+ for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
7772
+ if (base.startsWith(key)) return limit;
7773
+ }
7774
+ return 2e5;
7775
+ }
7776
+ function readSessionUsage() {
7777
+ const projectsDir = path40.join(os33.homedir(), ".claude", "projects");
7778
+ if (!fs37.existsSync(projectsDir)) return null;
7779
+ let latestFile = null;
7780
+ let latestMtime = 0;
7781
+ try {
7782
+ for (const dir of fs37.readdirSync(projectsDir)) {
7783
+ const dirPath = path40.join(projectsDir, dir);
7784
+ try {
7785
+ if (!fs37.statSync(dirPath).isDirectory()) continue;
7786
+ for (const file of fs37.readdirSync(dirPath)) {
7787
+ if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
7788
+ const filePath = path40.join(dirPath, file);
7789
+ try {
7790
+ const mtime = fs37.statSync(filePath).mtimeMs;
7791
+ if (mtime > latestMtime) {
7792
+ latestMtime = mtime;
7793
+ latestFile = filePath;
7794
+ }
7795
+ } catch {
7796
+ }
7797
+ }
7798
+ } catch {
7799
+ }
7800
+ }
7801
+ } catch {
7802
+ }
7803
+ if (!latestFile) return null;
7804
+ try {
7805
+ const lines = fs37.readFileSync(latestFile, "utf-8").split("\n");
7806
+ let lastModel = "";
7807
+ let lastInput = 0;
7808
+ let lastOutput = 0;
7809
+ for (const line of lines) {
7810
+ if (!line.trim()) continue;
7811
+ try {
7812
+ const entry = JSON.parse(line);
7813
+ if (entry.type !== "assistant" || !entry.message?.usage) continue;
7814
+ const u = entry.message.usage;
7815
+ lastInput = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
7816
+ lastOutput = u.output_tokens ?? 0;
7817
+ if (entry.message.model) lastModel = entry.message.model;
7818
+ } catch {
7819
+ }
7820
+ }
7821
+ if (!lastModel || lastInput === 0) return null;
7822
+ const limit = getModelContextLimit(lastModel);
7823
+ const fillPct = Math.round(lastInput / limit * 100);
7824
+ return { inputTokens: lastInput, outputTokens: lastOutput, model: lastModel, fillPct };
7825
+ } catch {
7826
+ return null;
7827
+ }
7828
+ }
7829
+ function formatContextStat(stat) {
7830
+ const pctColor = stat.fillPct >= 80 ? chalk25.red : stat.fillPct >= 50 ? chalk25.yellow : chalk25.cyan;
7831
+ const k = (n) => `${Math.round(n / 1e3)}k`;
7832
+ const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
7833
+ return chalk25.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk25.dim(
7834
+ ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
7835
+ );
7836
+ }
7562
7837
  function visibleLength(s) {
7563
7838
  return s.replace(/\x1B\[[0-9;]*m/g, "").length;
7564
7839
  }
@@ -7568,26 +7843,31 @@ function wrappedLineCount(text) {
7568
7843
  const len = visibleLength(text);
7569
7844
  return Math.max(1, Math.ceil(len / cols));
7570
7845
  }
7846
+ function agentLabel(agent) {
7847
+ if (!agent || agent === "Terminal") return "";
7848
+ const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
7849
+ return short ? chalk25.dim(`[${short}] `) : "";
7850
+ }
7571
7851
  function formatBase(activity) {
7572
7852
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
7573
7853
  const icon = getIcon(activity.tool);
7574
7854
  const toolName = activity.tool.slice(0, 16).padEnd(16);
7575
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os31.homedir(), "~");
7855
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os33.homedir(), "~");
7576
7856
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
7577
- return `${chalk24.gray(time)} ${icon} ${chalk24.white.bold(toolName)} ${chalk24.dim(argsPreview)}`;
7857
+ return `${chalk25.gray(time)} ${icon} ${agentLabel(activity.agent)}${chalk25.white.bold(toolName)} ${chalk25.dim(argsPreview)}`;
7578
7858
  }
7579
7859
  function renderResult(activity, result) {
7580
7860
  const base = formatBase(activity);
7581
7861
  let status;
7582
7862
  if (result.status === "allow") {
7583
- status = chalk24.green("\u2713 ALLOW");
7863
+ status = chalk25.green("\u2713 ALLOW");
7584
7864
  } else if (result.status === "dlp") {
7585
- status = chalk24.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7865
+ status = chalk25.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7586
7866
  } else {
7587
- status = chalk24.red("\u2717 BLOCK");
7867
+ status = chalk25.red("\u2717 BLOCK");
7588
7868
  }
7589
7869
  const cost = result.costEstimate ?? activity.costEstimate;
7590
- const costSuffix = cost == null ? "" : chalk24.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7870
+ const costSuffix = cost == null ? "" : chalk25.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7591
7871
  if (process.stdout.isTTY) {
7592
7872
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7593
7873
  readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -7604,19 +7884,19 @@ function renderResult(activity, result) {
7604
7884
  }
7605
7885
  function renderPending(activity) {
7606
7886
  if (!process.stdout.isTTY) return;
7607
- const line = `${formatBase(activity)} ${chalk24.yellow("\u25CF \u2026")}`;
7887
+ const line = `${formatBase(activity)} ${chalk25.yellow("\u25CF \u2026")}`;
7608
7888
  pendingShownForId = activity.id;
7609
7889
  pendingWrappedLines = wrappedLineCount(line);
7610
7890
  process.stdout.write(`${line}\r`);
7611
7891
  }
7612
7892
  async function ensureDaemon() {
7613
7893
  let pidPort = null;
7614
- if (fs35.existsSync(PID_FILE)) {
7894
+ if (fs37.existsSync(PID_FILE)) {
7615
7895
  try {
7616
- const { port } = JSON.parse(fs35.readFileSync(PID_FILE, "utf-8"));
7896
+ const { port } = JSON.parse(fs37.readFileSync(PID_FILE, "utf-8"));
7617
7897
  pidPort = port;
7618
7898
  } catch {
7619
- console.error(chalk24.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7899
+ console.error(chalk25.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7620
7900
  }
7621
7901
  }
7622
7902
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -7627,7 +7907,7 @@ async function ensureDaemon() {
7627
7907
  if (res.ok) return checkPort;
7628
7908
  } catch {
7629
7909
  }
7630
- console.log(chalk24.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7910
+ console.log(chalk25.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7631
7911
  const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
7632
7912
  detached: true,
7633
7913
  stdio: "ignore",
@@ -7644,7 +7924,7 @@ async function ensureDaemon() {
7644
7924
  } catch {
7645
7925
  }
7646
7926
  }
7647
- console.error(chalk24.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7927
+ console.error(chalk25.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7648
7928
  process.exit(1);
7649
7929
  }
7650
7930
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -7710,10 +7990,11 @@ function buildCardLines(req, localCount = 0) {
7710
7990
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
7711
7991
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
7712
7992
  const description = rawDesc ? cleanReason(rawDesc) : "";
7993
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk25.dim(`(${req.agent})`)}` : "";
7713
7994
  const lines = [
7714
7995
  ``,
7715
7996
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
7716
- `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
7997
+ `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}${agentSuffix}`,
7717
7998
  `${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
7718
7999
  ];
7719
8000
  if (description) {
@@ -7765,9 +8046,9 @@ function buildRecoveryCardLines(req) {
7765
8046
  ];
7766
8047
  }
7767
8048
  function readApproversFromDisk() {
7768
- const configPath = path38.join(os31.homedir(), ".node9", "config.json");
8049
+ const configPath = path40.join(os33.homedir(), ".node9", "config.json");
7769
8050
  try {
7770
- const raw = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
8051
+ const raw = JSON.parse(fs37.readFileSync(configPath, "utf-8"));
7771
8052
  const settings = raw.settings ?? {};
7772
8053
  return settings.approvers ?? {};
7773
8054
  } catch {
@@ -7778,20 +8059,20 @@ function approverStatusLine() {
7778
8059
  const a = readApproversFromDisk();
7779
8060
  const fmt = (label, key) => {
7780
8061
  const on = a[key] !== false;
7781
- return `[${key[0]}]${label.slice(1)} ${on ? chalk24.green("\u2713") : chalk24.dim("\u2717")}`;
8062
+ return `[${key[0]}]${label.slice(1)} ${on ? chalk25.green("\u2713") : chalk25.dim("\u2717")}`;
7782
8063
  };
7783
8064
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7784
8065
  }
7785
8066
  function toggleApprover(channel) {
7786
- const configPath = path38.join(os31.homedir(), ".node9", "config.json");
8067
+ const configPath = path40.join(os33.homedir(), ".node9", "config.json");
7787
8068
  try {
7788
- const raw = JSON.parse(fs35.readFileSync(configPath, "utf-8"));
8069
+ const raw = JSON.parse(fs37.readFileSync(configPath, "utf-8"));
7789
8070
  const settings = raw.settings ?? {};
7790
8071
  const approvers = settings.approvers ?? {};
7791
8072
  approvers[channel] = approvers[channel] === false;
7792
8073
  settings.approvers = approvers;
7793
8074
  raw.settings = settings;
7794
- fs35.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
8075
+ fs37.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7795
8076
  } catch (err2) {
7796
8077
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7797
8078
  `);
@@ -7823,7 +8104,7 @@ async function startTail(options = {}) {
7823
8104
  req2.end();
7824
8105
  });
7825
8106
  if (result.ok) {
7826
- console.log(chalk24.green("\u2713 Flight Recorder buffer cleared."));
8107
+ console.log(chalk25.green("\u2713 Flight Recorder buffer cleared."));
7827
8108
  } else if (result.code === "ECONNREFUSED") {
7828
8109
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7829
8110
  } else if (result.code === "ETIMEDOUT") {
@@ -7867,7 +8148,7 @@ async function startTail(options = {}) {
7867
8148
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7868
8149
  if (channel) {
7869
8150
  toggleApprover(channel);
7870
- console.log(chalk24.dim(` Approvers: ${approverStatusLine()}`));
8151
+ console.log(chalk25.dim(` Approvers: ${approverStatusLine()}`));
7871
8152
  }
7872
8153
  };
7873
8154
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7933,7 +8214,7 @@ async function startTail(options = {}) {
7933
8214
  localAllowCounts.get(req2.toolName) ?? 0
7934
8215
  )
7935
8216
  );
7936
- const decisionStamp = action === "always-allow" ? chalk24.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk24.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk24.green("\u2713 ALLOWED") : action === "redirect" ? chalk24.yellow("\u21A9 REDIRECT AI") : chalk24.red("\u2717 DENIED");
8217
+ const decisionStamp = action === "always-allow" ? chalk25.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk25.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk25.green("\u2713 ALLOWED") : action === "redirect" ? chalk25.yellow("\u21A9 REDIRECT AI") : chalk25.red("\u2717 DENIED");
7937
8218
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7938
8219
  for (const line of stampedLines) process.stdout.write(line + "\n");
7939
8220
  process.stdout.write(SHOW_CURSOR);
@@ -7961,8 +8242,8 @@ async function startTail(options = {}) {
7961
8242
  }
7962
8243
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7963
8244
  try {
7964
- fs35.appendFileSync(
7965
- path38.join(os31.homedir(), ".node9", "hook-debug.log"),
8245
+ fs37.appendFileSync(
8246
+ path40.join(os33.homedir(), ".node9", "hook-debug.log"),
7966
8247
  `[tail] POST /decision failed: ${String(err2)}
7967
8248
  `
7968
8249
  );
@@ -7984,7 +8265,7 @@ async function startTail(options = {}) {
7984
8265
  );
7985
8266
  const stampedLines = buildCardLines(req2, priorCount);
7986
8267
  if (externalDecision) {
7987
- const source = externalDecision === "allow" ? chalk24.green("\u2713 ALLOWED") : chalk24.red("\u2717 DENIED");
8268
+ const source = externalDecision === "allow" ? chalk25.green("\u2713 ALLOWED") : chalk25.red("\u2717 DENIED");
7988
8269
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7989
8270
  }
7990
8271
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -8043,16 +8324,31 @@ async function startTail(options = {}) {
8043
8324
  }
8044
8325
  } catch {
8045
8326
  }
8046
- console.log(chalk24.cyan.bold(`
8047
- \u{1F6F0}\uFE0F Node9 tail `) + chalk24.dim(`\u2192 ${dashboardUrl}`));
8327
+ const auditLog = path40.join(os33.homedir(), ".node9", "audit.log");
8328
+ try {
8329
+ const unackedDlp = fs37.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
8330
+ if (unackedDlp > 0) {
8331
+ console.log("");
8332
+ console.log(
8333
+ chalk25.bgRed.white.bold(
8334
+ ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
8335
+ )
8336
+ );
8337
+ }
8338
+ } catch {
8339
+ }
8340
+ console.log(chalk25.cyan.bold(`
8341
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk25.dim(`\u2192 ${dashboardUrl}`));
8048
8342
  if (canApprove) {
8049
- console.log(chalk24.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8050
- console.log(chalk24.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8343
+ console.log(chalk25.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8344
+ console.log(chalk25.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8051
8345
  }
8346
+ const ctxStat = readSessionUsage();
8347
+ if (ctxStat) console.log(" " + formatContextStat(ctxStat));
8052
8348
  if (options.history) {
8053
- console.log(chalk24.dim("Showing history + live events.\n"));
8349
+ console.log(chalk25.dim("Showing history + live events.\n"));
8054
8350
  } else {
8055
- console.log(chalk24.dim("Showing live events only. Use --history to include past.\n"));
8351
+ console.log(chalk25.dim("Showing live events only. Use --history to include past.\n"));
8056
8352
  }
8057
8353
  process.on("SIGINT", () => {
8058
8354
  exitIdleMode();
@@ -8062,13 +8358,13 @@ async function startTail(options = {}) {
8062
8358
  readline5.clearLine(process.stdout, 0);
8063
8359
  readline5.cursorTo(process.stdout, 0);
8064
8360
  }
8065
- console.log(chalk24.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8361
+ console.log(chalk25.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8066
8362
  process.exit(0);
8067
8363
  });
8068
8364
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
8069
8365
  const req = http2.get(sseUrl, (res) => {
8070
8366
  if (res.statusCode !== 200) {
8071
- console.error(chalk24.red(`Failed to connect: HTTP ${res.statusCode}`));
8367
+ console.error(chalk25.red(`Failed to connect: HTTP ${res.statusCode}`));
8072
8368
  process.exit(1);
8073
8369
  }
8074
8370
  if (canApprove) enterIdleMode();
@@ -8099,7 +8395,7 @@ async function startTail(options = {}) {
8099
8395
  readline5.clearLine(process.stdout, 0);
8100
8396
  readline5.cursorTo(process.stdout, 0);
8101
8397
  }
8102
- console.log(chalk24.red("\n\u274C Daemon disconnected."));
8398
+ console.log(chalk25.red("\n\u274C Daemon disconnected."));
8103
8399
  process.exit(1);
8104
8400
  });
8105
8401
  });
@@ -8191,9 +8487,9 @@ async function startTail(options = {}) {
8191
8487
  const hash = data.hash ?? "";
8192
8488
  const summary = data.argsSummary ?? data.tool;
8193
8489
  const fileCount = data.fileCount ?? 0;
8194
- const files = fileCount > 0 ? chalk24.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8490
+ const files = fileCount > 0 ? chalk25.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8195
8491
  process.stdout.write(
8196
- `${chalk24.dim(time)} ${chalk24.cyan("\u{1F4F8} snapshot")} ${chalk24.dim(hash)} ${summary}${files}
8492
+ `${chalk25.dim(time)} ${chalk25.cyan("\u{1F4F8} snapshot")} ${chalk25.dim(hash)} ${summary}${files}
8197
8493
  `
8198
8494
  );
8199
8495
  return;
@@ -8210,19 +8506,19 @@ async function startTail(options = {}) {
8210
8506
  }
8211
8507
  req.on("error", (err2) => {
8212
8508
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
8213
- console.error(chalk24.red(`
8509
+ console.error(chalk25.red(`
8214
8510
  \u274C ${msg}`));
8215
8511
  process.exit(1);
8216
8512
  });
8217
8513
  }
8218
- var PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8514
+ var PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8219
8515
  var init_tail = __esm({
8220
8516
  "src/tui/tail.ts"() {
8221
8517
  "use strict";
8222
8518
  init_daemon2();
8223
8519
  init_daemon();
8224
8520
  init_core();
8225
- PID_FILE = path38.join(os31.homedir(), ".node9", "daemon.pid");
8521
+ PID_FILE = path40.join(os33.homedir(), ".node9", "daemon.pid");
8226
8522
  ICONS = {
8227
8523
  bash: "\u{1F4BB}",
8228
8524
  shell: "\u{1F4BB}",
@@ -8240,6 +8536,13 @@ var init_tail = __esm({
8240
8536
  delete: "\u{1F5D1}\uFE0F",
8241
8537
  web: "\u{1F310}"
8242
8538
  };
8539
+ MODEL_CONTEXT_LIMITS = {
8540
+ "claude-opus-4": 2e5,
8541
+ "claude-sonnet-4": 2e5,
8542
+ "claude-haiku-4": 2e5,
8543
+ "claude-3-7": 2e5,
8544
+ "claude-3-5": 2e5
8545
+ };
8243
8546
  RESET2 = "\x1B[0m";
8244
8547
  BOLD2 = "\x1B[1m";
8245
8548
  RED = "\x1B[31m";
@@ -8263,9 +8566,9 @@ __export(hud_exports, {
8263
8566
  main: () => main,
8264
8567
  renderEnvironmentLine: () => renderEnvironmentLine
8265
8568
  });
8266
- import fs36 from "fs";
8267
- import path39 from "path";
8268
- import os32 from "os";
8569
+ import fs38 from "fs";
8570
+ import path41 from "path";
8571
+ import os34 from "os";
8269
8572
  import http3 from "http";
8270
8573
  async function readStdin() {
8271
8574
  const chunks = [];
@@ -8341,9 +8644,9 @@ function formatTimeLeft(resetsAt) {
8341
8644
  return ` (${m}m left)`;
8342
8645
  }
8343
8646
  function safeReadJson(filePath) {
8344
- if (!fs36.existsSync(filePath)) return null;
8647
+ if (!fs38.existsSync(filePath)) return null;
8345
8648
  try {
8346
- return JSON.parse(fs36.readFileSync(filePath, "utf-8"));
8649
+ return JSON.parse(fs38.readFileSync(filePath, "utf-8"));
8347
8650
  } catch {
8348
8651
  return null;
8349
8652
  }
@@ -8364,12 +8667,12 @@ function countHooksInFile(filePath) {
8364
8667
  return Object.keys(cfg.hooks).length;
8365
8668
  }
8366
8669
  function countRulesInDir(rulesDir) {
8367
- if (!fs36.existsSync(rulesDir)) return 0;
8670
+ if (!fs38.existsSync(rulesDir)) return 0;
8368
8671
  let count = 0;
8369
8672
  try {
8370
- for (const entry of fs36.readdirSync(rulesDir, { withFileTypes: true })) {
8673
+ for (const entry of fs38.readdirSync(rulesDir, { withFileTypes: true })) {
8371
8674
  if (entry.isDirectory()) {
8372
- count += countRulesInDir(path39.join(rulesDir, entry.name));
8675
+ count += countRulesInDir(path41.join(rulesDir, entry.name));
8373
8676
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
8374
8677
  count++;
8375
8678
  }
@@ -8380,46 +8683,46 @@ function countRulesInDir(rulesDir) {
8380
8683
  }
8381
8684
  function isSamePath(a, b) {
8382
8685
  try {
8383
- return path39.resolve(a) === path39.resolve(b);
8686
+ return path41.resolve(a) === path41.resolve(b);
8384
8687
  } catch {
8385
8688
  return false;
8386
8689
  }
8387
8690
  }
8388
8691
  function countConfigs(cwd) {
8389
- const homeDir2 = os32.homedir();
8390
- const claudeDir = path39.join(homeDir2, ".claude");
8692
+ const homeDir2 = os34.homedir();
8693
+ const claudeDir = path41.join(homeDir2, ".claude");
8391
8694
  let claudeMdCount = 0;
8392
8695
  let rulesCount = 0;
8393
8696
  let hooksCount = 0;
8394
8697
  const userMcpServers = /* @__PURE__ */ new Set();
8395
8698
  const projectMcpServers = /* @__PURE__ */ new Set();
8396
- if (fs36.existsSync(path39.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8397
- rulesCount += countRulesInDir(path39.join(claudeDir, "rules"));
8398
- const userSettings = path39.join(claudeDir, "settings.json");
8699
+ if (fs38.existsSync(path41.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8700
+ rulesCount += countRulesInDir(path41.join(claudeDir, "rules"));
8701
+ const userSettings = path41.join(claudeDir, "settings.json");
8399
8702
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
8400
8703
  hooksCount += countHooksInFile(userSettings);
8401
- const userClaudeJson = path39.join(homeDir2, ".claude.json");
8704
+ const userClaudeJson = path41.join(homeDir2, ".claude.json");
8402
8705
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
8403
8706
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
8404
8707
  userMcpServers.delete(name);
8405
8708
  }
8406
8709
  if (cwd) {
8407
- if (fs36.existsSync(path39.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8408
- if (fs36.existsSync(path39.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8409
- const projectClaudeDir = path39.join(cwd, ".claude");
8710
+ if (fs38.existsSync(path41.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8711
+ if (fs38.existsSync(path41.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8712
+ const projectClaudeDir = path41.join(cwd, ".claude");
8410
8713
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
8411
8714
  if (!overlapsUserScope) {
8412
- if (fs36.existsSync(path39.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8413
- rulesCount += countRulesInDir(path39.join(projectClaudeDir, "rules"));
8414
- const projSettings = path39.join(projectClaudeDir, "settings.json");
8715
+ if (fs38.existsSync(path41.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8716
+ rulesCount += countRulesInDir(path41.join(projectClaudeDir, "rules"));
8717
+ const projSettings = path41.join(projectClaudeDir, "settings.json");
8415
8718
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
8416
8719
  hooksCount += countHooksInFile(projSettings);
8417
8720
  }
8418
- if (fs36.existsSync(path39.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8419
- const localSettings = path39.join(projectClaudeDir, "settings.local.json");
8721
+ if (fs38.existsSync(path41.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8722
+ const localSettings = path41.join(projectClaudeDir, "settings.local.json");
8420
8723
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
8421
8724
  hooksCount += countHooksInFile(localSettings);
8422
- const mcpJsonServers = getMcpServerNames(path39.join(cwd, ".mcp.json"));
8725
+ const mcpJsonServers = getMcpServerNames(path41.join(cwd, ".mcp.json"));
8423
8726
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
8424
8727
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
8425
8728
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -8452,12 +8755,12 @@ function readActiveShieldsHud() {
8452
8755
  return shieldsCache.value;
8453
8756
  }
8454
8757
  try {
8455
- const shieldsPath = path39.join(os32.homedir(), ".node9", "shields.json");
8456
- if (!fs36.existsSync(shieldsPath)) {
8758
+ const shieldsPath = path41.join(os34.homedir(), ".node9", "shields.json");
8759
+ if (!fs38.existsSync(shieldsPath)) {
8457
8760
  shieldsCache = { value: [], ts: now };
8458
8761
  return [];
8459
8762
  }
8460
- const parsed = JSON.parse(fs36.readFileSync(shieldsPath, "utf-8"));
8763
+ const parsed = JSON.parse(fs38.readFileSync(shieldsPath, "utf-8"));
8461
8764
  if (!Array.isArray(parsed.active)) {
8462
8765
  shieldsCache = { value: [], ts: now };
8463
8766
  return [];
@@ -8559,17 +8862,17 @@ function renderContextLine(stdin) {
8559
8862
  async function main() {
8560
8863
  try {
8561
8864
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
8562
- if (fs36.existsSync(path39.join(os32.homedir(), ".node9", "hud-debug"))) {
8865
+ if (fs38.existsSync(path41.join(os34.homedir(), ".node9", "hud-debug"))) {
8563
8866
  try {
8564
- const logPath = path39.join(os32.homedir(), ".node9", "hud-debug.log");
8867
+ const logPath = path41.join(os34.homedir(), ".node9", "hud-debug.log");
8565
8868
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
8566
8869
  let size = 0;
8567
8870
  try {
8568
- size = fs36.statSync(logPath).size;
8871
+ size = fs38.statSync(logPath).size;
8569
8872
  } catch {
8570
8873
  }
8571
8874
  if (size < MAX_LOG_SIZE) {
8572
- fs36.appendFileSync(
8875
+ fs38.appendFileSync(
8573
8876
  logPath,
8574
8877
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
8575
8878
  );
@@ -8590,11 +8893,11 @@ async function main() {
8590
8893
  try {
8591
8894
  const cwd = stdin.cwd ?? process.cwd();
8592
8895
  for (const configPath of [
8593
- path39.join(cwd, "node9.config.json"),
8594
- path39.join(os32.homedir(), ".node9", "config.json")
8896
+ path41.join(cwd, "node9.config.json"),
8897
+ path41.join(os34.homedir(), ".node9", "config.json")
8595
8898
  ]) {
8596
- if (!fs36.existsSync(configPath)) continue;
8597
- const cfg = JSON.parse(fs36.readFileSync(configPath, "utf-8"));
8899
+ if (!fs38.existsSync(configPath)) continue;
8900
+ const cfg = JSON.parse(fs38.readFileSync(configPath, "utf-8"));
8598
8901
  const hud = cfg.settings?.hud;
8599
8902
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
8600
8903
  }
@@ -9529,10 +9832,10 @@ function getAgentsStatus(homeDir2 = os11.homedir()) {
9529
9832
 
9530
9833
  // src/cli.ts
9531
9834
  init_daemon2();
9532
- import chalk25 from "chalk";
9533
- import fs37 from "fs";
9534
- import path40 from "path";
9535
- import os33 from "os";
9835
+ import chalk26 from "chalk";
9836
+ import fs39 from "fs";
9837
+ import path42 from "path";
9838
+ import os35 from "os";
9536
9839
  import { confirm as confirm2 } from "@inquirer/prompts";
9537
9840
 
9538
9841
  // src/utils/duration.ts
@@ -9761,19 +10064,19 @@ init_daemon();
9761
10064
  init_config();
9762
10065
  init_policy();
9763
10066
  import chalk5 from "chalk";
9764
- import fs23 from "fs";
10067
+ import fs24 from "fs";
9765
10068
  import { spawn as spawn6 } from "child_process";
9766
- import path25 from "path";
9767
- import os19 from "os";
10069
+ import path26 from "path";
10070
+ import os20 from "os";
9768
10071
 
9769
10072
  // src/undo.ts
9770
10073
  import { spawnSync as spawnSync5, spawn as spawn5 } from "child_process";
9771
10074
  import crypto3 from "crypto";
9772
- import fs21 from "fs";
10075
+ import fs22 from "fs";
9773
10076
  import net3 from "net";
9774
- import path23 from "path";
9775
- import os17 from "os";
9776
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path23.join(os17.tmpdir(), "node9-activity.sock");
10077
+ import path24 from "path";
10078
+ import os18 from "os";
10079
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path24.join(os18.tmpdir(), "node9-activity.sock");
9777
10080
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
9778
10081
  try {
9779
10082
  const payload = JSON.stringify({
@@ -9793,22 +10096,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
9793
10096
  } catch {
9794
10097
  }
9795
10098
  }
9796
- var SNAPSHOT_STACK_PATH = path23.join(os17.homedir(), ".node9", "snapshots.json");
9797
- var UNDO_LATEST_PATH = path23.join(os17.homedir(), ".node9", "undo_latest.txt");
10099
+ var SNAPSHOT_STACK_PATH = path24.join(os18.homedir(), ".node9", "snapshots.json");
10100
+ var UNDO_LATEST_PATH = path24.join(os18.homedir(), ".node9", "undo_latest.txt");
9798
10101
  var MAX_SNAPSHOTS = 10;
9799
10102
  var GIT_TIMEOUT = 15e3;
9800
10103
  function readStack() {
9801
10104
  try {
9802
- if (fs21.existsSync(SNAPSHOT_STACK_PATH))
9803
- return JSON.parse(fs21.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
10105
+ if (fs22.existsSync(SNAPSHOT_STACK_PATH))
10106
+ return JSON.parse(fs22.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
9804
10107
  } catch {
9805
10108
  }
9806
10109
  return [];
9807
10110
  }
9808
10111
  function writeStack(stack) {
9809
- const dir = path23.dirname(SNAPSHOT_STACK_PATH);
9810
- if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
9811
- fs21.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
10112
+ const dir = path24.dirname(SNAPSHOT_STACK_PATH);
10113
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
10114
+ fs22.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
9812
10115
  }
9813
10116
  function extractFilePath(args) {
9814
10117
  if (!args || typeof args !== "object") return null;
@@ -9828,12 +10131,12 @@ function buildArgsSummary(tool, args) {
9828
10131
  return "";
9829
10132
  }
9830
10133
  function findProjectRoot(filePath) {
9831
- let dir = path23.dirname(filePath);
10134
+ let dir = path24.dirname(filePath);
9832
10135
  while (true) {
9833
- if (fs21.existsSync(path23.join(dir, ".git")) || fs21.existsSync(path23.join(dir, "package.json"))) {
10136
+ if (fs22.existsSync(path24.join(dir, ".git")) || fs22.existsSync(path24.join(dir, "package.json"))) {
9834
10137
  return dir;
9835
10138
  }
9836
- const parent = path23.dirname(dir);
10139
+ const parent = path24.dirname(dir);
9837
10140
  if (parent === dir) return process.cwd();
9838
10141
  dir = parent;
9839
10142
  }
@@ -9841,7 +10144,7 @@ function findProjectRoot(filePath) {
9841
10144
  function normalizeCwdForHash(cwd) {
9842
10145
  let normalized;
9843
10146
  try {
9844
- normalized = fs21.realpathSync(cwd);
10147
+ normalized = fs22.realpathSync(cwd);
9845
10148
  } catch {
9846
10149
  normalized = cwd;
9847
10150
  }
@@ -9851,16 +10154,16 @@ function normalizeCwdForHash(cwd) {
9851
10154
  }
9852
10155
  function getShadowRepoDir(cwd) {
9853
10156
  const hash = crypto3.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
9854
- return path23.join(os17.homedir(), ".node9", "snapshots", hash);
10157
+ return path24.join(os18.homedir(), ".node9", "snapshots", hash);
9855
10158
  }
9856
10159
  function cleanOrphanedIndexFiles(shadowDir) {
9857
10160
  try {
9858
10161
  const cutoff = Date.now() - 6e4;
9859
- for (const f of fs21.readdirSync(shadowDir)) {
10162
+ for (const f of fs22.readdirSync(shadowDir)) {
9860
10163
  if (f.startsWith("index_")) {
9861
- const fp = path23.join(shadowDir, f);
10164
+ const fp = path24.join(shadowDir, f);
9862
10165
  try {
9863
- if (fs21.statSync(fp).mtimeMs < cutoff) fs21.unlinkSync(fp);
10166
+ if (fs22.statSync(fp).mtimeMs < cutoff) fs22.unlinkSync(fp);
9864
10167
  } catch {
9865
10168
  }
9866
10169
  }
@@ -9872,7 +10175,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
9872
10175
  const hardcoded = [".git", ".node9"];
9873
10176
  const lines = [...hardcoded, ...ignorePaths].join("\n");
9874
10177
  try {
9875
- fs21.writeFileSync(path23.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
10178
+ fs22.writeFileSync(path24.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9876
10179
  } catch {
9877
10180
  }
9878
10181
  }
@@ -9885,25 +10188,25 @@ function ensureShadowRepo(shadowDir, cwd) {
9885
10188
  timeout: 3e3
9886
10189
  });
9887
10190
  if (check.status === 0) {
9888
- const ptPath = path23.join(shadowDir, "project-path.txt");
10191
+ const ptPath = path24.join(shadowDir, "project-path.txt");
9889
10192
  try {
9890
- const stored = fs21.readFileSync(ptPath, "utf8").trim();
10193
+ const stored = fs22.readFileSync(ptPath, "utf8").trim();
9891
10194
  if (stored === normalizedCwd) return true;
9892
10195
  if (process.env.NODE9_DEBUG === "1")
9893
10196
  console.error(
9894
10197
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
9895
10198
  );
9896
- fs21.rmSync(shadowDir, { recursive: true, force: true });
10199
+ fs22.rmSync(shadowDir, { recursive: true, force: true });
9897
10200
  } catch {
9898
10201
  try {
9899
- fs21.writeFileSync(ptPath, normalizedCwd, "utf8");
10202
+ fs22.writeFileSync(ptPath, normalizedCwd, "utf8");
9900
10203
  } catch {
9901
10204
  }
9902
10205
  return true;
9903
10206
  }
9904
10207
  }
9905
10208
  try {
9906
- fs21.mkdirSync(shadowDir, { recursive: true });
10209
+ fs22.mkdirSync(shadowDir, { recursive: true });
9907
10210
  } catch {
9908
10211
  }
9909
10212
  const init = spawnSync5("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -9912,7 +10215,7 @@ function ensureShadowRepo(shadowDir, cwd) {
9912
10215
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
9913
10216
  return false;
9914
10217
  }
9915
- const configFile = path23.join(shadowDir, "config");
10218
+ const configFile = path24.join(shadowDir, "config");
9916
10219
  spawnSync5("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
9917
10220
  timeout: 3e3
9918
10221
  });
@@ -9920,7 +10223,7 @@ function ensureShadowRepo(shadowDir, cwd) {
9920
10223
  timeout: 3e3
9921
10224
  });
9922
10225
  try {
9923
- fs21.writeFileSync(path23.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
10226
+ fs22.writeFileSync(path24.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9924
10227
  } catch {
9925
10228
  }
9926
10229
  return true;
@@ -9940,12 +10243,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9940
10243
  let indexFile = null;
9941
10244
  try {
9942
10245
  const rawFilePath = extractFilePath(args);
9943
- const absFilePath = rawFilePath && path23.isAbsolute(rawFilePath) ? rawFilePath : null;
10246
+ const absFilePath = rawFilePath && path24.isAbsolute(rawFilePath) ? rawFilePath : null;
9944
10247
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
9945
10248
  const shadowDir = getShadowRepoDir(cwd);
9946
10249
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
9947
10250
  writeShadowExcludes(shadowDir, ignorePaths);
9948
- indexFile = path23.join(shadowDir, `index_${process.pid}_${Date.now()}`);
10251
+ indexFile = path24.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9949
10252
  const shadowEnv = {
9950
10253
  ...process.env,
9951
10254
  GIT_DIR: shadowDir,
@@ -10017,7 +10320,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
10017
10320
  writeStack(stack);
10018
10321
  const entry = stack[stack.length - 1];
10019
10322
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
10020
- fs21.writeFileSync(UNDO_LATEST_PATH, commitHash);
10323
+ fs22.writeFileSync(UNDO_LATEST_PATH, commitHash);
10021
10324
  if (shouldGc) {
10022
10325
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
10023
10326
  }
@@ -10028,7 +10331,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
10028
10331
  } finally {
10029
10332
  if (indexFile) {
10030
10333
  try {
10031
- fs21.unlinkSync(indexFile);
10334
+ fs22.unlinkSync(indexFile);
10032
10335
  } catch {
10033
10336
  }
10034
10337
  }
@@ -10104,9 +10407,9 @@ function applyUndo(hash, cwd) {
10104
10407
  timeout: GIT_TIMEOUT
10105
10408
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
10106
10409
  for (const file of [...tracked, ...untracked]) {
10107
- const fullPath = path23.join(dir, file);
10108
- if (!snapshotFiles.has(file) && fs21.existsSync(fullPath)) {
10109
- fs21.unlinkSync(fullPath);
10410
+ const fullPath = path24.join(dir, file);
10411
+ if (!snapshotFiles.has(file) && fs22.existsSync(fullPath)) {
10412
+ fs22.unlinkSync(fullPath);
10110
10413
  }
10111
10414
  }
10112
10415
  return true;
@@ -10116,12 +10419,12 @@ function applyUndo(hash, cwd) {
10116
10419
  }
10117
10420
 
10118
10421
  // src/skill-pin.ts
10119
- import fs22 from "fs";
10120
- import path24 from "path";
10121
- import os18 from "os";
10422
+ import fs23 from "fs";
10423
+ import path25 from "path";
10424
+ import os19 from "os";
10122
10425
  import crypto4 from "crypto";
10123
10426
  function getPinsFilePath() {
10124
- return path24.join(os18.homedir(), ".node9", "skill-pins.json");
10427
+ return path25.join(os19.homedir(), ".node9", "skill-pins.json");
10125
10428
  }
10126
10429
  var MAX_FILES = 5e3;
10127
10430
  var MAX_TOTAL_BYTES = 50 * 1024 * 1024;
@@ -10135,18 +10438,18 @@ function walkDir(root) {
10135
10438
  if (out.length >= MAX_FILES) return;
10136
10439
  let entries;
10137
10440
  try {
10138
- entries = fs22.readdirSync(dir, { withFileTypes: true });
10441
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
10139
10442
  } catch {
10140
10443
  return;
10141
10444
  }
10142
10445
  entries.sort((a, b) => a.name.localeCompare(b.name));
10143
10446
  for (const entry of entries) {
10144
10447
  if (out.length >= MAX_FILES) return;
10145
- const full = path24.join(dir, entry.name);
10146
- const rel = relDir ? path24.posix.join(relDir, entry.name) : entry.name;
10448
+ const full = path25.join(dir, entry.name);
10449
+ const rel = relDir ? path25.posix.join(relDir, entry.name) : entry.name;
10147
10450
  let lst;
10148
10451
  try {
10149
- lst = fs22.lstatSync(full);
10452
+ lst = fs23.lstatSync(full);
10150
10453
  } catch {
10151
10454
  continue;
10152
10455
  }
@@ -10158,7 +10461,7 @@ function walkDir(root) {
10158
10461
  if (!lst.isFile()) continue;
10159
10462
  if (totalBytes + lst.size > MAX_TOTAL_BYTES) continue;
10160
10463
  try {
10161
- const buf = fs22.readFileSync(full);
10464
+ const buf = fs23.readFileSync(full);
10162
10465
  totalBytes += buf.length;
10163
10466
  out.push({ rel, hash: sha256Bytes(buf) });
10164
10467
  } catch {
@@ -10172,14 +10475,14 @@ function walkDir(root) {
10172
10475
  function hashSkillRoot(absPath) {
10173
10476
  let lst;
10174
10477
  try {
10175
- lst = fs22.lstatSync(absPath);
10478
+ lst = fs23.lstatSync(absPath);
10176
10479
  } catch {
10177
10480
  return { exists: false, contentHash: "", fileCount: 0 };
10178
10481
  }
10179
10482
  if (lst.isSymbolicLink()) return { exists: false, contentHash: "", fileCount: 0 };
10180
10483
  if (lst.isFile()) {
10181
10484
  try {
10182
- return { exists: true, contentHash: sha256Bytes(fs22.readFileSync(absPath)), fileCount: 1 };
10485
+ return { exists: true, contentHash: sha256Bytes(fs23.readFileSync(absPath)), fileCount: 1 };
10183
10486
  } catch {
10184
10487
  return { exists: false, contentHash: "", fileCount: 0 };
10185
10488
  }
@@ -10197,7 +10500,7 @@ function getRootKey(absPath) {
10197
10500
  function readSkillPinsSafe() {
10198
10501
  const filePath = getPinsFilePath();
10199
10502
  try {
10200
- const raw = fs22.readFileSync(filePath, "utf-8");
10503
+ const raw = fs23.readFileSync(filePath, "utf-8");
10201
10504
  if (!raw.trim()) return { ok: false, reason: "corrupt", detail: "empty file" };
10202
10505
  const parsed = JSON.parse(raw);
10203
10506
  if (!parsed.roots || typeof parsed.roots !== "object" || Array.isArray(parsed.roots)) {
@@ -10217,10 +10520,10 @@ function readSkillPins() {
10217
10520
  }
10218
10521
  function writeSkillPins(data) {
10219
10522
  const filePath = getPinsFilePath();
10220
- fs22.mkdirSync(path24.dirname(filePath), { recursive: true });
10523
+ fs23.mkdirSync(path25.dirname(filePath), { recursive: true });
10221
10524
  const tmp = `${filePath}.${crypto4.randomBytes(6).toString("hex")}.tmp`;
10222
- fs22.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10223
- fs22.renameSync(tmp, filePath);
10525
+ fs23.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
10526
+ fs23.renameSync(tmp, filePath);
10224
10527
  }
10225
10528
  function removePin(rootKey) {
10226
10529
  const pins = readSkillPins();
@@ -10264,36 +10567,36 @@ function verifyAndPinRoots(roots) {
10264
10567
  return { kind: "verified" };
10265
10568
  }
10266
10569
  function defaultSkillRoots(_cwd) {
10267
- const marketplaces = path24.join(os18.homedir(), ".claude", "plugins", "marketplaces");
10570
+ const marketplaces = path25.join(os19.homedir(), ".claude", "plugins", "marketplaces");
10268
10571
  const roots = [];
10269
10572
  let registries;
10270
10573
  try {
10271
- registries = fs22.readdirSync(marketplaces, { withFileTypes: true });
10574
+ registries = fs23.readdirSync(marketplaces, { withFileTypes: true });
10272
10575
  } catch {
10273
10576
  return [];
10274
10577
  }
10275
10578
  for (const registry of registries) {
10276
10579
  if (!registry.isDirectory()) continue;
10277
- const pluginsDir = path24.join(marketplaces, registry.name, "plugins");
10580
+ const pluginsDir = path25.join(marketplaces, registry.name, "plugins");
10278
10581
  let plugins;
10279
10582
  try {
10280
- plugins = fs22.readdirSync(pluginsDir, { withFileTypes: true });
10583
+ plugins = fs23.readdirSync(pluginsDir, { withFileTypes: true });
10281
10584
  } catch {
10282
10585
  continue;
10283
10586
  }
10284
10587
  for (const plugin of plugins) {
10285
10588
  if (!plugin.isDirectory()) continue;
10286
- roots.push(path24.join(pluginsDir, plugin.name));
10589
+ roots.push(path25.join(pluginsDir, plugin.name));
10287
10590
  }
10288
10591
  }
10289
10592
  return roots;
10290
10593
  }
10291
10594
  function resolveUserSkillRoot(entry, cwd) {
10292
10595
  if (!entry) return null;
10293
- if (entry.startsWith("~/") || entry === "~") return path24.join(os18.homedir(), entry.slice(1));
10294
- if (path24.isAbsolute(entry)) return entry;
10295
- if (!cwd || !path24.isAbsolute(cwd)) return null;
10296
- return path24.join(cwd, entry);
10596
+ if (entry.startsWith("~/") || entry === "~") return path25.join(os19.homedir(), entry.slice(1));
10597
+ if (path25.isAbsolute(entry)) return entry;
10598
+ if (!cwd || !path25.isAbsolute(cwd)) return null;
10599
+ return path25.join(cwd, entry);
10297
10600
  }
10298
10601
 
10299
10602
  // src/cli/commands/check.ts
@@ -10311,9 +10614,9 @@ function registerCheckCommand(program2) {
10311
10614
  } catch (err2) {
10312
10615
  const tempConfig = getConfig();
10313
10616
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
10314
- const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
10617
+ const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
10315
10618
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
10316
- fs23.appendFileSync(
10619
+ fs24.appendFileSync(
10317
10620
  logPath,
10318
10621
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
10319
10622
  RAW: ${raw}
@@ -10326,11 +10629,11 @@ RAW: ${raw}
10326
10629
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
10327
10630
  try {
10328
10631
  const scriptPath = process.argv[1];
10329
- if (typeof scriptPath !== "string" || !path25.isAbsolute(scriptPath))
10632
+ if (typeof scriptPath !== "string" || !path26.isAbsolute(scriptPath))
10330
10633
  throw new Error("node9: argv[1] is not an absolute path");
10331
- const resolvedScript = fs23.realpathSync(scriptPath);
10332
- const packageDist = fs23.realpathSync(path25.resolve(__dirname, "../.."));
10333
- if (!resolvedScript.startsWith(packageDist + path25.sep) && resolvedScript !== packageDist)
10634
+ const resolvedScript = fs24.realpathSync(scriptPath);
10635
+ const packageDist = fs24.realpathSync(path26.resolve(__dirname, "../.."));
10636
+ if (!resolvedScript.startsWith(packageDist + path26.sep) && resolvedScript !== packageDist)
10334
10637
  throw new Error(
10335
10638
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
10336
10639
  );
@@ -10352,10 +10655,10 @@ RAW: ${raw}
10352
10655
  });
10353
10656
  d.unref();
10354
10657
  } catch (spawnErr) {
10355
- const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
10658
+ const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
10356
10659
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
10357
10660
  try {
10358
- fs23.appendFileSync(
10661
+ fs24.appendFileSync(
10359
10662
  logPath,
10360
10663
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
10361
10664
  `
@@ -10365,10 +10668,10 @@ RAW: ${raw}
10365
10668
  }
10366
10669
  }
10367
10670
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
10368
- const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
10369
- if (!fs23.existsSync(path25.dirname(logPath)))
10370
- fs23.mkdirSync(path25.dirname(logPath), { recursive: true });
10371
- fs23.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10671
+ const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
10672
+ if (!fs24.existsSync(path26.dirname(logPath)))
10673
+ fs24.mkdirSync(path26.dirname(logPath), { recursive: true });
10674
+ fs24.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10372
10675
  `);
10373
10676
  }
10374
10677
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -10381,8 +10684,8 @@ RAW: ${raw}
10381
10684
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
10382
10685
  let ttyFd = null;
10383
10686
  try {
10384
- ttyFd = fs23.openSync("/dev/tty", "w");
10385
- const writeTty = (line) => fs23.writeSync(ttyFd, line + "\n");
10687
+ ttyFd = fs24.openSync("/dev/tty", "w");
10688
+ const writeTty = (line) => fs24.writeSync(ttyFd, line + "\n");
10386
10689
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
10387
10690
  writeTty(chalk5.bgRed.white.bold(`
10388
10691
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -10401,7 +10704,7 @@ RAW: ${raw}
10401
10704
  } finally {
10402
10705
  if (ttyFd !== null)
10403
10706
  try {
10404
- fs23.closeSync(ttyFd);
10707
+ fs24.closeSync(ttyFd);
10405
10708
  } catch {
10406
10709
  }
10407
10710
  }
@@ -10435,17 +10738,17 @@ RAW: ${raw}
10435
10738
  const safeSessionId = /^[A-Za-z0-9_\-]{1,128}$/.test(rawSessionId) ? rawSessionId : "";
10436
10739
  if (skillPinCfg.enabled && safeSessionId) {
10437
10740
  try {
10438
- const sessionsDir = path25.join(os19.homedir(), ".node9", "skill-sessions");
10439
- const flagPath = path25.join(sessionsDir, `${safeSessionId}.json`);
10741
+ const sessionsDir = path26.join(os20.homedir(), ".node9", "skill-sessions");
10742
+ const flagPath = path26.join(sessionsDir, `${safeSessionId}.json`);
10440
10743
  let flag = null;
10441
10744
  try {
10442
- flag = JSON.parse(fs23.readFileSync(flagPath, "utf-8"));
10745
+ flag = JSON.parse(fs24.readFileSync(flagPath, "utf-8"));
10443
10746
  } catch {
10444
10747
  }
10445
10748
  const writeFlag = (data2) => {
10446
10749
  try {
10447
- fs23.mkdirSync(sessionsDir, { recursive: true });
10448
- fs23.writeFileSync(
10750
+ fs24.mkdirSync(sessionsDir, { recursive: true });
10751
+ fs24.writeFileSync(
10449
10752
  flagPath,
10450
10753
  JSON.stringify({ ...data2, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
10451
10754
  { mode: 384 }
@@ -10456,8 +10759,8 @@ RAW: ${raw}
10456
10759
  const sendSkillWarn = (detail, recoveryCmd) => {
10457
10760
  let ttyFd = null;
10458
10761
  try {
10459
- ttyFd = fs23.openSync("/dev/tty", "w");
10460
- const w = (line) => fs23.writeSync(ttyFd, line + "\n");
10762
+ ttyFd = fs24.openSync("/dev/tty", "w");
10763
+ const w = (line) => fs24.writeSync(ttyFd, line + "\n");
10461
10764
  w(chalk5.yellow(`
10462
10765
  \u26A0\uFE0F Node9: installed skill drift detected`));
10463
10766
  w(chalk5.gray(` ${detail}`));
@@ -10472,7 +10775,7 @@ RAW: ${raw}
10472
10775
  } finally {
10473
10776
  if (ttyFd !== null)
10474
10777
  try {
10475
- fs23.closeSync(ttyFd);
10778
+ fs24.closeSync(ttyFd);
10476
10779
  } catch {
10477
10780
  }
10478
10781
  }
@@ -10488,7 +10791,7 @@ RAW: ${raw}
10488
10791
  return;
10489
10792
  }
10490
10793
  if (!flag || flag.state !== "verified" && flag.state !== "warned") {
10491
- const absoluteCwd = typeof payload.cwd === "string" && path25.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10794
+ const absoluteCwd = typeof payload.cwd === "string" && path26.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10492
10795
  const extraRoots = skillPinCfg.roots;
10493
10796
  const resolvedExtra = extraRoots.map((r) => resolveUserSkillRoot(r, absoluteCwd)).filter((r) => typeof r === "string");
10494
10797
  const roots = [...defaultSkillRoots(absoluteCwd), ...resolvedExtra];
@@ -10529,10 +10832,10 @@ RAW: ${raw}
10529
10832
  }
10530
10833
  try {
10531
10834
  const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
10532
- for (const name of fs23.readdirSync(sessionsDir)) {
10533
- const p = path25.join(sessionsDir, name);
10835
+ for (const name of fs24.readdirSync(sessionsDir)) {
10836
+ const p = path26.join(sessionsDir, name);
10534
10837
  try {
10535
- if (fs23.statSync(p).mtimeMs < cutoff) fs23.unlinkSync(p);
10838
+ if (fs24.statSync(p).mtimeMs < cutoff) fs24.unlinkSync(p);
10536
10839
  } catch {
10537
10840
  }
10538
10841
  }
@@ -10542,9 +10845,9 @@ RAW: ${raw}
10542
10845
  } catch (err2) {
10543
10846
  if (process.env.NODE9_DEBUG === "1") {
10544
10847
  try {
10545
- const dbg = path25.join(os19.homedir(), ".node9", "hook-debug.log");
10848
+ const dbg = path26.join(os20.homedir(), ".node9", "hook-debug.log");
10546
10849
  const msg = err2 instanceof Error ? err2.message : String(err2);
10547
- fs23.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
10850
+ fs24.appendFileSync(dbg, `[${(/* @__PURE__ */ new Date()).toISOString()}] SKILL_PIN_ERROR: ${msg}
10548
10851
  `);
10549
10852
  } catch {
10550
10853
  }
@@ -10554,7 +10857,7 @@ RAW: ${raw}
10554
10857
  if (shouldSnapshot(toolName, toolInput, config)) {
10555
10858
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
10556
10859
  }
10557
- const safeCwdForAuth = typeof payload.cwd === "string" && path25.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10860
+ const safeCwdForAuth = typeof payload.cwd === "string" && path26.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10558
10861
  const result = await authorizeHeadless(toolName, toolInput, meta, {
10559
10862
  cwd: safeCwdForAuth
10560
10863
  });
@@ -10566,12 +10869,12 @@ RAW: ${raw}
10566
10869
  }
10567
10870
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
10568
10871
  try {
10569
- const tty = fs23.openSync("/dev/tty", "w");
10570
- fs23.writeSync(
10872
+ const tty = fs24.openSync("/dev/tty", "w");
10873
+ fs24.writeSync(
10571
10874
  tty,
10572
10875
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
10573
10876
  );
10574
- fs23.closeSync(tty);
10877
+ fs24.closeSync(tty);
10575
10878
  } catch {
10576
10879
  }
10577
10880
  const daemonReady = await autoStartDaemonAndWait();
@@ -10598,9 +10901,9 @@ RAW: ${raw}
10598
10901
  });
10599
10902
  } catch (err2) {
10600
10903
  if (process.env.NODE9_DEBUG === "1") {
10601
- const logPath = path25.join(os19.homedir(), ".node9", "hook-debug.log");
10904
+ const logPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
10602
10905
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
10603
- fs23.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10906
+ fs24.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10604
10907
  `);
10605
10908
  }
10606
10909
  process.exit(0);
@@ -10637,9 +10940,9 @@ RAW: ${raw}
10637
10940
  init_audit();
10638
10941
  init_config();
10639
10942
  init_policy();
10640
- import fs24 from "fs";
10641
- import path26 from "path";
10642
- import os20 from "os";
10943
+ import fs25 from "fs";
10944
+ import path27 from "path";
10945
+ import os21 from "os";
10643
10946
  init_daemon();
10644
10947
 
10645
10948
  // src/utils/cp-mv-parser.ts
@@ -10712,10 +11015,10 @@ function registerLogCommand(program2) {
10712
11015
  decision: "allowed",
10713
11016
  source: "post-hook"
10714
11017
  };
10715
- const logPath = path26.join(os20.homedir(), ".node9", "audit.log");
10716
- if (!fs24.existsSync(path26.dirname(logPath)))
10717
- fs24.mkdirSync(path26.dirname(logPath), { recursive: true });
10718
- fs24.appendFileSync(logPath, JSON.stringify(entry) + "\n");
11018
+ const logPath = path27.join(os21.homedir(), ".node9", "audit.log");
11019
+ if (!fs25.existsSync(path27.dirname(logPath)))
11020
+ fs25.mkdirSync(path27.dirname(logPath), { recursive: true });
11021
+ fs25.appendFileSync(logPath, JSON.stringify(entry) + "\n");
10719
11022
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
10720
11023
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
10721
11024
  if (command) {
@@ -10748,7 +11051,7 @@ function registerLogCommand(program2) {
10748
11051
  }
10749
11052
  }
10750
11053
  }
10751
- const safeCwd = typeof payload.cwd === "string" && path26.isAbsolute(payload.cwd) ? payload.cwd : void 0;
11054
+ const safeCwd = typeof payload.cwd === "string" && path27.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10752
11055
  const config = getConfig(safeCwd);
10753
11056
  if (shouldSnapshot(tool, {}, config)) {
10754
11057
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -10757,9 +11060,9 @@ function registerLogCommand(program2) {
10757
11060
  const msg = err2 instanceof Error ? err2.message : String(err2);
10758
11061
  process.stderr.write(`[Node9] audit log error: ${msg}
10759
11062
  `);
10760
- const debugPath = path26.join(os20.homedir(), ".node9", "hook-debug.log");
11063
+ const debugPath = path27.join(os21.homedir(), ".node9", "hook-debug.log");
10761
11064
  try {
10762
- fs24.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
11065
+ fs25.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
10763
11066
  `);
10764
11067
  } catch {
10765
11068
  }
@@ -11160,13 +11463,13 @@ function registerConfigShowCommand(program2) {
11160
11463
  // src/cli/commands/doctor.ts
11161
11464
  init_daemon();
11162
11465
  import chalk7 from "chalk";
11163
- import fs25 from "fs";
11164
- import path27 from "path";
11165
- import os21 from "os";
11466
+ import fs26 from "fs";
11467
+ import path28 from "path";
11468
+ import os22 from "os";
11166
11469
  import { execSync as execSync2 } from "child_process";
11167
11470
  function registerDoctorCommand(program2, version2) {
11168
11471
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
11169
- const homeDir2 = os21.homedir();
11472
+ const homeDir2 = os22.homedir();
11170
11473
  let failures = 0;
11171
11474
  function pass(msg) {
11172
11475
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -11215,10 +11518,10 @@ function registerDoctorCommand(program2, version2) {
11215
11518
  );
11216
11519
  }
11217
11520
  section("Configuration");
11218
- const globalConfigPath = path27.join(homeDir2, ".node9", "config.json");
11219
- if (fs25.existsSync(globalConfigPath)) {
11521
+ const globalConfigPath = path28.join(homeDir2, ".node9", "config.json");
11522
+ if (fs26.existsSync(globalConfigPath)) {
11220
11523
  try {
11221
- JSON.parse(fs25.readFileSync(globalConfigPath, "utf-8"));
11524
+ JSON.parse(fs26.readFileSync(globalConfigPath, "utf-8"));
11222
11525
  pass("~/.node9/config.json found and valid");
11223
11526
  } catch {
11224
11527
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -11226,10 +11529,10 @@ function registerDoctorCommand(program2, version2) {
11226
11529
  } else {
11227
11530
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
11228
11531
  }
11229
- const projectConfigPath = path27.join(process.cwd(), "node9.config.json");
11230
- if (fs25.existsSync(projectConfigPath)) {
11532
+ const projectConfigPath = path28.join(process.cwd(), "node9.config.json");
11533
+ if (fs26.existsSync(projectConfigPath)) {
11231
11534
  try {
11232
- JSON.parse(fs25.readFileSync(projectConfigPath, "utf-8"));
11535
+ JSON.parse(fs26.readFileSync(projectConfigPath, "utf-8"));
11233
11536
  pass("node9.config.json found and valid (project)");
11234
11537
  } catch {
11235
11538
  fail(
@@ -11238,8 +11541,8 @@ function registerDoctorCommand(program2, version2) {
11238
11541
  );
11239
11542
  }
11240
11543
  }
11241
- const credsPath = path27.join(homeDir2, ".node9", "credentials.json");
11242
- if (fs25.existsSync(credsPath)) {
11544
+ const credsPath = path28.join(homeDir2, ".node9", "credentials.json");
11545
+ if (fs26.existsSync(credsPath)) {
11243
11546
  pass("Cloud credentials found (~/.node9/credentials.json)");
11244
11547
  } else {
11245
11548
  warn(
@@ -11248,10 +11551,10 @@ function registerDoctorCommand(program2, version2) {
11248
11551
  );
11249
11552
  }
11250
11553
  section("Agent Hooks");
11251
- const claudeSettingsPath = path27.join(homeDir2, ".claude", "settings.json");
11252
- if (fs25.existsSync(claudeSettingsPath)) {
11554
+ const claudeSettingsPath = path28.join(homeDir2, ".claude", "settings.json");
11555
+ if (fs26.existsSync(claudeSettingsPath)) {
11253
11556
  try {
11254
- const cs = JSON.parse(fs25.readFileSync(claudeSettingsPath, "utf-8"));
11557
+ const cs = JSON.parse(fs26.readFileSync(claudeSettingsPath, "utf-8"));
11255
11558
  const hasHook = cs.hooks?.PreToolUse?.some(
11256
11559
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
11257
11560
  );
@@ -11267,10 +11570,10 @@ function registerDoctorCommand(program2, version2) {
11267
11570
  } else {
11268
11571
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
11269
11572
  }
11270
- const geminiSettingsPath = path27.join(homeDir2, ".gemini", "settings.json");
11271
- if (fs25.existsSync(geminiSettingsPath)) {
11573
+ const geminiSettingsPath = path28.join(homeDir2, ".gemini", "settings.json");
11574
+ if (fs26.existsSync(geminiSettingsPath)) {
11272
11575
  try {
11273
- const gs = JSON.parse(fs25.readFileSync(geminiSettingsPath, "utf-8"));
11576
+ const gs = JSON.parse(fs26.readFileSync(geminiSettingsPath, "utf-8"));
11274
11577
  const hasHook = gs.hooks?.BeforeTool?.some(
11275
11578
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
11276
11579
  );
@@ -11286,10 +11589,10 @@ function registerDoctorCommand(program2, version2) {
11286
11589
  } else {
11287
11590
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
11288
11591
  }
11289
- const cursorHooksPath = path27.join(homeDir2, ".cursor", "hooks.json");
11290
- if (fs25.existsSync(cursorHooksPath)) {
11592
+ const cursorHooksPath = path28.join(homeDir2, ".cursor", "hooks.json");
11593
+ if (fs26.existsSync(cursorHooksPath)) {
11291
11594
  try {
11292
- const cur = JSON.parse(fs25.readFileSync(cursorHooksPath, "utf-8"));
11595
+ const cur = JSON.parse(fs26.readFileSync(cursorHooksPath, "utf-8"));
11293
11596
  const hasHook = cur.hooks?.preToolUse?.some(
11294
11597
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
11295
11598
  );
@@ -11327,9 +11630,9 @@ function registerDoctorCommand(program2, version2) {
11327
11630
 
11328
11631
  // src/cli/commands/audit.ts
11329
11632
  import chalk8 from "chalk";
11330
- import fs26 from "fs";
11331
- import path28 from "path";
11332
- import os22 from "os";
11633
+ import fs27 from "fs";
11634
+ import path29 from "path";
11635
+ import os23 from "os";
11333
11636
  function formatRelativeTime(timestamp) {
11334
11637
  const diff = Date.now() - new Date(timestamp).getTime();
11335
11638
  const sec = Math.floor(diff / 1e3);
@@ -11342,14 +11645,14 @@ function formatRelativeTime(timestamp) {
11342
11645
  }
11343
11646
  function registerAuditCommand(program2) {
11344
11647
  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) => {
11345
- const logPath = path28.join(os22.homedir(), ".node9", "audit.log");
11346
- if (!fs26.existsSync(logPath)) {
11648
+ const logPath = path29.join(os23.homedir(), ".node9", "audit.log");
11649
+ if (!fs27.existsSync(logPath)) {
11347
11650
  console.log(
11348
11651
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
11349
11652
  );
11350
11653
  return;
11351
11654
  }
11352
- const raw = fs26.readFileSync(logPath, "utf-8");
11655
+ const raw = fs27.readFileSync(logPath, "utf-8");
11353
11656
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
11354
11657
  let entries = lines.flatMap((line) => {
11355
11658
  try {
@@ -11403,9 +11706,9 @@ function registerAuditCommand(program2) {
11403
11706
 
11404
11707
  // src/cli/commands/report.ts
11405
11708
  import chalk9 from "chalk";
11406
- import fs27 from "fs";
11407
- import path29 from "path";
11408
- import os23 from "os";
11709
+ import fs28 from "fs";
11710
+ import path30 from "path";
11711
+ import os24 from "os";
11409
11712
  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;
11410
11713
  function buildTestTimestamps(allEntries) {
11411
11714
  const testTs = /* @__PURE__ */ new Set();
@@ -11452,8 +11755,8 @@ function getDateRange(period) {
11452
11755
  }
11453
11756
  }
11454
11757
  function parseAuditLog(logPath) {
11455
- if (!fs27.existsSync(logPath)) return [];
11456
- const raw = fs27.readFileSync(logPath, "utf-8");
11758
+ if (!fs28.existsSync(logPath)) return [];
11759
+ const raw = fs28.readFileSync(logPath, "utf-8");
11457
11760
  return raw.split("\n").flatMap((line) => {
11458
11761
  if (!line.trim()) return [];
11459
11762
  try {
@@ -11520,34 +11823,38 @@ function loadClaudeCost(start, end) {
11520
11823
  byDay: /* @__PURE__ */ new Map(),
11521
11824
  byModel: /* @__PURE__ */ new Map(),
11522
11825
  inputTokens: 0,
11826
+ outputTokens: 0,
11827
+ cacheWriteTokens: 0,
11523
11828
  cacheReadTokens: 0
11524
11829
  };
11525
- const projectsDir = path29.join(os23.homedir(), ".claude", "projects");
11526
- if (!fs27.existsSync(projectsDir)) return empty;
11830
+ const projectsDir = path30.join(os24.homedir(), ".claude", "projects");
11831
+ if (!fs28.existsSync(projectsDir)) return empty;
11527
11832
  let dirs;
11528
11833
  try {
11529
- dirs = fs27.readdirSync(projectsDir);
11834
+ dirs = fs28.readdirSync(projectsDir);
11530
11835
  } catch {
11531
11836
  return empty;
11532
11837
  }
11533
11838
  let total = 0;
11534
11839
  let inputTokens = 0;
11840
+ let outputTokens = 0;
11841
+ let cacheWriteTokens = 0;
11535
11842
  let cacheReadTokens = 0;
11536
11843
  const byDay = /* @__PURE__ */ new Map();
11537
11844
  const byModel = /* @__PURE__ */ new Map();
11538
11845
  for (const proj of dirs) {
11539
- const projPath = path29.join(projectsDir, proj);
11846
+ const projPath = path30.join(projectsDir, proj);
11540
11847
  let files;
11541
11848
  try {
11542
- const stat = fs27.statSync(projPath);
11849
+ const stat = fs28.statSync(projPath);
11543
11850
  if (!stat.isDirectory()) continue;
11544
- files = fs27.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11851
+ files = fs28.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11545
11852
  } catch {
11546
11853
  continue;
11547
11854
  }
11548
11855
  for (const file of files) {
11549
11856
  try {
11550
- const raw = fs27.readFileSync(path29.join(projPath, file), "utf-8");
11857
+ const raw = fs28.readFileSync(path30.join(projPath, file), "utf-8");
11551
11858
  for (const line of raw.split("\n")) {
11552
11859
  if (!line.trim()) continue;
11553
11860
  let entry;
@@ -11572,6 +11879,8 @@ function loadClaudeCost(start, end) {
11572
11879
  const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
11573
11880
  total += cost;
11574
11881
  inputTokens += inp;
11882
+ outputTokens += out;
11883
+ cacheWriteTokens += cw;
11575
11884
  cacheReadTokens += cr;
11576
11885
  const dateKey = entry.timestamp.slice(0, 10);
11577
11886
  byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
@@ -11583,15 +11892,24 @@ function loadClaudeCost(start, end) {
11583
11892
  }
11584
11893
  }
11585
11894
  }
11586
- return { total, byDay, byModel, inputTokens, cacheReadTokens };
11895
+ return { total, byDay, byModel, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens };
11587
11896
  }
11588
11897
  function registerReportCommand(program2) {
11589
11898
  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) => {
11590
11899
  const period = ["today", "7d", "30d", "month"].includes(
11591
11900
  options.period
11592
11901
  ) ? options.period : "7d";
11593
- const logPath = path29.join(os23.homedir(), ".node9", "audit.log");
11902
+ const logPath = path30.join(os24.homedir(), ".node9", "audit.log");
11594
11903
  const allEntries = parseAuditLog(logPath);
11904
+ const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
11905
+ if (unackedDlp.length > 0) {
11906
+ console.log("");
11907
+ console.log(
11908
+ chalk9.bgRed.white.bold(
11909
+ ` \u26A0\uFE0F DLP ALERT: ${unackedDlp.length} secret${unackedDlp.length !== 1 ? "s" : ""} found in Claude response text `
11910
+ ) + " " + chalk9.yellow("\u2192 run: node9 dlp")
11911
+ );
11912
+ }
11595
11913
  if (allEntries.length === 0) {
11596
11914
  console.log(
11597
11915
  chalk9.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
@@ -11604,6 +11922,8 @@ function registerReportCommand(program2) {
11604
11922
  byDay: costByDay,
11605
11923
  byModel: costByModel,
11606
11924
  inputTokens: costInputTokens,
11925
+ outputTokens: costOutputTokens,
11926
+ cacheWriteTokens: costCacheWrite,
11607
11927
  cacheReadTokens: costCacheRead
11608
11928
  } = loadClaudeCost(start, end);
11609
11929
  const periodMs = end.getTime() - start.getTime();
@@ -11621,6 +11941,7 @@ function registerReportCommand(program2) {
11621
11941
  let filteredTestCount = 0;
11622
11942
  const entries = allEntries.filter((e) => {
11623
11943
  if (e.source === "post-hook") return false;
11944
+ if (e.source === "response-dlp") return false;
11624
11945
  const ts = new Date(e.ts);
11625
11946
  if (ts < start || ts > end) return false;
11626
11947
  if (excludeTests && isTestEntry(e, testTs)) {
@@ -11754,7 +12075,7 @@ function registerReportCommand(program2) {
11754
12075
  if (topBlocks.length === 0) {
11755
12076
  console.log(" " + " ".repeat(COL) + " " + chalk9.dim("nothing blocked \u2713"));
11756
12077
  }
11757
- if (agentMap.size > 1) {
12078
+ if (agentMap.size >= 1) {
11758
12079
  console.log("");
11759
12080
  console.log(" " + chalk9.bold("Agents"));
11760
12081
  console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
@@ -11804,6 +12125,40 @@ function registerReportCommand(program2) {
11804
12125
  );
11805
12126
  }
11806
12127
  }
12128
+ const totalTokens = costInputTokens + costOutputTokens + costCacheWrite + costCacheRead;
12129
+ if (totalTokens > 0) {
12130
+ const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
12131
+ console.log("");
12132
+ console.log(" " + chalk9.bold("Tokens") + " " + chalk9.dim(`${num(totalTokens)} total`));
12133
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
12134
+ const tokenRows = [
12135
+ ["Input", costInputTokens, chalk9.cyan(num(costInputTokens))],
12136
+ ["Output", costOutputTokens, chalk9.white(num(costOutputTokens))],
12137
+ ["Cache write", costCacheWrite, chalk9.yellow(num(costCacheWrite))],
12138
+ ["Cache read", costCacheRead, chalk9.green(num(costCacheRead))]
12139
+ ];
12140
+ const maxTok = Math.max(
12141
+ costInputTokens,
12142
+ costOutputTokens,
12143
+ costCacheWrite,
12144
+ costCacheRead,
12145
+ 1
12146
+ );
12147
+ const TOK_BAR = Math.max(6, Math.min(20, W - 30));
12148
+ const TOK_LABEL = 14;
12149
+ for (const [label, count, colored] of tokenRows) {
12150
+ if (count === 0) continue;
12151
+ const b = colorBar(count, maxTok, TOK_BAR);
12152
+ console.log(" " + chalk9.white(label.padEnd(TOK_LABEL)) + b + " " + colored);
12153
+ }
12154
+ if (cacheHitPct > 0) {
12155
+ console.log(
12156
+ " " + chalk9.dim(
12157
+ `Cache hit rate: ${cacheHitPct}% (saves ~${fmtCost(costCacheRead * 27e-7)} vs fresh input)`
12158
+ )
12159
+ );
12160
+ }
12161
+ }
11807
12162
  if (costUSD > 0) {
11808
12163
  const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
11809
12164
  const avgPerDay = costUSD / periodDays;
@@ -11828,6 +12183,33 @@ function registerReportCommand(program2) {
11828
12183
  );
11829
12184
  }
11830
12185
  }
12186
+ const responseDlpEntries = allEntries.filter((e) => {
12187
+ if (e.source !== "response-dlp") return false;
12188
+ const ts = new Date(e.ts);
12189
+ return ts >= start && ts <= end;
12190
+ });
12191
+ if (responseDlpEntries.length > 0) {
12192
+ console.log("");
12193
+ console.log(
12194
+ " " + chalk9.red.bold("\u26A0\uFE0F Response DLP") + chalk9.dim(" \xB7 ") + chalk9.red(
12195
+ `${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
12196
+ )
12197
+ );
12198
+ console.log(" " + chalk9.dim("\u2500".repeat(Math.min(60, W - 4))));
12199
+ console.log(
12200
+ " " + chalk9.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
12201
+ );
12202
+ console.log(" " + chalk9.yellow("Rotate affected keys immediately."));
12203
+ for (const e of responseDlpEntries.slice(0, 5)) {
12204
+ const ts = chalk9.dim(fmtDate(e.ts) + " ");
12205
+ const pattern = chalk9.red(e.dlpPattern ?? "DLP");
12206
+ const sample = chalk9.gray(e.dlpSample ?? "");
12207
+ console.log(` ${ts}${pattern} ${sample}`);
12208
+ }
12209
+ if (responseDlpEntries.length > 5) {
12210
+ console.log(chalk9.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
12211
+ }
12212
+ }
11831
12213
  console.log("");
11832
12214
  console.log(
11833
12215
  " " + chalk9.dim("node9 audit --deny") + chalk9.dim(" \xB7 ") + chalk9.dim("node9 report --period today|7d|30d|month --no-tests")
@@ -11945,12 +12327,12 @@ function registerDaemonCommand(program2) {
11945
12327
  init_core();
11946
12328
  init_daemon();
11947
12329
  import chalk11 from "chalk";
11948
- import fs28 from "fs";
11949
- import path30 from "path";
11950
- import os24 from "os";
12330
+ import fs29 from "fs";
12331
+ import path31 from "path";
12332
+ import os25 from "os";
11951
12333
  function readJson2(filePath) {
11952
12334
  try {
11953
- if (fs28.existsSync(filePath)) return JSON.parse(fs28.readFileSync(filePath, "utf-8"));
12335
+ if (fs29.existsSync(filePath)) return JSON.parse(fs29.readFileSync(filePath, "utf-8"));
11954
12336
  } catch {
11955
12337
  }
11956
12338
  return null;
@@ -12015,28 +12397,28 @@ function registerStatusCommand(program2) {
12015
12397
  console.log("");
12016
12398
  const modeLabel = settings.mode === "audit" ? chalk11.blue("audit") : settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
12017
12399
  console.log(` Mode: ${modeLabel}`);
12018
- const projectConfig = path30.join(process.cwd(), "node9.config.json");
12019
- const globalConfig = path30.join(os24.homedir(), ".node9", "config.json");
12400
+ const projectConfig = path31.join(process.cwd(), "node9.config.json");
12401
+ const globalConfig = path31.join(os25.homedir(), ".node9", "config.json");
12020
12402
  console.log(
12021
- ` Local: ${fs28.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
12403
+ ` Local: ${fs29.existsSync(projectConfig) ? chalk11.green("Active (node9.config.json)") : chalk11.gray("Not present")}`
12022
12404
  );
12023
12405
  console.log(
12024
- ` Global: ${fs28.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
12406
+ ` Global: ${fs29.existsSync(globalConfig) ? chalk11.green("Active (~/.node9/config.json)") : chalk11.gray("Not present")}`
12025
12407
  );
12026
12408
  if (mergedConfig.policy.sandboxPaths.length > 0) {
12027
12409
  console.log(
12028
12410
  ` Sandbox: ${chalk11.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
12029
12411
  );
12030
12412
  }
12031
- const homeDir2 = os24.homedir();
12413
+ const homeDir2 = os25.homedir();
12032
12414
  const claudeSettings = readJson2(
12033
- path30.join(homeDir2, ".claude", "settings.json")
12415
+ path31.join(homeDir2, ".claude", "settings.json")
12034
12416
  );
12035
- const claudeConfig = readJson2(path30.join(homeDir2, ".claude.json"));
12417
+ const claudeConfig = readJson2(path31.join(homeDir2, ".claude.json"));
12036
12418
  const geminiSettings = readJson2(
12037
- path30.join(homeDir2, ".gemini", "settings.json")
12419
+ path31.join(homeDir2, ".gemini", "settings.json")
12038
12420
  );
12039
- const cursorConfig = readJson2(path30.join(homeDir2, ".cursor", "mcp.json"));
12421
+ const cursorConfig = readJson2(path31.join(homeDir2, ".cursor", "mcp.json"));
12040
12422
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
12041
12423
  if (agentFound) {
12042
12424
  console.log("");
@@ -12096,9 +12478,9 @@ function registerStatusCommand(program2) {
12096
12478
  // src/cli/commands/init.ts
12097
12479
  init_core();
12098
12480
  import chalk12 from "chalk";
12099
- import fs29 from "fs";
12100
- import path31 from "path";
12101
- import os25 from "os";
12481
+ import fs30 from "fs";
12482
+ import path32 from "path";
12483
+ import os26 from "os";
12102
12484
  import https3 from "https";
12103
12485
  init_shields();
12104
12486
  init_service();
@@ -12158,15 +12540,15 @@ function registerInitCommand(program2) {
12158
12540
  }
12159
12541
  console.log("");
12160
12542
  }
12161
- const configPath = path31.join(os25.homedir(), ".node9", "config.json");
12162
- if (fs29.existsSync(configPath) && !options.force) {
12543
+ const configPath = path32.join(os26.homedir(), ".node9", "config.json");
12544
+ if (fs30.existsSync(configPath) && !options.force) {
12163
12545
  try {
12164
- const existing = JSON.parse(fs29.readFileSync(configPath, "utf-8"));
12546
+ const existing = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
12165
12547
  const settings = existing.settings ?? {};
12166
12548
  if (settings.mode !== chosenMode) {
12167
12549
  settings.mode = chosenMode;
12168
12550
  existing.settings = settings;
12169
- fs29.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
12551
+ fs30.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
12170
12552
  console.log(chalk12.green(`\u2705 Mode updated: ${chosenMode}`));
12171
12553
  } else {
12172
12554
  console.log(chalk12.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -12179,9 +12561,9 @@ function registerInitCommand(program2) {
12179
12561
  ...DEFAULT_CONFIG,
12180
12562
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
12181
12563
  };
12182
- const dir = path31.dirname(configPath);
12183
- if (!fs29.existsSync(dir)) fs29.mkdirSync(dir, { recursive: true });
12184
- fs29.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
12564
+ const dir = path32.dirname(configPath);
12565
+ if (!fs30.existsSync(dir)) fs30.mkdirSync(dir, { recursive: true });
12566
+ fs30.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
12185
12567
  console.log(chalk12.green(`\u2705 Config created: ${configPath}`));
12186
12568
  console.log(chalk12.gray(` Mode: ${chosenMode}`));
12187
12569
  }
@@ -12265,7 +12647,7 @@ function registerInitCommand(program2) {
12265
12647
  }
12266
12648
 
12267
12649
  // src/cli/commands/undo.ts
12268
- import path32 from "path";
12650
+ import path33 from "path";
12269
12651
  import chalk14 from "chalk";
12270
12652
 
12271
12653
  // src/tui/undo-navigator.ts
@@ -12424,7 +12806,7 @@ function findMatchingCwd(startDir, history) {
12424
12806
  let dir = startDir;
12425
12807
  while (true) {
12426
12808
  if (cwds.has(dir)) return dir;
12427
- const parent = path32.dirname(dir);
12809
+ const parent = path33.dirname(dir);
12428
12810
  if (parent === dir) return null;
12429
12811
  dir = parent;
12430
12812
  }
@@ -12620,12 +13002,12 @@ import { execa as execa2 } from "execa";
12620
13002
  init_provenance();
12621
13003
 
12622
13004
  // src/mcp-pin.ts
12623
- import fs30 from "fs";
12624
- import path33 from "path";
12625
- import os26 from "os";
13005
+ import fs31 from "fs";
13006
+ import path34 from "path";
13007
+ import os27 from "os";
12626
13008
  import crypto5 from "crypto";
12627
13009
  function getPinsFilePath2() {
12628
- return path33.join(os26.homedir(), ".node9", "mcp-pins.json");
13010
+ return path34.join(os27.homedir(), ".node9", "mcp-pins.json");
12629
13011
  }
12630
13012
  function hashToolDefinitions(tools) {
12631
13013
  const sorted = [...tools].sort((a, b) => {
@@ -12642,7 +13024,7 @@ function getServerKey(upstreamCommand) {
12642
13024
  function readMcpPinsSafe() {
12643
13025
  const filePath = getPinsFilePath2();
12644
13026
  try {
12645
- const raw = fs30.readFileSync(filePath, "utf-8");
13027
+ const raw = fs31.readFileSync(filePath, "utf-8");
12646
13028
  if (!raw.trim()) {
12647
13029
  return { ok: false, reason: "corrupt", detail: "empty file" };
12648
13030
  }
@@ -12666,10 +13048,10 @@ function readMcpPins() {
12666
13048
  }
12667
13049
  function writeMcpPins(data) {
12668
13050
  const filePath = getPinsFilePath2();
12669
- fs30.mkdirSync(path33.dirname(filePath), { recursive: true });
13051
+ fs31.mkdirSync(path34.dirname(filePath), { recursive: true });
12670
13052
  const tmp = `${filePath}.${crypto5.randomBytes(6).toString("hex")}.tmp`;
12671
- fs30.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12672
- fs30.renameSync(tmp, filePath);
13053
+ fs31.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
13054
+ fs31.renameSync(tmp, filePath);
12673
13055
  }
12674
13056
  function checkPin(serverKey, currentHash) {
12675
13057
  const result = readMcpPinsSafe();
@@ -13041,9 +13423,9 @@ function registerMcpGatewayCommand(program2) {
13041
13423
 
13042
13424
  // src/mcp-server/index.ts
13043
13425
  import readline4 from "readline";
13044
- import fs31 from "fs";
13045
- import os27 from "os";
13046
- import path34 from "path";
13426
+ import fs32 from "fs";
13427
+ import os28 from "os";
13428
+ import path35 from "path";
13047
13429
  init_core();
13048
13430
  init_daemon();
13049
13431
  init_shields();
@@ -13218,13 +13600,13 @@ function handleStatus() {
13218
13600
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
13219
13601
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
13220
13602
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
13221
- const projectConfig = path34.join(process.cwd(), "node9.config.json");
13222
- const globalConfig = path34.join(os27.homedir(), ".node9", "config.json");
13603
+ const projectConfig = path35.join(process.cwd(), "node9.config.json");
13604
+ const globalConfig = path35.join(os28.homedir(), ".node9", "config.json");
13223
13605
  lines.push(
13224
- `Project config (node9.config.json): ${fs31.existsSync(projectConfig) ? "present" : "not found"}`
13606
+ `Project config (node9.config.json): ${fs32.existsSync(projectConfig) ? "present" : "not found"}`
13225
13607
  );
13226
13608
  lines.push(
13227
- `Global config (~/.node9/config.json): ${fs31.existsSync(globalConfig) ? "present" : "not found"}`
13609
+ `Global config (~/.node9/config.json): ${fs32.existsSync(globalConfig) ? "present" : "not found"}`
13228
13610
  );
13229
13611
  return lines.join("\n");
13230
13612
  }
@@ -13298,21 +13680,21 @@ function handleShieldDisable(args) {
13298
13680
  writeActiveShields(active.filter((s) => s !== name));
13299
13681
  return `Shield "${name}" disabled.`;
13300
13682
  }
13301
- var GLOBAL_CONFIG_PATH2 = path34.join(os27.homedir(), ".node9", "config.json");
13683
+ var GLOBAL_CONFIG_PATH2 = path35.join(os28.homedir(), ".node9", "config.json");
13302
13684
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
13303
13685
  function readGlobalConfigRaw() {
13304
13686
  try {
13305
- if (fs31.existsSync(GLOBAL_CONFIG_PATH2)) {
13306
- return JSON.parse(fs31.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
13687
+ if (fs32.existsSync(GLOBAL_CONFIG_PATH2)) {
13688
+ return JSON.parse(fs32.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
13307
13689
  }
13308
13690
  } catch {
13309
13691
  }
13310
13692
  return {};
13311
13693
  }
13312
13694
  function writeGlobalConfigRaw(data) {
13313
- const dir = path34.dirname(GLOBAL_CONFIG_PATH2);
13314
- if (!fs31.existsSync(dir)) fs31.mkdirSync(dir, { recursive: true });
13315
- fs31.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
13695
+ const dir = path35.dirname(GLOBAL_CONFIG_PATH2);
13696
+ if (!fs32.existsSync(dir)) fs32.mkdirSync(dir, { recursive: true });
13697
+ fs32.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
13316
13698
  }
13317
13699
  function handleApproverList() {
13318
13700
  const config = getConfig();
@@ -13355,9 +13737,9 @@ function handleApproverSet(args) {
13355
13737
  }
13356
13738
  function handleAuditGet(args) {
13357
13739
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
13358
- const auditPath = path34.join(os27.homedir(), ".node9", "audit.log");
13359
- if (!fs31.existsSync(auditPath)) return "No audit log found.";
13360
- const lines = fs31.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13740
+ const auditPath = path35.join(os28.homedir(), ".node9", "audit.log");
13741
+ if (!fs32.existsSync(auditPath)) return "No audit log found.";
13742
+ const lines = fs32.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13361
13743
  const recent = lines.slice(-limit);
13362
13744
  const entries = recent.map((line) => {
13363
13745
  try {
@@ -13819,9 +14201,9 @@ init_config();
13819
14201
  init_policy();
13820
14202
  init_dlp();
13821
14203
  import chalk21 from "chalk";
13822
- import fs32 from "fs";
13823
- import path35 from "path";
13824
- import os28 from "os";
14204
+ import fs33 from "fs";
14205
+ import path36 from "path";
14206
+ import os29 from "os";
13825
14207
  var CLAUDE_PRICING2 = {
13826
14208
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13827
14209
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -13889,7 +14271,7 @@ function buildRuleSources() {
13889
14271
  return sources;
13890
14272
  }
13891
14273
  function scanClaudeHistory(startDate) {
13892
- const projectsDir = path35.join(os28.homedir(), ".claude", "projects");
14274
+ const projectsDir = path36.join(os29.homedir(), ".claude", "projects");
13893
14275
  const result = {
13894
14276
  filesScanned: 0,
13895
14277
  sessions: 0,
@@ -13901,25 +14283,25 @@ function scanClaudeHistory(startDate) {
13901
14283
  firstDate: null,
13902
14284
  lastDate: null
13903
14285
  };
13904
- if (!fs32.existsSync(projectsDir)) return result;
14286
+ if (!fs33.existsSync(projectsDir)) return result;
13905
14287
  let projDirs;
13906
14288
  try {
13907
- projDirs = fs32.readdirSync(projectsDir);
14289
+ projDirs = fs33.readdirSync(projectsDir);
13908
14290
  } catch {
13909
14291
  return result;
13910
14292
  }
13911
14293
  const ruleSources = buildRuleSources();
13912
14294
  for (const proj of projDirs) {
13913
- const projPath = path35.join(projectsDir, proj);
14295
+ const projPath = path36.join(projectsDir, proj);
13914
14296
  try {
13915
- if (!fs32.statSync(projPath).isDirectory()) continue;
14297
+ if (!fs33.statSync(projPath).isDirectory()) continue;
13916
14298
  } catch {
13917
14299
  continue;
13918
14300
  }
13919
- const projLabel = decodeURIComponent(proj).replace(os28.homedir(), "~").slice(0, 40);
14301
+ const projLabel = decodeURIComponent(proj).replace(os29.homedir(), "~").slice(0, 40);
13920
14302
  let files;
13921
14303
  try {
13922
- files = fs32.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
14304
+ files = fs33.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
13923
14305
  } catch {
13924
14306
  continue;
13925
14307
  }
@@ -13928,7 +14310,7 @@ function scanClaudeHistory(startDate) {
13928
14310
  result.sessions++;
13929
14311
  let raw;
13930
14312
  try {
13931
- raw = fs32.readFileSync(path35.join(projPath, file), "utf-8");
14313
+ raw = fs33.readFileSync(path36.join(projPath, file), "utf-8");
13932
14314
  } catch {
13933
14315
  continue;
13934
14316
  }
@@ -13969,6 +14351,9 @@ function scanClaudeHistory(startDate) {
13969
14351
  if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
13970
14352
  result.bashCalls++;
13971
14353
  }
14354
+ const rawCmd = String(input.command ?? "").trimStart();
14355
+ if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
14356
+ continue;
13972
14357
  const dlpMatch = scanArgs(input);
13973
14358
  if (dlpMatch) {
13974
14359
  const isDupe = result.dlpFindings.some(
@@ -13986,6 +14371,7 @@ function scanClaudeHistory(startDate) {
13986
14371
  }
13987
14372
  for (const source of ruleSources) {
13988
14373
  const { rule } = source;
14374
+ if (rule.verdict === "allow") continue;
13989
14375
  if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
13990
14376
  if (!evaluateSmartConditions(input, rule)) continue;
13991
14377
  const inputPreview = preview(input, 120);
@@ -14021,8 +14407,8 @@ function registerScanCommand(program2) {
14021
14407
  console.log("");
14022
14408
  console.log(chalk21.cyan.bold("\u{1F50D} node9 scan") + chalk21.dim(" \u2014 what would node9 catch?"));
14023
14409
  console.log("");
14024
- const projectsDir = path35.join(os28.homedir(), ".claude", "projects");
14025
- if (!fs32.existsSync(projectsDir)) {
14410
+ const projectsDir = path36.join(os29.homedir(), ".claude", "projects");
14411
+ if (!fs33.existsSync(projectsDir)) {
14026
14412
  console.log(chalk21.yellow(" No Claude history found at ~/.claude/projects/"));
14027
14413
  console.log(chalk21.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14028
14414
  return;
@@ -14134,8 +14520,8 @@ function registerScanCommand(program2) {
14134
14520
  );
14135
14521
  console.log("");
14136
14522
  }
14137
- const auditLog = path35.join(os28.homedir(), ".node9", "audit.log");
14138
- if (fs32.existsSync(auditLog)) {
14523
+ const auditLog = path36.join(os29.homedir(), ".node9", "audit.log");
14524
+ if (fs33.existsSync(auditLog)) {
14139
14525
  console.log(chalk21.green(" \u2705 node9 is active \u2014 future sessions are protected."));
14140
14526
  console.log(
14141
14527
  chalk21.dim(" Run ") + chalk21.cyan("node9 report") + chalk21.dim(" to see live stats.")
@@ -14152,9 +14538,9 @@ function registerScanCommand(program2) {
14152
14538
 
14153
14539
  // src/cli/commands/sessions.ts
14154
14540
  import chalk22 from "chalk";
14155
- import fs33 from "fs";
14156
- import path36 from "path";
14157
- import os29 from "os";
14541
+ import fs34 from "fs";
14542
+ import path37 from "path";
14543
+ import os30 from "os";
14158
14544
  var CLAUDE_PRICING3 = {
14159
14545
  "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
14160
14546
  "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
@@ -14179,10 +14565,10 @@ function encodeProjectPath(projectPath) {
14179
14565
  }
14180
14566
  function sessionJsonlPath(projectPath, sessionId) {
14181
14567
  const encoded = encodeProjectPath(projectPath);
14182
- return path36.join(os29.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
14568
+ return path37.join(os30.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
14183
14569
  }
14184
14570
  function projectLabel(projectPath) {
14185
- return projectPath.replace(os29.homedir(), "~");
14571
+ return projectPath.replace(os30.homedir(), "~");
14186
14572
  }
14187
14573
  function parseHistoryLines(lines) {
14188
14574
  const entries = [];
@@ -14251,10 +14637,10 @@ function parseSessionLines(lines) {
14251
14637
  return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
14252
14638
  }
14253
14639
  function loadAuditEntries(auditPath) {
14254
- const aPath = auditPath ?? path36.join(os29.homedir(), ".node9", "audit.log");
14640
+ const aPath = auditPath ?? path37.join(os30.homedir(), ".node9", "audit.log");
14255
14641
  let raw;
14256
14642
  try {
14257
- raw = fs33.readFileSync(aPath, "utf-8");
14643
+ raw = fs34.readFileSync(aPath, "utf-8");
14258
14644
  } catch {
14259
14645
  return [];
14260
14646
  }
@@ -14290,10 +14676,10 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
14290
14676
  return result;
14291
14677
  }
14292
14678
  function buildSessions(days, historyPath) {
14293
- const hPath = historyPath ?? path36.join(os29.homedir(), ".claude", "history.jsonl");
14679
+ const hPath = historyPath ?? path37.join(os30.homedir(), ".claude", "history.jsonl");
14294
14680
  let historyRaw;
14295
14681
  try {
14296
- historyRaw = fs33.readFileSync(hPath, "utf-8");
14682
+ historyRaw = fs34.readFileSync(hPath, "utf-8");
14297
14683
  } catch {
14298
14684
  return [];
14299
14685
  }
@@ -14318,7 +14704,7 @@ function buildSessions(days, historyPath) {
14318
14704
  const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
14319
14705
  let sessionLines = [];
14320
14706
  try {
14321
- sessionLines = fs33.readFileSync(jsonlFile, "utf-8").split("\n");
14707
+ sessionLines = fs34.readFileSync(jsonlFile, "utf-8").split("\n");
14322
14708
  } catch {
14323
14709
  }
14324
14710
  const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
@@ -14569,8 +14955,8 @@ function registerSessionsCommand(program2) {
14569
14955
  console.log("");
14570
14956
  console.log(chalk22.cyan.bold("\u{1F4CB} node9 sessions") + chalk22.dim(" \u2014 what your AI agent did"));
14571
14957
  console.log("");
14572
- const historyPath = path36.join(os29.homedir(), ".claude", "history.jsonl");
14573
- if (!fs33.existsSync(historyPath)) {
14958
+ const historyPath = path37.join(os30.homedir(), ".claude", "history.jsonl");
14959
+ if (!fs34.existsSync(historyPath)) {
14574
14960
  console.log(chalk22.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
14575
14961
  console.log(chalk22.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14576
14962
  return;
@@ -14602,12 +14988,12 @@ function registerSessionsCommand(program2) {
14602
14988
 
14603
14989
  // src/cli/commands/skill-pin.ts
14604
14990
  import chalk23 from "chalk";
14605
- import fs34 from "fs";
14606
- import os30 from "os";
14607
- import path37 from "path";
14991
+ import fs35 from "fs";
14992
+ import os31 from "os";
14993
+ import path38 from "path";
14608
14994
  function wipeSkillSessions() {
14609
14995
  try {
14610
- fs34.rmSync(path37.join(os30.homedir(), ".node9", "skill-sessions"), {
14996
+ fs35.rmSync(path38.join(os31.homedir(), ".node9", "skill-sessions"), {
14611
14997
  recursive: true,
14612
14998
  force: true
14613
14999
  });
@@ -14688,22 +15074,145 @@ function registerSkillPinCommand(program2) {
14688
15074
  });
14689
15075
  }
14690
15076
 
15077
+ // src/cli/commands/dlp.ts
15078
+ import chalk24 from "chalk";
15079
+ import fs36 from "fs";
15080
+ import path39 from "path";
15081
+ import os32 from "os";
15082
+ var AUDIT_LOG = path39.join(os32.homedir(), ".node9", "audit.log");
15083
+ var RESOLVED_FILE = path39.join(os32.homedir(), ".node9", "dlp-resolved.json");
15084
+ var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
15085
+ function stripAnsi(s) {
15086
+ return s.replace(ANSI_RE, "");
15087
+ }
15088
+ function loadResolved() {
15089
+ try {
15090
+ const raw = JSON.parse(fs36.readFileSync(RESOLVED_FILE, "utf-8"));
15091
+ return new Set(raw);
15092
+ } catch {
15093
+ return /* @__PURE__ */ new Set();
15094
+ }
15095
+ }
15096
+ function saveResolved(resolved) {
15097
+ try {
15098
+ fs36.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
15099
+ } catch {
15100
+ }
15101
+ }
15102
+ function loadDlpFindings() {
15103
+ if (!fs36.existsSync(AUDIT_LOG)) return [];
15104
+ return fs36.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
15105
+ if (!line.trim()) return [];
15106
+ try {
15107
+ const e = JSON.parse(line);
15108
+ return e.source === "response-dlp" ? [e] : [];
15109
+ } catch {
15110
+ return [];
15111
+ }
15112
+ });
15113
+ }
15114
+ function entryKey(e) {
15115
+ return `${e.ts}:${e.dlpPattern}:${e.dlpSample}`;
15116
+ }
15117
+ function fmtDate3(ts) {
15118
+ try {
15119
+ return new Date(ts).toLocaleDateString("en-US", {
15120
+ month: "short",
15121
+ day: "numeric",
15122
+ year: "numeric"
15123
+ });
15124
+ } catch {
15125
+ return ts.slice(0, 10);
15126
+ }
15127
+ }
15128
+ function registerDlpCommand(program2) {
15129
+ const cmd = program2.command("dlp").description("Show secrets detected in Claude response text and mark them resolved");
15130
+ cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
15131
+ const findings = loadDlpFindings();
15132
+ if (findings.length === 0) {
15133
+ console.log(chalk24.green("\n \u2705 No response-DLP findings to resolve.\n"));
15134
+ return;
15135
+ }
15136
+ const resolved = loadResolved();
15137
+ for (const e of findings) resolved.add(entryKey(e));
15138
+ saveResolved(resolved);
15139
+ console.log(
15140
+ chalk24.green(
15141
+ `
15142
+ \u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
15143
+ `
15144
+ )
15145
+ );
15146
+ });
15147
+ cmd.action(() => {
15148
+ const findings = loadDlpFindings();
15149
+ const resolved = loadResolved();
15150
+ const open = findings.filter((e) => !resolved.has(entryKey(e)));
15151
+ const resolvedCount = findings.length - open.length;
15152
+ console.log("");
15153
+ console.log(
15154
+ chalk24.bold.cyan("\u{1F510} node9 dlp") + chalk24.dim(" \u2014 secrets found in Claude response text")
15155
+ );
15156
+ console.log("");
15157
+ if (open.length === 0) {
15158
+ if (resolvedCount > 0) {
15159
+ console.log(chalk24.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
15160
+ } else {
15161
+ console.log(
15162
+ chalk24.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
15163
+ );
15164
+ }
15165
+ console.log("");
15166
+ return;
15167
+ }
15168
+ console.log(
15169
+ chalk24.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk24.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
15170
+ );
15171
+ console.log("");
15172
+ console.log(
15173
+ chalk24.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
15174
+ );
15175
+ console.log(chalk24.dim(" Rotate each affected key immediately.\n"));
15176
+ for (const e of open) {
15177
+ console.log(
15178
+ " " + chalk24.red("\u25CF") + " " + chalk24.white(e.dlpPattern ?? "Secret") + chalk24.dim(" " + fmtDate3(e.ts))
15179
+ );
15180
+ if (e.dlpSample) {
15181
+ console.log(" " + chalk24.dim("Sample: ") + chalk24.yellow(stripAnsi(e.dlpSample)));
15182
+ }
15183
+ if (e.project) {
15184
+ console.log(" " + chalk24.dim("Project: ") + chalk24.dim(stripAnsi(e.project)));
15185
+ }
15186
+ console.log("");
15187
+ }
15188
+ console.log(" " + chalk24.bold("Next steps:"));
15189
+ console.log(" " + chalk24.cyan("1.") + " Rotate any exposed keys shown above");
15190
+ console.log(
15191
+ " " + chalk24.cyan("2.") + " Run " + chalk24.white("node9 dlp resolve") + " to acknowledge"
15192
+ );
15193
+ console.log(
15194
+ " " + chalk24.cyan("3.") + " Run " + chalk24.white("node9 report") + " for full audit history"
15195
+ );
15196
+ console.log("");
15197
+ });
15198
+ }
15199
+
14691
15200
  // src/cli.ts
14692
15201
  var { version } = JSON.parse(
14693
- fs37.readFileSync(path40.join(__dirname, "../package.json"), "utf-8")
15202
+ fs39.readFileSync(path42.join(__dirname, "../package.json"), "utf-8")
14694
15203
  );
14695
15204
  var program = new Command();
14696
15205
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
14697
15206
  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) => {
14698
15207
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
14699
- const credPath = path40.join(os33.homedir(), ".node9", "credentials.json");
14700
- if (!fs37.existsSync(path40.dirname(credPath)))
14701
- fs37.mkdirSync(path40.dirname(credPath), { recursive: true });
15208
+ const credPath = path42.join(os35.homedir(), ".node9", "credentials.json");
15209
+ if (!fs39.existsSync(path42.dirname(credPath)))
15210
+ fs39.mkdirSync(path42.dirname(credPath), { recursive: true });
14702
15211
  const profileName = options.profile || "default";
14703
15212
  let existingCreds = {};
14704
15213
  try {
14705
- if (fs37.existsSync(credPath)) {
14706
- const raw = JSON.parse(fs37.readFileSync(credPath, "utf-8"));
15214
+ if (fs39.existsSync(credPath)) {
15215
+ const raw = JSON.parse(fs39.readFileSync(credPath, "utf-8"));
14707
15216
  if (raw.apiKey) {
14708
15217
  existingCreds = {
14709
15218
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -14715,13 +15224,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
14715
15224
  } catch {
14716
15225
  }
14717
15226
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
14718
- fs37.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
15227
+ fs39.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14719
15228
  if (profileName === "default") {
14720
- const configPath = path40.join(os33.homedir(), ".node9", "config.json");
15229
+ const configPath = path42.join(os35.homedir(), ".node9", "config.json");
14721
15230
  let config = {};
14722
15231
  try {
14723
- if (fs37.existsSync(configPath))
14724
- config = JSON.parse(fs37.readFileSync(configPath, "utf-8"));
15232
+ if (fs39.existsSync(configPath))
15233
+ config = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
14725
15234
  } catch {
14726
15235
  }
14727
15236
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -14736,47 +15245,61 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
14736
15245
  approvers.cloud = false;
14737
15246
  }
14738
15247
  s.approvers = approvers;
14739
- if (!fs37.existsSync(path40.dirname(configPath)))
14740
- fs37.mkdirSync(path40.dirname(configPath), { recursive: true });
14741
- fs37.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
15248
+ if (!fs39.existsSync(path42.dirname(configPath)))
15249
+ fs39.mkdirSync(path42.dirname(configPath), { recursive: true });
15250
+ fs39.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
14742
15251
  }
14743
15252
  if (options.profile && profileName !== "default") {
14744
- console.log(chalk25.green(`\u2705 Profile "${profileName}" saved`));
14745
- console.log(chalk25.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
15253
+ console.log(chalk26.green(`\u2705 Profile "${profileName}" saved`));
15254
+ console.log(chalk26.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
14746
15255
  } else if (options.local) {
14747
- console.log(chalk25.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
14748
- console.log(chalk25.gray(` All decisions stay on this machine.`));
15256
+ console.log(chalk26.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
15257
+ console.log(chalk26.gray(` All decisions stay on this machine.`));
14749
15258
  } else {
14750
- console.log(chalk25.green(`\u2705 Logged in \u2014 agent mode`));
14751
- console.log(chalk25.gray(` Team policy enforced for all calls via Node9 cloud.`));
15259
+ console.log(chalk26.green(`\u2705 Logged in \u2014 agent mode`));
15260
+ console.log(chalk26.gray(` Team policy enforced for all calls via Node9 cloud.`));
14752
15261
  }
14753
15262
  });
14754
- program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
15263
+ program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
15264
+ "after",
15265
+ "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
15266
+ ).argument(
15267
+ "<target>",
15268
+ "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
15269
+ ).action(async (target) => {
14755
15270
  if (target === "gemini") return await setupGemini();
14756
15271
  if (target === "claude") return await setupClaude();
14757
15272
  if (target === "cursor") return await setupCursor();
15273
+ if (target === "codex") return await setupCodex();
14758
15274
  if (target === "windsurf") return await setupWindsurf();
14759
15275
  if (target === "vscode") return await setupVSCode();
14760
15276
  if (target === "hud") return setupHud();
14761
15277
  console.error(
14762
- chalk25.red(
14763
- `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
15278
+ chalk26.red(
15279
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
14764
15280
  )
14765
15281
  );
14766
15282
  process.exit(1);
14767
15283
  });
14768
- program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
15284
+ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
15285
+ "after",
15286
+ "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
15287
+ ).argument(
15288
+ "[target]",
15289
+ "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
15290
+ ).action(async (target) => {
14769
15291
  if (!target) {
14770
- console.log(chalk25.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
14771
- console.log(" Usage: " + chalk25.white("node9 setup <target>") + "\n");
15292
+ console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
15293
+ console.log(" Usage: " + chalk26.white("node9 setup <target>") + "\n");
14772
15294
  console.log(" Targets:");
14773
- console.log(" " + chalk25.green("claude") + " \u2014 Claude Code (hook mode)");
14774
- console.log(" " + chalk25.green("gemini") + " \u2014 Gemini CLI (hook mode)");
14775
- console.log(" " + chalk25.green("cursor") + " \u2014 Cursor (MCP proxy)");
14776
- console.log(" " + chalk25.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
14777
- console.log(" " + chalk25.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
15295
+ console.log(" " + chalk26.green("claude") + " \u2014 Claude Code (hook mode)");
15296
+ console.log(" " + chalk26.green("gemini") + " \u2014 Gemini CLI (hook mode)");
15297
+ console.log(" " + chalk26.green("cursor") + " \u2014 Cursor (MCP proxy)");
15298
+ console.log(" " + chalk26.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
15299
+ console.log(" " + chalk26.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
15300
+ console.log(" " + chalk26.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
14778
15301
  process.stdout.write(
14779
- " " + chalk25.green("hud") + " \u2014 Claude Code security statusline\n"
15302
+ " " + chalk26.green("hud") + " \u2014 Claude Code security statusline\n"
14780
15303
  );
14781
15304
  console.log("");
14782
15305
  return;
@@ -14785,61 +15308,67 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
14785
15308
  if (t === "gemini") return await setupGemini();
14786
15309
  if (t === "claude") return await setupClaude();
14787
15310
  if (t === "cursor") return await setupCursor();
15311
+ if (t === "codex") return await setupCodex();
14788
15312
  if (t === "windsurf") return await setupWindsurf();
14789
15313
  if (t === "vscode") return await setupVSCode();
14790
15314
  if (t === "hud") return setupHud();
14791
15315
  console.error(
14792
- chalk25.red(
14793
- `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
15316
+ chalk26.red(
15317
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
14794
15318
  )
14795
15319
  );
14796
15320
  process.exit(1);
14797
15321
  });
14798
- program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument(
15322
+ program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
15323
+ "after",
15324
+ "\n Supported targets: claude gemini cursor codex windsurf vscode hud"
15325
+ ).argument(
14799
15326
  "<target>",
14800
- "The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
15327
+ "The agent to remove from: claude | gemini | cursor | codex | windsurf | vscode | hud"
14801
15328
  ).action((target) => {
14802
15329
  let fn;
14803
15330
  if (target === "claude") fn = teardownClaude;
14804
15331
  else if (target === "gemini") fn = teardownGemini;
14805
15332
  else if (target === "cursor") fn = teardownCursor;
15333
+ else if (target === "codex") fn = teardownCodex;
14806
15334
  else if (target === "windsurf") fn = teardownWindsurf;
14807
15335
  else if (target === "vscode") fn = teardownVSCode;
14808
15336
  else if (target === "hud") fn = teardownHud;
14809
15337
  else {
14810
15338
  console.error(
14811
- chalk25.red(
14812
- `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
15339
+ chalk26.red(
15340
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
14813
15341
  )
14814
15342
  );
14815
15343
  process.exit(1);
14816
15344
  }
14817
- console.log(chalk25.cyan(`
15345
+ console.log(chalk26.cyan(`
14818
15346
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
14819
15347
  `));
14820
15348
  try {
14821
15349
  fn();
14822
15350
  } catch (err2) {
14823
- console.error(chalk25.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
15351
+ console.error(chalk26.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
14824
15352
  process.exit(1);
14825
15353
  }
14826
- console.log(chalk25.gray("\n Restart the agent for changes to take effect."));
15354
+ console.log(chalk26.gray("\n Restart the agent for changes to take effect."));
14827
15355
  });
14828
15356
  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) => {
14829
- console.log(chalk25.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14830
- console.log(chalk25.bold("Stopping daemon..."));
15357
+ console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
15358
+ console.log(chalk26.bold("Stopping daemon..."));
14831
15359
  try {
14832
15360
  stopDaemon();
14833
- console.log(chalk25.green(" \u2705 Daemon stopped"));
15361
+ console.log(chalk26.green(" \u2705 Daemon stopped"));
14834
15362
  } catch {
14835
- console.log(chalk25.blue(" \u2139\uFE0F Daemon was not running"));
15363
+ console.log(chalk26.blue(" \u2139\uFE0F Daemon was not running"));
14836
15364
  }
14837
- console.log(chalk25.bold("\nRemoving hooks..."));
15365
+ console.log(chalk26.bold("\nRemoving hooks..."));
14838
15366
  let teardownFailed = false;
14839
15367
  for (const [label, fn] of [
14840
15368
  ["Claude", teardownClaude],
14841
15369
  ["Gemini", teardownGemini],
14842
15370
  ["Cursor", teardownCursor],
15371
+ ["Codex", teardownCodex],
14843
15372
  ["Windsurf", teardownWindsurf],
14844
15373
  ["VSCode", teardownVSCode]
14845
15374
  ]) {
@@ -14848,45 +15377,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
14848
15377
  } catch (err2) {
14849
15378
  teardownFailed = true;
14850
15379
  console.error(
14851
- chalk25.red(
15380
+ chalk26.red(
14852
15381
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
14853
15382
  )
14854
15383
  );
14855
15384
  }
14856
15385
  }
14857
15386
  if (options.purge) {
14858
- const node9Dir = path40.join(os33.homedir(), ".node9");
14859
- if (fs37.existsSync(node9Dir)) {
15387
+ const node9Dir = path42.join(os35.homedir(), ".node9");
15388
+ if (fs39.existsSync(node9Dir)) {
14860
15389
  const confirmed = await confirm2({
14861
15390
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
14862
15391
  default: false
14863
15392
  });
14864
15393
  if (confirmed) {
14865
- fs37.rmSync(node9Dir, { recursive: true });
14866
- if (fs37.existsSync(node9Dir)) {
15394
+ fs39.rmSync(node9Dir, { recursive: true });
15395
+ if (fs39.existsSync(node9Dir)) {
14867
15396
  console.error(
14868
- chalk25.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
15397
+ chalk26.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
14869
15398
  );
14870
15399
  } else {
14871
- console.log(chalk25.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
15400
+ console.log(chalk26.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14872
15401
  }
14873
15402
  } else {
14874
- console.log(chalk25.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
15403
+ console.log(chalk26.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14875
15404
  }
14876
15405
  } else {
14877
- console.log(chalk25.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
15406
+ console.log(chalk26.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
14878
15407
  }
14879
15408
  } else {
14880
15409
  console.log(
14881
- chalk25.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
15410
+ chalk26.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
14882
15411
  );
14883
15412
  }
14884
15413
  if (teardownFailed) {
14885
- console.error(chalk25.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
15414
+ console.error(chalk26.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
14886
15415
  process.exit(1);
14887
15416
  }
14888
- console.log(chalk25.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
14889
- console.log(chalk25.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
15417
+ console.log(chalk26.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
15418
+ console.log(chalk26.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
14890
15419
  });
14891
15420
  registerDoctorCommand(program, version);
14892
15421
  program.command("explain").description(
@@ -14899,7 +15428,7 @@ program.command("explain").description(
14899
15428
  try {
14900
15429
  args = JSON.parse(trimmed);
14901
15430
  } catch {
14902
- console.error(chalk25.red(`
15431
+ console.error(chalk26.red(`
14903
15432
  \u274C Invalid JSON: ${trimmed}
14904
15433
  `));
14905
15434
  process.exit(1);
@@ -14910,54 +15439,54 @@ program.command("explain").description(
14910
15439
  }
14911
15440
  const result = await explainPolicy(tool, args);
14912
15441
  console.log("");
14913
- console.log(chalk25.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
15442
+ console.log(chalk26.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14914
15443
  console.log("");
14915
- console.log(` ${chalk25.bold("Tool:")} ${chalk25.white(result.tool)}`);
15444
+ console.log(` ${chalk26.bold("Tool:")} ${chalk26.white(result.tool)}`);
14916
15445
  if (argsRaw) {
14917
15446
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
14918
- console.log(` ${chalk25.bold("Input:")} ${chalk25.gray(preview2)}`);
15447
+ console.log(` ${chalk26.bold("Input:")} ${chalk26.gray(preview2)}`);
14919
15448
  }
14920
15449
  console.log("");
14921
- console.log(chalk25.bold("Config Sources (Waterfall):"));
15450
+ console.log(chalk26.bold("Config Sources (Waterfall):"));
14922
15451
  for (const tier of result.waterfall) {
14923
- const num3 = chalk25.gray(` ${tier.tier}.`);
15452
+ const num3 = chalk26.gray(` ${tier.tier}.`);
14924
15453
  const label = tier.label.padEnd(16);
14925
15454
  let statusStr;
14926
15455
  if (tier.tier === 1) {
14927
- statusStr = chalk25.gray(tier.note ?? "");
15456
+ statusStr = chalk26.gray(tier.note ?? "");
14928
15457
  } else if (tier.status === "active") {
14929
- const loc = tier.path ? chalk25.gray(tier.path) : "";
14930
- const note = tier.note ? chalk25.gray(`(${tier.note})`) : "";
14931
- statusStr = chalk25.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
15458
+ const loc = tier.path ? chalk26.gray(tier.path) : "";
15459
+ const note = tier.note ? chalk26.gray(`(${tier.note})`) : "";
15460
+ statusStr = chalk26.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
14932
15461
  } else {
14933
- statusStr = chalk25.gray("\u25CB " + (tier.note ?? "not found"));
15462
+ statusStr = chalk26.gray("\u25CB " + (tier.note ?? "not found"));
14934
15463
  }
14935
- console.log(`${num3} ${chalk25.white(label)} ${statusStr}`);
15464
+ console.log(`${num3} ${chalk26.white(label)} ${statusStr}`);
14936
15465
  }
14937
15466
  console.log("");
14938
- console.log(chalk25.bold("Policy Evaluation:"));
15467
+ console.log(chalk26.bold("Policy Evaluation:"));
14939
15468
  for (const step of result.steps) {
14940
15469
  const isFinal = step.isFinal;
14941
15470
  let icon;
14942
- if (step.outcome === "allow") icon = chalk25.green(" \u2705");
14943
- else if (step.outcome === "review") icon = chalk25.red(" \u{1F534}");
14944
- else if (step.outcome === "skip") icon = chalk25.gray(" \u2500 ");
14945
- else icon = chalk25.gray(" \u25CB ");
15471
+ if (step.outcome === "allow") icon = chalk26.green(" \u2705");
15472
+ else if (step.outcome === "review") icon = chalk26.red(" \u{1F534}");
15473
+ else if (step.outcome === "skip") icon = chalk26.gray(" \u2500 ");
15474
+ else icon = chalk26.gray(" \u25CB ");
14946
15475
  const name = step.name.padEnd(18);
14947
- const nameStr = isFinal ? chalk25.white.bold(name) : chalk25.white(name);
14948
- const detail = isFinal ? chalk25.white(step.detail) : chalk25.gray(step.detail);
14949
- const arrow = isFinal ? chalk25.yellow(" \u2190 STOP") : "";
15476
+ const nameStr = isFinal ? chalk26.white.bold(name) : chalk26.white(name);
15477
+ const detail = isFinal ? chalk26.white(step.detail) : chalk26.gray(step.detail);
15478
+ const arrow = isFinal ? chalk26.yellow(" \u2190 STOP") : "";
14950
15479
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
14951
15480
  }
14952
15481
  console.log("");
14953
15482
  if (result.decision === "allow") {
14954
- console.log(chalk25.green.bold(" Decision: \u2705 ALLOW") + chalk25.gray(" \u2014 no approval needed"));
15483
+ console.log(chalk26.green.bold(" Decision: \u2705 ALLOW") + chalk26.gray(" \u2014 no approval needed"));
14955
15484
  } else {
14956
15485
  console.log(
14957
- chalk25.red.bold(" Decision: \u{1F534} REVIEW") + chalk25.gray(" \u2014 human approval required")
15486
+ chalk26.red.bold(" Decision: \u{1F534} REVIEW") + chalk26.gray(" \u2014 human approval required")
14958
15487
  );
14959
15488
  if (result.blockedByLabel) {
14960
- console.log(chalk25.gray(` Reason: ${result.blockedByLabel}`));
15489
+ console.log(chalk26.gray(` Reason: ${result.blockedByLabel}`));
14961
15490
  }
14962
15491
  }
14963
15492
  console.log("");
@@ -14972,7 +15501,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
14972
15501
  try {
14973
15502
  await startTail2(options);
14974
15503
  } catch (err2) {
14975
- console.error(chalk25.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
15504
+ console.error(chalk26.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
14976
15505
  process.exit(1);
14977
15506
  }
14978
15507
  });
@@ -15005,14 +15534,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
15005
15534
  Run "node9 addto claude" to register it as the statusLine.`
15006
15535
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
15007
15536
  if (subcommand === "debug") {
15008
- const flagFile = path40.join(os33.homedir(), ".node9", "hud-debug");
15537
+ const flagFile = path42.join(os35.homedir(), ".node9", "hud-debug");
15009
15538
  if (state === "on") {
15010
- fs37.mkdirSync(path40.dirname(flagFile), { recursive: true });
15011
- fs37.writeFileSync(flagFile, "");
15539
+ fs39.mkdirSync(path42.dirname(flagFile), { recursive: true });
15540
+ fs39.writeFileSync(flagFile, "");
15012
15541
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
15013
15542
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
15014
15543
  } else if (state === "off") {
15015
- if (fs37.existsSync(flagFile)) fs37.unlinkSync(flagFile);
15544
+ if (fs39.existsSync(flagFile)) fs39.unlinkSync(flagFile);
15016
15545
  console.log("HUD debug logging disabled.");
15017
15546
  } else {
15018
15547
  console.error("Usage: node9 hud debug on|off");
@@ -15027,7 +15556,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
15027
15556
  const ms = parseDuration(options.duration);
15028
15557
  if (ms === null) {
15029
15558
  console.error(
15030
- chalk25.red(`
15559
+ chalk26.red(`
15031
15560
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
15032
15561
  `)
15033
15562
  );
@@ -15035,20 +15564,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
15035
15564
  }
15036
15565
  pauseNode9(ms, options.duration);
15037
15566
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
15038
- console.log(chalk25.yellow(`
15567
+ console.log(chalk26.yellow(`
15039
15568
  \u23F8 Node9 paused until ${expiresAt}`));
15040
- console.log(chalk25.gray(` All tool calls will be allowed without review.`));
15041
- console.log(chalk25.gray(` Run "node9 resume" to re-enable early.
15569
+ console.log(chalk26.gray(` All tool calls will be allowed without review.`));
15570
+ console.log(chalk26.gray(` Run "node9 resume" to re-enable early.
15042
15571
  `));
15043
15572
  });
15044
15573
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
15045
15574
  const { paused } = checkPause();
15046
15575
  if (!paused) {
15047
- console.log(chalk25.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
15576
+ console.log(chalk26.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
15048
15577
  return;
15049
15578
  }
15050
15579
  resumeNode9();
15051
- console.log(chalk25.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
15580
+ console.log(chalk26.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
15052
15581
  });
15053
15582
  var HOOK_BASED_AGENTS = {
15054
15583
  claude: "claude",
@@ -15061,15 +15590,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
15061
15590
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
15062
15591
  const target = HOOK_BASED_AGENTS[firstArg2];
15063
15592
  console.error(
15064
- chalk25.yellow(`
15593
+ chalk26.yellow(`
15065
15594
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
15066
15595
  );
15067
- console.error(chalk25.white(`
15596
+ console.error(chalk26.white(`
15068
15597
  "${target}" uses its own hook system. Use:`));
15069
15598
  console.error(
15070
- chalk25.green(` node9 addto ${target} `) + chalk25.gray("# one-time setup")
15599
+ chalk26.green(` node9 addto ${target} `) + chalk26.gray("# one-time setup")
15071
15600
  );
15072
- console.error(chalk25.green(` ${target} `) + chalk25.gray("# run normally"));
15601
+ console.error(chalk26.green(` ${target} `) + chalk26.gray("# run normally"));
15073
15602
  process.exit(1);
15074
15603
  }
15075
15604
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -15086,7 +15615,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
15086
15615
  }
15087
15616
  );
15088
15617
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
15089
- console.error(chalk25.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
15618
+ console.error(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
15090
15619
  const daemonReady = await autoStartDaemonAndWait();
15091
15620
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
15092
15621
  }
@@ -15099,12 +15628,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
15099
15628
  }
15100
15629
  if (!result.approved) {
15101
15630
  console.error(
15102
- chalk25.red(`
15631
+ chalk26.red(`
15103
15632
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
15104
15633
  );
15105
15634
  process.exit(1);
15106
15635
  }
15107
- console.error(chalk25.green("\n\u2705 Approved \u2014 running command...\n"));
15636
+ console.error(chalk26.green("\n\u2705 Approved \u2014 running command...\n"));
15108
15637
  await runProxy(fullCommand);
15109
15638
  } else {
15110
15639
  program.help();
@@ -15118,14 +15647,15 @@ registerSyncCommand(program);
15118
15647
  registerAgentsCommand(program);
15119
15648
  registerScanCommand(program);
15120
15649
  registerSessionsCommand(program);
15650
+ registerDlpCommand(program);
15121
15651
  if (process.argv[2] !== "daemon") {
15122
15652
  process.on("unhandledRejection", (reason) => {
15123
15653
  const isCheckHook = process.argv[2] === "check";
15124
15654
  if (isCheckHook) {
15125
15655
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
15126
- const logPath = path40.join(os33.homedir(), ".node9", "hook-debug.log");
15656
+ const logPath = path42.join(os35.homedir(), ".node9", "hook-debug.log");
15127
15657
  const msg = reason instanceof Error ? reason.message : String(reason);
15128
- fs37.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
15658
+ fs39.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
15129
15659
  `);
15130
15660
  }
15131
15661
  process.exit(0);