@node9/proxy 1.12.11 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
168
168
  }
169
169
  }
170
170
  const lines = result.error.issues.map((issue) => {
171
- const path45 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path45}: ${issue.message}`;
171
+ const path46 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path46}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -1220,7 +1220,26 @@ function scanText(text) {
1220
1220
  }
1221
1221
  return null;
1222
1222
  }
1223
- var import_fs4, import_path4, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
1223
+ function redactText(text) {
1224
+ const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
1225
+ let result = t;
1226
+ const found = [];
1227
+ const lower = t.toLowerCase();
1228
+ for (const { pattern, globalRegex } of DLP_PATTERNS_GLOBAL) {
1229
+ if (pattern.keywords && !pattern.keywords.some((kw) => lower.includes(kw.toLowerCase()))) {
1230
+ continue;
1231
+ }
1232
+ result = result.replace(globalRegex, (match) => {
1233
+ if (DLP_STOPWORDS.some((sw) => match.toLowerCase().includes(sw))) return match;
1234
+ if (pattern.minEntropy !== void 0 && shannonEntropy(match) < pattern.minEntropy)
1235
+ return match;
1236
+ if (!found.includes(pattern.name)) found.push(pattern.name);
1237
+ return `[node9-redacted:${pattern.name}]`;
1238
+ });
1239
+ }
1240
+ return { result, found };
1241
+ }
1242
+ var import_fs4, import_path4, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
1224
1243
  var init_dlp = __esm({
1225
1244
  "src/dlp.ts"() {
1226
1245
  "use strict";
@@ -1266,7 +1285,8 @@ var init_dlp = __esm({
1266
1285
  name: "GitHub Token",
1267
1286
  regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/,
1268
1287
  severity: "block",
1269
- keywords: ["ghp_", "gho_", "ghu_", "ghs_"]
1288
+ keywords: ["ghp_", "gho_", "ghu_", "ghs_"],
1289
+ minEntropy: 3
1270
1290
  },
1271
1291
  {
1272
1292
  name: "GitHub Fine-Grained PAT",
@@ -1317,7 +1337,8 @@ var init_dlp = __esm({
1317
1337
  name: "GCP API Key",
1318
1338
  regex: /\bAIza[0-9A-Za-z_-]{35}\b/,
1319
1339
  severity: "block",
1320
- keywords: ["aiza"]
1340
+ keywords: ["aiza"],
1341
+ minEntropy: 3
1321
1342
  },
1322
1343
  {
1323
1344
  name: "GCP Service Account",
@@ -1564,7 +1585,8 @@ var init_dlp = __esm({
1564
1585
  name: "Mapbox Access Token",
1565
1586
  regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
1566
1587
  severity: "block",
1567
- keywords: ["pk.eyj1"]
1588
+ keywords: ["pk.eyj1"],
1589
+ minEntropy: 3
1568
1590
  },
1569
1591
  // ── Notion ────────────────────────────────────────────────────────────────
1570
1592
  {
@@ -1637,6 +1659,15 @@ var init_dlp = __esm({
1637
1659
  keywords: ["age-secret-key-"]
1638
1660
  }
1639
1661
  ];
1662
+ DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
1663
+ (p) => ({
1664
+ pattern: p,
1665
+ globalRegex: new RegExp(
1666
+ p.regex.source,
1667
+ p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g"
1668
+ )
1669
+ })
1670
+ );
1640
1671
  SENSITIVE_PATH_PATTERNS = [
1641
1672
  /[/\\]\.ssh[/\\]/i,
1642
1673
  /[/\\]\.aws[/\\]/i,
@@ -2198,9 +2229,9 @@ function matchesPattern(text, patterns) {
2198
2229
  const withoutDotSlash = text.replace(/^\.\//, "");
2199
2230
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
2200
2231
  }
2201
- function getNestedValue(obj, path45) {
2232
+ function getNestedValue(obj, path46) {
2202
2233
  if (!obj || typeof obj !== "object") return null;
2203
- return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
2234
+ return path46.split(".").reduce((prev, curr) => prev?.[curr], obj);
2204
2235
  }
2205
2236
  function normalizeCommandForPolicy(command) {
2206
2237
  try {
@@ -9173,14 +9204,30 @@ var init_ui = __esm({
9173
9204
  // \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
9174
9205
  if (leaksByPattern.length) {
9175
9206
  html +=
9176
- '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in tool calls</div>';
9207
+ '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in history or shell config</div>';
9177
9208
  html += leaksByPattern
9178
9209
  .map(([pattern, group]) => {
9179
9210
  const count = group.length;
9180
9211
  const barPct = Math.round((count / maxBar) * 100);
9181
9212
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
9182
9213
  const rows = group
9183
- .map((l) => findingRow(l.timestamp, esc(l.redactedSample || '')))
9214
+ .map((l) => {
9215
+ const agentBadge =
9216
+ l.agent === 'gemini'
9217
+ ? '[Gemini]'
9218
+ : l.agent === 'codex'
9219
+ ? '[Codex]'
9220
+ : l.agent === 'shell'
9221
+ ? '[Shell]'
9222
+ : '[Claude]';
9223
+ return findingRow(
9224
+ l.timestamp,
9225
+ '<span style="opacity:0.6;margin-right:6px">' +
9226
+ esc(agentBadge) +
9227
+ '</span>' +
9228
+ esc(l.redactedSample || '')
9229
+ );
9230
+ })
9184
9231
  .join('');
9185
9232
  return (
9186
9233
  '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
@@ -9929,6 +9976,7 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9929
9976
  continue;
9930
9977
  }
9931
9978
  const sessionCalls = [];
9979
+ const toolUseFilePaths = /* @__PURE__ */ new Map();
9932
9980
  for (const line of raw.split("\n")) {
9933
9981
  if (!line.trim()) continue;
9934
9982
  onLine?.();
@@ -9971,6 +10019,33 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9971
10019
  }
9972
10020
  }
9973
10021
  }
10022
+ for (const block of content2) {
10023
+ if (block.type !== "tool_result") continue;
10024
+ const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
10025
+ if (filePath) {
10026
+ const ext = import_path17.default.extname(filePath).toLowerCase();
10027
+ if (CODE_EXTENSIONS.has(ext)) continue;
10028
+ }
10029
+ const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
10030
+ if (!resultText) continue;
10031
+ const dlpMatch = scanArgs({ text: resultText });
10032
+ if (dlpMatch) {
10033
+ const isDupe = result.dlpFindings.some(
10034
+ (f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
10035
+ );
10036
+ if (!isDupe) {
10037
+ result.dlpFindings.push({
10038
+ patternName: dlpMatch.patternName,
10039
+ redactedSample: dlpMatch.redactedSample,
10040
+ toolName: "tool-result",
10041
+ timestamp: entry.timestamp ?? "",
10042
+ project: projLabel,
10043
+ sessionId,
10044
+ agent: "claude"
10045
+ });
10046
+ }
10047
+ }
10048
+ }
9974
10049
  }
9975
10050
  continue;
9976
10051
  }
@@ -9990,6 +10065,9 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9990
10065
  const toolName = block.name ?? "";
9991
10066
  const toolNameLower = toolName.toLowerCase();
9992
10067
  const input = block.input ?? {};
10068
+ if (block.id && typeof input.file_path === "string") {
10069
+ toolUseFilePaths.set(block.id, input.file_path);
10070
+ }
9993
10071
  sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
9994
10072
  if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
9995
10073
  result.bashCalls++;
@@ -9997,6 +10075,9 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9997
10075
  const rawCmd = String(input.command ?? "").trimStart();
9998
10076
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
9999
10077
  continue;
10078
+ const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
10079
+ const inputFileExt = inputFilePath ? import_path17.default.extname(inputFilePath).toLowerCase() : "";
10080
+ if (CODE_EXTENSIONS.has(inputFileExt)) continue;
10000
10081
  const dlpMatch = scanArgs(input);
10001
10082
  if (dlpMatch) {
10002
10083
  const isDupe = result.dlpFindings.some(
@@ -10493,6 +10574,44 @@ function scanCodexHistory(startDate, onProgress, onLine) {
10493
10574
  }
10494
10575
  return result;
10495
10576
  }
10577
+ function scanShellConfig() {
10578
+ const home = import_os12.default.homedir();
10579
+ const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
10580
+ (f) => import_path17.default.join(home, f)
10581
+ );
10582
+ const findings = [];
10583
+ for (const filePath of configFiles) {
10584
+ if (!import_fs14.default.existsSync(filePath)) continue;
10585
+ let lines;
10586
+ try {
10587
+ lines = import_fs14.default.readFileSync(filePath, "utf-8").split("\n");
10588
+ } catch {
10589
+ continue;
10590
+ }
10591
+ const shortPath = filePath.replace(home, "~");
10592
+ for (const line of lines) {
10593
+ const trimmed = line.trim();
10594
+ if (!trimmed || trimmed.startsWith("#")) continue;
10595
+ const dlpMatch = scanArgs({ text: trimmed });
10596
+ if (!dlpMatch) continue;
10597
+ const isDupe = findings.some(
10598
+ (f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === shortPath
10599
+ );
10600
+ if (!isDupe) {
10601
+ findings.push({
10602
+ patternName: dlpMatch.patternName,
10603
+ redactedSample: dlpMatch.redactedSample,
10604
+ toolName: "shell-config",
10605
+ timestamp: "",
10606
+ project: shortPath,
10607
+ sessionId: "",
10608
+ agent: "shell"
10609
+ });
10610
+ }
10611
+ }
10612
+ }
10613
+ return findings;
10614
+ }
10496
10615
  function mergeScans(a, b) {
10497
10616
  const dates = [a.firstDate, b.firstDate].filter(Boolean);
10498
10617
  const lastDates = [a.lastDate, b.lastDate].filter(Boolean);
@@ -10607,6 +10726,7 @@ function registerScanCommand(program2) {
10607
10726
  onLine
10608
10727
  );
10609
10728
  const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
10729
+ scan.dlpFindings.push(...scanShellConfig());
10610
10730
  const summary = buildScanSummary([
10611
10731
  { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
10612
10732
  { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
@@ -10660,7 +10780,7 @@ function registerScanCommand(program2) {
10660
10780
  }
10661
10781
  if (scan.dlpFindings.length > 0) {
10662
10782
  console.log(
10663
- " " + import_chalk2.default.red("\u{1F511} Credential leak") + " " + import_chalk2.default.red.bold(String(scan.dlpFindings.length).padStart(5)) + import_chalk2.default.dim(" secret detected in tool call")
10783
+ " " + import_chalk2.default.red("\u{1F511} Credential leak") + " " + import_chalk2.default.red.bold(String(scan.dlpFindings.length).padStart(5)) + import_chalk2.default.dim(" secret detected in history or shell config")
10664
10784
  );
10665
10785
  }
10666
10786
  if (blockedCount > 0) {
@@ -10669,8 +10789,9 @@ function registerScanCommand(program2) {
10669
10789
  );
10670
10790
  }
10671
10791
  if (scan.loopFindings.length > 0) {
10792
+ const loopCost = summary.loopWastedUSD > 0 ? import_chalk2.default.dim(" \xB7 ") + import_chalk2.default.yellow("~" + fmtCost(summary.loopWastedUSD) + " wasted") : "";
10672
10793
  console.log(
10673
- " " + import_chalk2.default.yellow("\u{1F501} Loop detected") + " " + import_chalk2.default.yellow.bold(String(scan.loopFindings.length).padStart(5)) + import_chalk2.default.dim(" repeated tool call patterns found")
10794
+ " " + import_chalk2.default.yellow("\u{1F501} Loop detected") + " " + import_chalk2.default.yellow.bold(String(scan.loopFindings.length).padStart(5)) + import_chalk2.default.dim(" repeated tool call patterns found") + loopCost
10674
10795
  );
10675
10796
  }
10676
10797
  if (reviewCount > 0) {
@@ -10690,7 +10811,7 @@ function registerScanCommand(program2) {
10690
10811
  for (const f of shownDlp) {
10691
10812
  const ts = f.timestamp ? import_chalk2.default.dim(fmtTs(f.timestamp) + " ") : "";
10692
10813
  const proj = import_chalk2.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
10693
- const agentBadge = f.agent === "gemini" ? import_chalk2.default.blue("[Gemini] ") : f.agent === "codex" ? import_chalk2.default.magenta("[Codex] ") : import_chalk2.default.cyan("[Claude] ");
10814
+ const agentBadge = f.agent === "gemini" ? import_chalk2.default.blue("[Gemini] ") : f.agent === "codex" ? import_chalk2.default.magenta("[Codex] ") : f.agent === "shell" ? import_chalk2.default.yellow("[Shell] ") : import_chalk2.default.cyan("[Claude] ");
10694
10815
  const sessionSuffix = f.sessionId ? import_chalk2.default.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
10695
10816
  console.log(
10696
10817
  ` \u{1F6A8} ${ts}${proj}${agentBadge}` + import_chalk2.default.yellow(f.patternName) + import_chalk2.default.dim(" ") + import_chalk2.default.gray(f.redactedSample) + sessionSuffix
@@ -10722,10 +10843,11 @@ function registerScanCommand(program2) {
10722
10843
  }
10723
10844
  if (scan.loopFindings.length > 0) {
10724
10845
  console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
10846
+ const loopCostLabel = summary.loopWastedUSD > 0 ? import_chalk2.default.dim(" \xB7 ") + import_chalk2.default.yellow("~" + fmtCost(summary.loopWastedUSD) + " wasted") : "";
10725
10847
  console.log(
10726
10848
  " " + import_chalk2.default.yellow.bold("\u{1F501} Agent Loops") + import_chalk2.default.dim(" \xB7 ") + import_chalk2.default.yellow(
10727
10849
  `${num(scan.loopFindings.length)} repeated pattern${scan.loopFindings.length !== 1 ? "s" : ""} found`
10728
- )
10850
+ ) + loopCostLabel
10729
10851
  );
10730
10852
  const shownLoops = drillDown ? scan.loopFindings : scan.loopFindings.slice(0, topN);
10731
10853
  for (const f of shownLoops) {
@@ -10848,7 +10970,7 @@ function registerScanCommand(program2) {
10848
10970
  }
10849
10971
  });
10850
10972
  }
10851
- var import_chalk2, import_fs14, import_path17, import_os12, CLAUDE_PRICING, GEMINI_PRICING, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
10973
+ var import_chalk2, import_fs14, import_path17, import_os12, CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
10852
10974
  var init_scan = __esm({
10853
10975
  "src/cli/commands/scan.ts"() {
10854
10976
  "use strict";
@@ -10884,6 +11006,33 @@ var init_scan = __esm({
10884
11006
  "gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
10885
11007
  "gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
10886
11008
  };
11009
+ CODE_EXTENSIONS = /* @__PURE__ */ new Set([
11010
+ ".ts",
11011
+ ".tsx",
11012
+ ".js",
11013
+ ".jsx",
11014
+ ".mjs",
11015
+ ".cjs",
11016
+ ".py",
11017
+ ".rb",
11018
+ ".go",
11019
+ ".rs",
11020
+ ".java",
11021
+ ".kt",
11022
+ ".swift",
11023
+ ".c",
11024
+ ".cpp",
11025
+ ".h",
11026
+ ".cs",
11027
+ ".php",
11028
+ ".sh",
11029
+ ".bash",
11030
+ ".html",
11031
+ ".css",
11032
+ ".scss",
11033
+ ".vue",
11034
+ ".svelte"
11035
+ ]);
10887
11036
  LOOP_TOOLS = /* @__PURE__ */ new Set([
10888
11037
  "bash",
10889
11038
  "execute_bash",
@@ -13590,20 +13739,20 @@ function getModelContextLimit(model) {
13590
13739
  return 2e5;
13591
13740
  }
13592
13741
  function readSessionUsage() {
13593
- const projectsDir = import_path42.default.join(import_os35.default.homedir(), ".claude", "projects");
13594
- if (!import_fs39.default.existsSync(projectsDir)) return null;
13742
+ const projectsDir = import_path43.default.join(import_os36.default.homedir(), ".claude", "projects");
13743
+ if (!import_fs40.default.existsSync(projectsDir)) return null;
13595
13744
  let latestFile = null;
13596
13745
  let latestMtime = 0;
13597
13746
  try {
13598
- for (const dir of import_fs39.default.readdirSync(projectsDir)) {
13599
- const dirPath = import_path42.default.join(projectsDir, dir);
13747
+ for (const dir of import_fs40.default.readdirSync(projectsDir)) {
13748
+ const dirPath = import_path43.default.join(projectsDir, dir);
13600
13749
  try {
13601
- if (!import_fs39.default.statSync(dirPath).isDirectory()) continue;
13602
- for (const file of import_fs39.default.readdirSync(dirPath)) {
13750
+ if (!import_fs40.default.statSync(dirPath).isDirectory()) continue;
13751
+ for (const file of import_fs40.default.readdirSync(dirPath)) {
13603
13752
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
13604
- const filePath = import_path42.default.join(dirPath, file);
13753
+ const filePath = import_path43.default.join(dirPath, file);
13605
13754
  try {
13606
- const mtime = import_fs39.default.statSync(filePath).mtimeMs;
13755
+ const mtime = import_fs40.default.statSync(filePath).mtimeMs;
13607
13756
  if (mtime > latestMtime) {
13608
13757
  latestMtime = mtime;
13609
13758
  latestFile = filePath;
@@ -13618,7 +13767,7 @@ function readSessionUsage() {
13618
13767
  }
13619
13768
  if (!latestFile) return null;
13620
13769
  try {
13621
- const lines = import_fs39.default.readFileSync(latestFile, "utf-8").split("\n");
13770
+ const lines = import_fs40.default.readFileSync(latestFile, "utf-8").split("\n");
13622
13771
  let lastModel = "";
13623
13772
  let lastInput = 0;
13624
13773
  let lastOutput = 0;
@@ -13643,10 +13792,10 @@ function readSessionUsage() {
13643
13792
  }
13644
13793
  }
13645
13794
  function formatContextStat(stat) {
13646
- const pctColor = stat.fillPct >= 80 ? import_chalk25.default.red : stat.fillPct >= 50 ? import_chalk25.default.yellow : import_chalk25.default.cyan;
13795
+ const pctColor = stat.fillPct >= 80 ? import_chalk26.default.red : stat.fillPct >= 50 ? import_chalk26.default.yellow : import_chalk26.default.cyan;
13647
13796
  const k = (n) => `${Math.round(n / 1e3)}k`;
13648
13797
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
13649
- return import_chalk25.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk25.default.dim(
13798
+ return import_chalk26.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk26.default.dim(
13650
13799
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
13651
13800
  );
13652
13801
  }
@@ -13662,28 +13811,28 @@ function wrappedLineCount(text) {
13662
13811
  function agentLabel(agent) {
13663
13812
  if (!agent || agent === "Terminal") return "";
13664
13813
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
13665
- return short ? import_chalk25.default.dim(`[${short}] `) : "";
13814
+ return short ? import_chalk26.default.dim(`[${short}] `) : "";
13666
13815
  }
13667
13816
  function formatBase(activity) {
13668
13817
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
13669
13818
  const icon = getIcon(activity.tool);
13670
13819
  const toolName = activity.tool.slice(0, 16).padEnd(16);
13671
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os35.default.homedir(), "~");
13820
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os36.default.homedir(), "~");
13672
13821
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
13673
- return `${import_chalk25.default.gray(time)} ${icon} ${agentLabel(activity.agent)}${import_chalk25.default.white.bold(toolName)} ${import_chalk25.default.dim(argsPreview)}`;
13822
+ return `${import_chalk26.default.gray(time)} ${icon} ${agentLabel(activity.agent)}${import_chalk26.default.white.bold(toolName)} ${import_chalk26.default.dim(argsPreview)}`;
13674
13823
  }
13675
13824
  function renderResult(activity, result) {
13676
13825
  const base = formatBase(activity);
13677
13826
  let status;
13678
13827
  if (result.status === "allow") {
13679
- status = import_chalk25.default.green("\u2713 ALLOW");
13828
+ status = import_chalk26.default.green("\u2713 ALLOW");
13680
13829
  } else if (result.status === "dlp") {
13681
- status = import_chalk25.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13830
+ status = import_chalk26.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13682
13831
  } else {
13683
- status = import_chalk25.default.red("\u2717 BLOCK");
13832
+ status = import_chalk26.default.red("\u2717 BLOCK");
13684
13833
  }
13685
13834
  const cost = result.costEstimate ?? activity.costEstimate;
13686
- const costSuffix = cost == null ? "" : import_chalk25.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
13835
+ const costSuffix = cost == null ? "" : import_chalk26.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
13687
13836
  if (process.stdout.isTTY) {
13688
13837
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
13689
13838
  import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -13700,19 +13849,19 @@ function renderResult(activity, result) {
13700
13849
  }
13701
13850
  function renderPending(activity) {
13702
13851
  if (!process.stdout.isTTY) return;
13703
- const line = `${formatBase(activity)} ${import_chalk25.default.yellow("\u25CF \u2026")}`;
13852
+ const line = `${formatBase(activity)} ${import_chalk26.default.yellow("\u25CF \u2026")}`;
13704
13853
  pendingShownForId = activity.id;
13705
13854
  pendingWrappedLines = wrappedLineCount(line);
13706
13855
  process.stdout.write(`${line}\r`);
13707
13856
  }
13708
13857
  async function ensureDaemon() {
13709
13858
  let pidPort = null;
13710
- if (import_fs39.default.existsSync(PID_FILE)) {
13859
+ if (import_fs40.default.existsSync(PID_FILE)) {
13711
13860
  try {
13712
- const { port } = JSON.parse(import_fs39.default.readFileSync(PID_FILE, "utf-8"));
13861
+ const { port } = JSON.parse(import_fs40.default.readFileSync(PID_FILE, "utf-8"));
13713
13862
  pidPort = port;
13714
13863
  } catch {
13715
- console.error(import_chalk25.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
13864
+ console.error(import_chalk26.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
13716
13865
  }
13717
13866
  }
13718
13867
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -13723,7 +13872,7 @@ async function ensureDaemon() {
13723
13872
  if (res.ok) return checkPort;
13724
13873
  } catch {
13725
13874
  }
13726
- console.log(import_chalk25.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
13875
+ console.log(import_chalk26.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
13727
13876
  const child = (0, import_child_process16.spawn)(process.execPath, [process.argv[1], "daemon"], {
13728
13877
  detached: true,
13729
13878
  stdio: "ignore",
@@ -13740,7 +13889,7 @@ async function ensureDaemon() {
13740
13889
  } catch {
13741
13890
  }
13742
13891
  }
13743
- console.error(import_chalk25.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
13892
+ console.error(import_chalk26.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
13744
13893
  process.exit(1);
13745
13894
  }
13746
13895
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -13806,7 +13955,7 @@ function buildCardLines(req, localCount = 0) {
13806
13955
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
13807
13956
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
13808
13957
  const description = rawDesc ? cleanReason(rawDesc) : "";
13809
- const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk25.default.dim(`(${req.agent})`)}` : "";
13958
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk26.default.dim(`(${req.agent})`)}` : "";
13810
13959
  const lines = [
13811
13960
  ``,
13812
13961
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -13862,9 +14011,9 @@ function buildRecoveryCardLines(req) {
13862
14011
  ];
13863
14012
  }
13864
14013
  function readApproversFromDisk() {
13865
- const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
14014
+ const configPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "config.json");
13866
14015
  try {
13867
- const raw = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
14016
+ const raw = JSON.parse(import_fs40.default.readFileSync(configPath, "utf-8"));
13868
14017
  const settings = raw.settings ?? {};
13869
14018
  return settings.approvers ?? {};
13870
14019
  } catch {
@@ -13875,20 +14024,20 @@ function approverStatusLine() {
13875
14024
  const a = readApproversFromDisk();
13876
14025
  const fmt = (label, key) => {
13877
14026
  const on = a[key] !== false;
13878
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk25.default.green("\u2713") : import_chalk25.default.dim("\u2717")}`;
14027
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk26.default.green("\u2713") : import_chalk26.default.dim("\u2717")}`;
13879
14028
  };
13880
14029
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
13881
14030
  }
13882
14031
  function toggleApprover(channel) {
13883
- const configPath = import_path42.default.join(import_os35.default.homedir(), ".node9", "config.json");
14032
+ const configPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "config.json");
13884
14033
  try {
13885
- const raw = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
14034
+ const raw = JSON.parse(import_fs40.default.readFileSync(configPath, "utf-8"));
13886
14035
  const settings = raw.settings ?? {};
13887
14036
  const approvers = settings.approvers ?? {};
13888
14037
  approvers[channel] = approvers[channel] === false;
13889
14038
  settings.approvers = approvers;
13890
14039
  raw.settings = settings;
13891
- import_fs39.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
14040
+ import_fs40.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
13892
14041
  } catch (err2) {
13893
14042
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
13894
14043
  `);
@@ -13920,7 +14069,7 @@ async function startTail(options = {}) {
13920
14069
  req2.end();
13921
14070
  });
13922
14071
  if (result.ok) {
13923
- console.log(import_chalk25.default.green("\u2713 Flight Recorder buffer cleared."));
14072
+ console.log(import_chalk26.default.green("\u2713 Flight Recorder buffer cleared."));
13924
14073
  } else if (result.code === "ECONNREFUSED") {
13925
14074
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
13926
14075
  } else if (result.code === "ETIMEDOUT") {
@@ -13964,7 +14113,7 @@ async function startTail(options = {}) {
13964
14113
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
13965
14114
  if (channel) {
13966
14115
  toggleApprover(channel);
13967
- console.log(import_chalk25.default.dim(` Approvers: ${approverStatusLine()}`));
14116
+ console.log(import_chalk26.default.dim(` Approvers: ${approverStatusLine()}`));
13968
14117
  }
13969
14118
  };
13970
14119
  process.stdin.on("keypress", idleKeypressHandler);
@@ -14030,7 +14179,7 @@ async function startTail(options = {}) {
14030
14179
  localAllowCounts.get(req2.toolName) ?? 0
14031
14180
  )
14032
14181
  );
14033
- const decisionStamp = action === "always-allow" ? import_chalk25.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk25.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk25.default.yellow("\u21A9 REDIRECT AI") : import_chalk25.default.red("\u2717 DENIED");
14182
+ const decisionStamp = action === "always-allow" ? import_chalk26.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk26.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk26.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk26.default.yellow("\u21A9 REDIRECT AI") : import_chalk26.default.red("\u2717 DENIED");
14034
14183
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
14035
14184
  for (const line of stampedLines) process.stdout.write(line + "\n");
14036
14185
  process.stdout.write(SHOW_CURSOR);
@@ -14058,8 +14207,8 @@ async function startTail(options = {}) {
14058
14207
  }
14059
14208
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
14060
14209
  try {
14061
- import_fs39.default.appendFileSync(
14062
- import_path42.default.join(import_os35.default.homedir(), ".node9", "hook-debug.log"),
14210
+ import_fs40.default.appendFileSync(
14211
+ import_path43.default.join(import_os36.default.homedir(), ".node9", "hook-debug.log"),
14063
14212
  `[tail] POST /decision failed: ${String(err2)}
14064
14213
  `
14065
14214
  );
@@ -14081,7 +14230,7 @@ async function startTail(options = {}) {
14081
14230
  );
14082
14231
  const stampedLines = buildCardLines(req2, priorCount);
14083
14232
  if (externalDecision) {
14084
- const source = externalDecision === "allow" ? import_chalk25.default.green("\u2713 ALLOWED") : import_chalk25.default.red("\u2717 DENIED");
14233
+ const source = externalDecision === "allow" ? import_chalk26.default.green("\u2713 ALLOWED") : import_chalk26.default.red("\u2717 DENIED");
14085
14234
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
14086
14235
  }
14087
14236
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -14140,31 +14289,31 @@ async function startTail(options = {}) {
14140
14289
  }
14141
14290
  } catch {
14142
14291
  }
14143
- const auditLog = import_path42.default.join(import_os35.default.homedir(), ".node9", "audit.log");
14292
+ const auditLog = import_path43.default.join(import_os36.default.homedir(), ".node9", "audit.log");
14144
14293
  try {
14145
- const unackedDlp = import_fs39.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
14294
+ const unackedDlp = import_fs40.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
14146
14295
  if (unackedDlp > 0) {
14147
14296
  console.log("");
14148
14297
  console.log(
14149
- import_chalk25.default.bgRed.white.bold(
14298
+ import_chalk26.default.bgRed.white.bold(
14150
14299
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
14151
14300
  )
14152
14301
  );
14153
14302
  }
14154
14303
  } catch {
14155
14304
  }
14156
- console.log(import_chalk25.default.cyan.bold(`
14157
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk25.default.dim(`\u2192 ${dashboardUrl}`));
14305
+ console.log(import_chalk26.default.cyan.bold(`
14306
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk26.default.dim(`\u2192 ${dashboardUrl}`));
14158
14307
  if (canApprove) {
14159
- console.log(import_chalk25.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
14160
- console.log(import_chalk25.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
14308
+ console.log(import_chalk26.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
14309
+ console.log(import_chalk26.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
14161
14310
  }
14162
14311
  const ctxStat = readSessionUsage();
14163
14312
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
14164
14313
  if (options.history) {
14165
- console.log(import_chalk25.default.dim("Showing history + live events.\n"));
14314
+ console.log(import_chalk26.default.dim("Showing history + live events.\n"));
14166
14315
  } else {
14167
- console.log(import_chalk25.default.dim("Showing live events only. Use --history to include past.\n"));
14316
+ console.log(import_chalk26.default.dim("Showing live events only. Use --history to include past.\n"));
14168
14317
  }
14169
14318
  process.on("SIGINT", () => {
14170
14319
  exitIdleMode();
@@ -14174,13 +14323,13 @@ async function startTail(options = {}) {
14174
14323
  import_readline5.default.clearLine(process.stdout, 0);
14175
14324
  import_readline5.default.cursorTo(process.stdout, 0);
14176
14325
  }
14177
- console.log(import_chalk25.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14326
+ console.log(import_chalk26.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14178
14327
  process.exit(0);
14179
14328
  });
14180
14329
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
14181
14330
  const req = import_http2.default.get(sseUrl, (res) => {
14182
14331
  if (res.statusCode !== 200) {
14183
- console.error(import_chalk25.default.red(`Failed to connect: HTTP ${res.statusCode}`));
14332
+ console.error(import_chalk26.default.red(`Failed to connect: HTTP ${res.statusCode}`));
14184
14333
  process.exit(1);
14185
14334
  }
14186
14335
  if (canApprove) enterIdleMode();
@@ -14211,7 +14360,7 @@ async function startTail(options = {}) {
14211
14360
  import_readline5.default.clearLine(process.stdout, 0);
14212
14361
  import_readline5.default.cursorTo(process.stdout, 0);
14213
14362
  }
14214
- console.log(import_chalk25.default.red("\n\u274C Daemon disconnected."));
14363
+ console.log(import_chalk26.default.red("\n\u274C Daemon disconnected."));
14215
14364
  process.exit(1);
14216
14365
  });
14217
14366
  });
@@ -14303,9 +14452,9 @@ async function startTail(options = {}) {
14303
14452
  const hash = data.hash ?? "";
14304
14453
  const summary = data.argsSummary ?? data.tool;
14305
14454
  const fileCount = data.fileCount ?? 0;
14306
- const files = fileCount > 0 ? import_chalk25.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
14455
+ const files = fileCount > 0 ? import_chalk26.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
14307
14456
  process.stdout.write(
14308
- `${import_chalk25.default.dim(time)} ${import_chalk25.default.cyan("\u{1F4F8} snapshot")} ${import_chalk25.default.dim(hash)} ${summary}${files}
14457
+ `${import_chalk26.default.dim(time)} ${import_chalk26.default.cyan("\u{1F4F8} snapshot")} ${import_chalk26.default.dim(hash)} ${summary}${files}
14309
14458
  `
14310
14459
  );
14311
14460
  return;
@@ -14322,26 +14471,26 @@ async function startTail(options = {}) {
14322
14471
  }
14323
14472
  req.on("error", (err2) => {
14324
14473
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
14325
- console.error(import_chalk25.default.red(`
14474
+ console.error(import_chalk26.default.red(`
14326
14475
  \u274C ${msg}`));
14327
14476
  process.exit(1);
14328
14477
  });
14329
14478
  }
14330
- var import_http2, import_chalk25, import_fs39, import_os35, import_path42, import_readline5, import_child_process16, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
14479
+ var import_http2, import_chalk26, import_fs40, import_os36, import_path43, import_readline5, import_child_process16, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
14331
14480
  var init_tail = __esm({
14332
14481
  "src/tui/tail.ts"() {
14333
14482
  "use strict";
14334
14483
  import_http2 = __toESM(require("http"));
14335
- import_chalk25 = __toESM(require("chalk"));
14336
- import_fs39 = __toESM(require("fs"));
14337
- import_os35 = __toESM(require("os"));
14338
- import_path42 = __toESM(require("path"));
14484
+ import_chalk26 = __toESM(require("chalk"));
14485
+ import_fs40 = __toESM(require("fs"));
14486
+ import_os36 = __toESM(require("os"));
14487
+ import_path43 = __toESM(require("path"));
14339
14488
  import_readline5 = __toESM(require("readline"));
14340
14489
  import_child_process16 = require("child_process");
14341
14490
  init_daemon2();
14342
14491
  init_daemon();
14343
14492
  init_core();
14344
- PID_FILE = import_path42.default.join(import_os35.default.homedir(), ".node9", "daemon.pid");
14493
+ PID_FILE = import_path43.default.join(import_os36.default.homedir(), ".node9", "daemon.pid");
14345
14494
  ICONS = {
14346
14495
  bash: "\u{1F4BB}",
14347
14496
  shell: "\u{1F4BB}",
@@ -14463,9 +14612,9 @@ function formatTimeLeft(resetsAt) {
14463
14612
  return ` (${m}m left)`;
14464
14613
  }
14465
14614
  function safeReadJson(filePath) {
14466
- if (!import_fs40.default.existsSync(filePath)) return null;
14615
+ if (!import_fs41.default.existsSync(filePath)) return null;
14467
14616
  try {
14468
- return JSON.parse(import_fs40.default.readFileSync(filePath, "utf-8"));
14617
+ return JSON.parse(import_fs41.default.readFileSync(filePath, "utf-8"));
14469
14618
  } catch {
14470
14619
  return null;
14471
14620
  }
@@ -14486,12 +14635,12 @@ function countHooksInFile(filePath) {
14486
14635
  return Object.keys(cfg.hooks).length;
14487
14636
  }
14488
14637
  function countRulesInDir(rulesDir) {
14489
- if (!import_fs40.default.existsSync(rulesDir)) return 0;
14638
+ if (!import_fs41.default.existsSync(rulesDir)) return 0;
14490
14639
  let count = 0;
14491
14640
  try {
14492
- for (const entry of import_fs40.default.readdirSync(rulesDir, { withFileTypes: true })) {
14641
+ for (const entry of import_fs41.default.readdirSync(rulesDir, { withFileTypes: true })) {
14493
14642
  if (entry.isDirectory()) {
14494
- count += countRulesInDir(import_path43.default.join(rulesDir, entry.name));
14643
+ count += countRulesInDir(import_path44.default.join(rulesDir, entry.name));
14495
14644
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
14496
14645
  count++;
14497
14646
  }
@@ -14502,46 +14651,46 @@ function countRulesInDir(rulesDir) {
14502
14651
  }
14503
14652
  function isSamePath(a, b) {
14504
14653
  try {
14505
- return import_path43.default.resolve(a) === import_path43.default.resolve(b);
14654
+ return import_path44.default.resolve(a) === import_path44.default.resolve(b);
14506
14655
  } catch {
14507
14656
  return false;
14508
14657
  }
14509
14658
  }
14510
14659
  function countConfigs(cwd) {
14511
- const homeDir2 = import_os36.default.homedir();
14512
- const claudeDir = import_path43.default.join(homeDir2, ".claude");
14660
+ const homeDir2 = import_os37.default.homedir();
14661
+ const claudeDir = import_path44.default.join(homeDir2, ".claude");
14513
14662
  let claudeMdCount = 0;
14514
14663
  let rulesCount = 0;
14515
14664
  let hooksCount = 0;
14516
14665
  const userMcpServers = /* @__PURE__ */ new Set();
14517
14666
  const projectMcpServers = /* @__PURE__ */ new Set();
14518
- if (import_fs40.default.existsSync(import_path43.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
14519
- rulesCount += countRulesInDir(import_path43.default.join(claudeDir, "rules"));
14520
- const userSettings = import_path43.default.join(claudeDir, "settings.json");
14667
+ if (import_fs41.default.existsSync(import_path44.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
14668
+ rulesCount += countRulesInDir(import_path44.default.join(claudeDir, "rules"));
14669
+ const userSettings = import_path44.default.join(claudeDir, "settings.json");
14521
14670
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
14522
14671
  hooksCount += countHooksInFile(userSettings);
14523
- const userClaudeJson = import_path43.default.join(homeDir2, ".claude.json");
14672
+ const userClaudeJson = import_path44.default.join(homeDir2, ".claude.json");
14524
14673
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
14525
14674
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
14526
14675
  userMcpServers.delete(name);
14527
14676
  }
14528
14677
  if (cwd) {
14529
- if (import_fs40.default.existsSync(import_path43.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
14530
- if (import_fs40.default.existsSync(import_path43.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
14531
- const projectClaudeDir = import_path43.default.join(cwd, ".claude");
14678
+ if (import_fs41.default.existsSync(import_path44.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
14679
+ if (import_fs41.default.existsSync(import_path44.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
14680
+ const projectClaudeDir = import_path44.default.join(cwd, ".claude");
14532
14681
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
14533
14682
  if (!overlapsUserScope) {
14534
- if (import_fs40.default.existsSync(import_path43.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
14535
- rulesCount += countRulesInDir(import_path43.default.join(projectClaudeDir, "rules"));
14536
- const projSettings = import_path43.default.join(projectClaudeDir, "settings.json");
14683
+ if (import_fs41.default.existsSync(import_path44.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
14684
+ rulesCount += countRulesInDir(import_path44.default.join(projectClaudeDir, "rules"));
14685
+ const projSettings = import_path44.default.join(projectClaudeDir, "settings.json");
14537
14686
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
14538
14687
  hooksCount += countHooksInFile(projSettings);
14539
14688
  }
14540
- if (import_fs40.default.existsSync(import_path43.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
14541
- const localSettings = import_path43.default.join(projectClaudeDir, "settings.local.json");
14689
+ if (import_fs41.default.existsSync(import_path44.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
14690
+ const localSettings = import_path44.default.join(projectClaudeDir, "settings.local.json");
14542
14691
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
14543
14692
  hooksCount += countHooksInFile(localSettings);
14544
- const mcpJsonServers = getMcpServerNames(import_path43.default.join(cwd, ".mcp.json"));
14693
+ const mcpJsonServers = getMcpServerNames(import_path44.default.join(cwd, ".mcp.json"));
14545
14694
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
14546
14695
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
14547
14696
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -14574,12 +14723,12 @@ function readActiveShieldsHud() {
14574
14723
  return shieldsCache.value;
14575
14724
  }
14576
14725
  try {
14577
- const shieldsPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "shields.json");
14578
- if (!import_fs40.default.existsSync(shieldsPath)) {
14726
+ const shieldsPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "shields.json");
14727
+ if (!import_fs41.default.existsSync(shieldsPath)) {
14579
14728
  shieldsCache = { value: [], ts: now };
14580
14729
  return [];
14581
14730
  }
14582
- const parsed = JSON.parse(import_fs40.default.readFileSync(shieldsPath, "utf-8"));
14731
+ const parsed = JSON.parse(import_fs41.default.readFileSync(shieldsPath, "utf-8"));
14583
14732
  if (!Array.isArray(parsed.active)) {
14584
14733
  shieldsCache = { value: [], ts: now };
14585
14734
  return [];
@@ -14681,17 +14830,17 @@ function renderContextLine(stdin) {
14681
14830
  async function main() {
14682
14831
  try {
14683
14832
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
14684
- if (import_fs40.default.existsSync(import_path43.default.join(import_os36.default.homedir(), ".node9", "hud-debug"))) {
14833
+ if (import_fs41.default.existsSync(import_path44.default.join(import_os37.default.homedir(), ".node9", "hud-debug"))) {
14685
14834
  try {
14686
- const logPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "hud-debug.log");
14835
+ const logPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "hud-debug.log");
14687
14836
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
14688
14837
  let size = 0;
14689
14838
  try {
14690
- size = import_fs40.default.statSync(logPath).size;
14839
+ size = import_fs41.default.statSync(logPath).size;
14691
14840
  } catch {
14692
14841
  }
14693
14842
  if (size < MAX_LOG_SIZE) {
14694
- import_fs40.default.appendFileSync(
14843
+ import_fs41.default.appendFileSync(
14695
14844
  logPath,
14696
14845
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
14697
14846
  );
@@ -14712,11 +14861,11 @@ async function main() {
14712
14861
  try {
14713
14862
  const cwd = stdin.cwd ?? process.cwd();
14714
14863
  for (const configPath of [
14715
- import_path43.default.join(cwd, "node9.config.json"),
14716
- import_path43.default.join(import_os36.default.homedir(), ".node9", "config.json")
14864
+ import_path44.default.join(cwd, "node9.config.json"),
14865
+ import_path44.default.join(import_os37.default.homedir(), ".node9", "config.json")
14717
14866
  ]) {
14718
- if (!import_fs40.default.existsSync(configPath)) continue;
14719
- const cfg = JSON.parse(import_fs40.default.readFileSync(configPath, "utf-8"));
14867
+ if (!import_fs41.default.existsSync(configPath)) continue;
14868
+ const cfg = JSON.parse(import_fs41.default.readFileSync(configPath, "utf-8"));
14720
14869
  const hud = cfg.settings?.hud;
14721
14870
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
14722
14871
  }
@@ -14734,13 +14883,13 @@ async function main() {
14734
14883
  renderOffline();
14735
14884
  }
14736
14885
  }
14737
- var import_fs40, import_path43, import_os36, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
14886
+ var import_fs41, import_path44, import_os37, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
14738
14887
  var init_hud = __esm({
14739
14888
  "src/cli/hud.ts"() {
14740
14889
  "use strict";
14741
- import_fs40 = __toESM(require("fs"));
14742
- import_path43 = __toESM(require("path"));
14743
- import_os36 = __toESM(require("os"));
14890
+ import_fs41 = __toESM(require("fs"));
14891
+ import_path44 = __toESM(require("path"));
14892
+ import_os37 = __toESM(require("os"));
14744
14893
  import_http3 = __toESM(require("http"));
14745
14894
  init_daemon();
14746
14895
  RESET3 = "\x1B[0m";
@@ -14766,10 +14915,10 @@ var import_commander = require("commander");
14766
14915
  init_core();
14767
14916
  init_setup();
14768
14917
  init_daemon2();
14769
- var import_chalk26 = __toESM(require("chalk"));
14770
- var import_fs41 = __toESM(require("fs"));
14771
- var import_path44 = __toESM(require("path"));
14772
- var import_os37 = __toESM(require("os"));
14918
+ var import_chalk27 = __toESM(require("chalk"));
14919
+ var import_fs42 = __toESM(require("fs"));
14920
+ var import_path45 = __toESM(require("path"));
14921
+ var import_os38 = __toESM(require("os"));
14773
14922
  var import_prompts2 = require("@inquirer/prompts");
14774
14923
 
14775
14924
  // src/utils/duration.ts
@@ -20479,22 +20628,207 @@ function registerDlpCommand(program2) {
20479
20628
  });
20480
20629
  }
20481
20630
 
20631
+ // src/cli/commands/mask.ts
20632
+ var import_chalk25 = __toESM(require("chalk"));
20633
+ var import_fs39 = __toESM(require("fs"));
20634
+ var import_path42 = __toESM(require("path"));
20635
+ var import_os35 = __toESM(require("os"));
20636
+ init_dlp();
20637
+ function findJsonlFiles(dir) {
20638
+ const results = [];
20639
+ if (!import_fs39.default.existsSync(dir)) return results;
20640
+ for (const entry of import_fs39.default.readdirSync(dir, { withFileTypes: true })) {
20641
+ const full = import_path42.default.join(dir, entry.name);
20642
+ if (entry.isDirectory()) results.push(...findJsonlFiles(full));
20643
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
20644
+ }
20645
+ return results;
20646
+ }
20647
+ function redactJson(obj) {
20648
+ if (typeof obj === "string") {
20649
+ const { result, found } = redactText(obj);
20650
+ return { value: result, modified: result !== obj, found };
20651
+ }
20652
+ if (Array.isArray(obj)) {
20653
+ let modified = false;
20654
+ const found = [];
20655
+ const value = obj.map((item) => {
20656
+ const r = redactJson(item);
20657
+ if (r.modified) modified = true;
20658
+ r.found.forEach((f) => {
20659
+ if (!found.includes(f)) found.push(f);
20660
+ });
20661
+ return r.value;
20662
+ });
20663
+ return { value, modified, found };
20664
+ }
20665
+ if (obj !== null && typeof obj === "object") {
20666
+ let modified = false;
20667
+ const found = [];
20668
+ const value = {};
20669
+ for (const [k, v] of Object.entries(obj)) {
20670
+ const r = redactJson(v);
20671
+ value[k] = r.value;
20672
+ if (r.modified) modified = true;
20673
+ r.found.forEach((f) => {
20674
+ if (!found.includes(f)) found.push(f);
20675
+ });
20676
+ }
20677
+ return { value, modified, found };
20678
+ }
20679
+ return { value: obj, modified: false, found: [] };
20680
+ }
20681
+ function processFile(filePath, dryRun) {
20682
+ let raw;
20683
+ try {
20684
+ raw = import_fs39.default.readFileSync(filePath, "utf-8");
20685
+ } catch {
20686
+ return { redactedLines: 0, patterns: [] };
20687
+ }
20688
+ const lines = raw.split("\n");
20689
+ let redactedLines = 0;
20690
+ const patterns = [];
20691
+ const newLines = [];
20692
+ for (const line of lines) {
20693
+ if (!line.trim()) {
20694
+ newLines.push(line);
20695
+ continue;
20696
+ }
20697
+ let parsed;
20698
+ try {
20699
+ parsed = JSON.parse(line);
20700
+ } catch {
20701
+ newLines.push(line);
20702
+ continue;
20703
+ }
20704
+ const { value, modified, found } = redactJson(parsed);
20705
+ if (modified) {
20706
+ redactedLines++;
20707
+ found.forEach((f) => {
20708
+ if (!patterns.includes(f)) patterns.push(f);
20709
+ });
20710
+ newLines.push(JSON.stringify(value));
20711
+ } else {
20712
+ newLines.push(line);
20713
+ }
20714
+ }
20715
+ if (!dryRun && redactedLines > 0) {
20716
+ import_fs39.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
20717
+ }
20718
+ return { redactedLines, patterns };
20719
+ }
20720
+ function processJsonFile(filePath, dryRun) {
20721
+ let raw;
20722
+ try {
20723
+ raw = import_fs39.default.readFileSync(filePath, "utf-8");
20724
+ } catch {
20725
+ return { redactedLines: 0, patterns: [] };
20726
+ }
20727
+ let parsed;
20728
+ try {
20729
+ parsed = JSON.parse(raw);
20730
+ } catch {
20731
+ return { redactedLines: 0, patterns: [] };
20732
+ }
20733
+ const { value, modified, found } = redactJson(parsed);
20734
+ if (!modified) return { redactedLines: 0, patterns: [] };
20735
+ if (!dryRun) {
20736
+ import_fs39.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
20737
+ }
20738
+ return { redactedLines: 1, patterns: found };
20739
+ }
20740
+ function findJsonFiles(dir) {
20741
+ const results = [];
20742
+ if (!import_fs39.default.existsSync(dir)) return results;
20743
+ for (const entry of import_fs39.default.readdirSync(dir, { withFileTypes: true })) {
20744
+ const full = import_path42.default.join(dir, entry.name);
20745
+ if (entry.isDirectory()) results.push(...findJsonFiles(full));
20746
+ else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
20747
+ }
20748
+ return results;
20749
+ }
20750
+ function registerMaskCommand(program2) {
20751
+ program2.command("mask").description("Redact plaintext secrets from local AI session history files").option("--dry-run", "show what would be redacted without making changes").option("--all", "scan all history (default: last 30 days)").action(async (options) => {
20752
+ const dryRun = !!options.dryRun;
20753
+ const home = import_os35.default.homedir();
20754
+ const claudeDir = import_path42.default.join(home, ".claude", "projects");
20755
+ const geminiDir = import_path42.default.join(home, ".gemini", "tmp");
20756
+ const allFiles = [
20757
+ ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
20758
+ ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
20759
+ ];
20760
+ const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
20761
+ const filtered = cutoff ? allFiles.filter((f) => {
20762
+ try {
20763
+ return import_fs39.default.statSync(f.path).mtime >= cutoff;
20764
+ } catch {
20765
+ return false;
20766
+ }
20767
+ }) : allFiles;
20768
+ if (filtered.length === 0) {
20769
+ console.log(import_chalk25.default.yellow(" No session files found."));
20770
+ return;
20771
+ }
20772
+ console.log("");
20773
+ if (dryRun) {
20774
+ console.log(import_chalk25.default.dim(" Dry run \u2014 no files will be modified.\n"));
20775
+ }
20776
+ let totalFiles = 0;
20777
+ let totalLines = 0;
20778
+ const totalPatterns = [];
20779
+ for (const file of filtered) {
20780
+ const shortPath = file.path.replace(home, "~");
20781
+ const { redactedLines, patterns } = file.type === "jsonl" ? processFile(file.path, dryRun) : processJsonFile(file.path, dryRun);
20782
+ if (redactedLines > 0) {
20783
+ totalFiles++;
20784
+ totalLines += redactedLines;
20785
+ patterns.forEach((p) => {
20786
+ if (!totalPatterns.includes(p)) totalPatterns.push(p);
20787
+ });
20788
+ const verb = dryRun ? "Would redact" : "Redacted";
20789
+ console.log(
20790
+ " " + import_chalk25.default.dim(shortPath.slice(0, 60).padEnd(62)) + import_chalk25.default.red(`${verb}: `) + import_chalk25.default.yellow(patterns.join(", ")) + import_chalk25.default.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
20791
+ );
20792
+ }
20793
+ }
20794
+ console.log("");
20795
+ if (totalFiles === 0) {
20796
+ console.log(import_chalk25.default.green(" No secrets found in session history."));
20797
+ } else {
20798
+ const verb = dryRun ? "would be modified" : "modified";
20799
+ console.log(
20800
+ import_chalk25.default.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + import_chalk25.default.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
20801
+ );
20802
+ console.log(" Patterns: " + import_chalk25.default.yellow(totalPatterns.join(", ")));
20803
+ if (!dryRun) {
20804
+ console.log("");
20805
+ console.log(
20806
+ import_chalk25.default.dim(
20807
+ " Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
20808
+ )
20809
+ );
20810
+ }
20811
+ }
20812
+ console.log("");
20813
+ });
20814
+ }
20815
+
20482
20816
  // src/cli.ts
20483
20817
  var { version } = JSON.parse(
20484
- import_fs41.default.readFileSync(import_path44.default.join(__dirname, "../package.json"), "utf-8")
20818
+ import_fs42.default.readFileSync(import_path45.default.join(__dirname, "../package.json"), "utf-8")
20485
20819
  );
20486
20820
  var program = new import_commander.Command();
20487
20821
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
20488
20822
  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) => {
20489
20823
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
20490
- const credPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "credentials.json");
20491
- if (!import_fs41.default.existsSync(import_path44.default.dirname(credPath)))
20492
- import_fs41.default.mkdirSync(import_path44.default.dirname(credPath), { recursive: true });
20824
+ const credPath = import_path45.default.join(import_os38.default.homedir(), ".node9", "credentials.json");
20825
+ if (!import_fs42.default.existsSync(import_path45.default.dirname(credPath)))
20826
+ import_fs42.default.mkdirSync(import_path45.default.dirname(credPath), { recursive: true });
20493
20827
  const profileName = options.profile || "default";
20494
20828
  let existingCreds = {};
20495
20829
  try {
20496
- if (import_fs41.default.existsSync(credPath)) {
20497
- const raw = JSON.parse(import_fs41.default.readFileSync(credPath, "utf-8"));
20830
+ if (import_fs42.default.existsSync(credPath)) {
20831
+ const raw = JSON.parse(import_fs42.default.readFileSync(credPath, "utf-8"));
20498
20832
  if (raw.apiKey) {
20499
20833
  existingCreds = {
20500
20834
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -20506,13 +20840,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20506
20840
  } catch {
20507
20841
  }
20508
20842
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
20509
- import_fs41.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20843
+ import_fs42.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20510
20844
  if (profileName === "default") {
20511
- const configPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "config.json");
20845
+ const configPath = import_path45.default.join(import_os38.default.homedir(), ".node9", "config.json");
20512
20846
  let config = {};
20513
20847
  try {
20514
- if (import_fs41.default.existsSync(configPath))
20515
- config = JSON.parse(import_fs41.default.readFileSync(configPath, "utf-8"));
20848
+ if (import_fs42.default.existsSync(configPath))
20849
+ config = JSON.parse(import_fs42.default.readFileSync(configPath, "utf-8"));
20516
20850
  } catch {
20517
20851
  }
20518
20852
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -20527,19 +20861,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20527
20861
  approvers.cloud = false;
20528
20862
  }
20529
20863
  s.approvers = approvers;
20530
- if (!import_fs41.default.existsSync(import_path44.default.dirname(configPath)))
20531
- import_fs41.default.mkdirSync(import_path44.default.dirname(configPath), { recursive: true });
20532
- import_fs41.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
20864
+ if (!import_fs42.default.existsSync(import_path45.default.dirname(configPath)))
20865
+ import_fs42.default.mkdirSync(import_path45.default.dirname(configPath), { recursive: true });
20866
+ import_fs42.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
20533
20867
  }
20534
20868
  if (options.profile && profileName !== "default") {
20535
- console.log(import_chalk26.default.green(`\u2705 Profile "${profileName}" saved`));
20536
- console.log(import_chalk26.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
20869
+ console.log(import_chalk27.default.green(`\u2705 Profile "${profileName}" saved`));
20870
+ console.log(import_chalk27.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
20537
20871
  } else if (options.local) {
20538
- console.log(import_chalk26.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
20539
- console.log(import_chalk26.default.gray(` All decisions stay on this machine.`));
20872
+ console.log(import_chalk27.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
20873
+ console.log(import_chalk27.default.gray(` All decisions stay on this machine.`));
20540
20874
  } else {
20541
- console.log(import_chalk26.default.green(`\u2705 Logged in \u2014 agent mode`));
20542
- console.log(import_chalk26.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
20875
+ console.log(import_chalk27.default.green(`\u2705 Logged in \u2014 agent mode`));
20876
+ console.log(import_chalk27.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
20543
20877
  }
20544
20878
  });
20545
20879
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
@@ -20557,7 +20891,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
20557
20891
  if (target === "vscode") return await setupVSCode();
20558
20892
  if (target === "hud") return setupHud();
20559
20893
  console.error(
20560
- import_chalk26.default.red(
20894
+ import_chalk27.default.red(
20561
20895
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20562
20896
  )
20563
20897
  );
@@ -20571,17 +20905,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20571
20905
  "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
20572
20906
  ).action(async (target) => {
20573
20907
  if (!target) {
20574
- console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
20575
- console.log(" Usage: " + import_chalk26.default.white("node9 setup <target>") + "\n");
20908
+ console.log(import_chalk27.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
20909
+ console.log(" Usage: " + import_chalk27.default.white("node9 setup <target>") + "\n");
20576
20910
  console.log(" Targets:");
20577
- console.log(" " + import_chalk26.default.green("claude") + " \u2014 Claude Code (hook mode)");
20578
- console.log(" " + import_chalk26.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
20579
- console.log(" " + import_chalk26.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
20580
- console.log(" " + import_chalk26.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
20581
- console.log(" " + import_chalk26.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
20582
- console.log(" " + import_chalk26.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
20911
+ console.log(" " + import_chalk27.default.green("claude") + " \u2014 Claude Code (hook mode)");
20912
+ console.log(" " + import_chalk27.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
20913
+ console.log(" " + import_chalk27.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
20914
+ console.log(" " + import_chalk27.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
20915
+ console.log(" " + import_chalk27.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
20916
+ console.log(" " + import_chalk27.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
20583
20917
  process.stdout.write(
20584
- " " + import_chalk26.default.green("hud") + " \u2014 Claude Code security statusline\n"
20918
+ " " + import_chalk27.default.green("hud") + " \u2014 Claude Code security statusline\n"
20585
20919
  );
20586
20920
  console.log("");
20587
20921
  return;
@@ -20595,7 +20929,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20595
20929
  if (t === "vscode") return await setupVSCode();
20596
20930
  if (t === "hud") return setupHud();
20597
20931
  console.error(
20598
- import_chalk26.default.red(
20932
+ import_chalk27.default.red(
20599
20933
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20600
20934
  )
20601
20935
  );
@@ -20618,33 +20952,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
20618
20952
  else if (target === "hud") fn = teardownHud;
20619
20953
  else {
20620
20954
  console.error(
20621
- import_chalk26.default.red(
20955
+ import_chalk27.default.red(
20622
20956
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20623
20957
  )
20624
20958
  );
20625
20959
  process.exit(1);
20626
20960
  }
20627
- console.log(import_chalk26.default.cyan(`
20961
+ console.log(import_chalk27.default.cyan(`
20628
20962
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
20629
20963
  `));
20630
20964
  try {
20631
20965
  fn();
20632
20966
  } catch (err2) {
20633
- console.error(import_chalk26.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
20967
+ console.error(import_chalk27.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
20634
20968
  process.exit(1);
20635
20969
  }
20636
- console.log(import_chalk26.default.gray("\n Restart the agent for changes to take effect."));
20970
+ console.log(import_chalk27.default.gray("\n Restart the agent for changes to take effect."));
20637
20971
  });
20638
20972
  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) => {
20639
- console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
20640
- console.log(import_chalk26.default.bold("Stopping daemon..."));
20973
+ console.log(import_chalk27.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
20974
+ console.log(import_chalk27.default.bold("Stopping daemon..."));
20641
20975
  try {
20642
20976
  stopDaemon();
20643
- console.log(import_chalk26.default.green(" \u2705 Daemon stopped"));
20977
+ console.log(import_chalk27.default.green(" \u2705 Daemon stopped"));
20644
20978
  } catch {
20645
- console.log(import_chalk26.default.blue(" \u2139\uFE0F Daemon was not running"));
20979
+ console.log(import_chalk27.default.blue(" \u2139\uFE0F Daemon was not running"));
20646
20980
  }
20647
- console.log(import_chalk26.default.bold("\nRemoving hooks..."));
20981
+ console.log(import_chalk27.default.bold("\nRemoving hooks..."));
20648
20982
  let teardownFailed = false;
20649
20983
  for (const [label, fn] of [
20650
20984
  ["Claude", teardownClaude],
@@ -20659,45 +20993,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
20659
20993
  } catch (err2) {
20660
20994
  teardownFailed = true;
20661
20995
  console.error(
20662
- import_chalk26.default.red(
20996
+ import_chalk27.default.red(
20663
20997
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
20664
20998
  )
20665
20999
  );
20666
21000
  }
20667
21001
  }
20668
21002
  if (options.purge) {
20669
- const node9Dir = import_path44.default.join(import_os37.default.homedir(), ".node9");
20670
- if (import_fs41.default.existsSync(node9Dir)) {
21003
+ const node9Dir = import_path45.default.join(import_os38.default.homedir(), ".node9");
21004
+ if (import_fs42.default.existsSync(node9Dir)) {
20671
21005
  const confirmed = await (0, import_prompts2.confirm)({
20672
21006
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
20673
21007
  default: false
20674
21008
  });
20675
21009
  if (confirmed) {
20676
- import_fs41.default.rmSync(node9Dir, { recursive: true });
20677
- if (import_fs41.default.existsSync(node9Dir)) {
21010
+ import_fs42.default.rmSync(node9Dir, { recursive: true });
21011
+ if (import_fs42.default.existsSync(node9Dir)) {
20678
21012
  console.error(
20679
- import_chalk26.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
21013
+ import_chalk27.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
20680
21014
  );
20681
21015
  } else {
20682
- console.log(import_chalk26.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
21016
+ console.log(import_chalk27.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
20683
21017
  }
20684
21018
  } else {
20685
- console.log(import_chalk26.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
21019
+ console.log(import_chalk27.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
20686
21020
  }
20687
21021
  } else {
20688
- console.log(import_chalk26.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
21022
+ console.log(import_chalk27.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
20689
21023
  }
20690
21024
  } else {
20691
21025
  console.log(
20692
- import_chalk26.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
21026
+ import_chalk27.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
20693
21027
  );
20694
21028
  }
20695
21029
  if (teardownFailed) {
20696
- console.error(import_chalk26.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
21030
+ console.error(import_chalk27.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
20697
21031
  process.exit(1);
20698
21032
  }
20699
- console.log(import_chalk26.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
20700
- console.log(import_chalk26.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
21033
+ console.log(import_chalk27.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
21034
+ console.log(import_chalk27.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
20701
21035
  });
20702
21036
  registerDoctorCommand(program, version);
20703
21037
  program.command("explain").description(
@@ -20710,7 +21044,7 @@ program.command("explain").description(
20710
21044
  try {
20711
21045
  args = JSON.parse(trimmed);
20712
21046
  } catch {
20713
- console.error(import_chalk26.default.red(`
21047
+ console.error(import_chalk27.default.red(`
20714
21048
  \u274C Invalid JSON: ${trimmed}
20715
21049
  `));
20716
21050
  process.exit(1);
@@ -20721,54 +21055,54 @@ program.command("explain").description(
20721
21055
  }
20722
21056
  const result = await explainPolicy(tool, args);
20723
21057
  console.log("");
20724
- console.log(import_chalk26.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
21058
+ console.log(import_chalk27.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
20725
21059
  console.log("");
20726
- console.log(` ${import_chalk26.default.bold("Tool:")} ${import_chalk26.default.white(result.tool)}`);
21060
+ console.log(` ${import_chalk27.default.bold("Tool:")} ${import_chalk27.default.white(result.tool)}`);
20727
21061
  if (argsRaw) {
20728
21062
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
20729
- console.log(` ${import_chalk26.default.bold("Input:")} ${import_chalk26.default.gray(preview2)}`);
21063
+ console.log(` ${import_chalk27.default.bold("Input:")} ${import_chalk27.default.gray(preview2)}`);
20730
21064
  }
20731
21065
  console.log("");
20732
- console.log(import_chalk26.default.bold("Config Sources (Waterfall):"));
21066
+ console.log(import_chalk27.default.bold("Config Sources (Waterfall):"));
20733
21067
  for (const tier of result.waterfall) {
20734
- const num3 = import_chalk26.default.gray(` ${tier.tier}.`);
21068
+ const num3 = import_chalk27.default.gray(` ${tier.tier}.`);
20735
21069
  const label = tier.label.padEnd(16);
20736
21070
  let statusStr;
20737
21071
  if (tier.tier === 1) {
20738
- statusStr = import_chalk26.default.gray(tier.note ?? "");
21072
+ statusStr = import_chalk27.default.gray(tier.note ?? "");
20739
21073
  } else if (tier.status === "active") {
20740
- const loc = tier.path ? import_chalk26.default.gray(tier.path) : "";
20741
- const note = tier.note ? import_chalk26.default.gray(`(${tier.note})`) : "";
20742
- statusStr = import_chalk26.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
21074
+ const loc = tier.path ? import_chalk27.default.gray(tier.path) : "";
21075
+ const note = tier.note ? import_chalk27.default.gray(`(${tier.note})`) : "";
21076
+ statusStr = import_chalk27.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
20743
21077
  } else {
20744
- statusStr = import_chalk26.default.gray("\u25CB " + (tier.note ?? "not found"));
21078
+ statusStr = import_chalk27.default.gray("\u25CB " + (tier.note ?? "not found"));
20745
21079
  }
20746
- console.log(`${num3} ${import_chalk26.default.white(label)} ${statusStr}`);
21080
+ console.log(`${num3} ${import_chalk27.default.white(label)} ${statusStr}`);
20747
21081
  }
20748
21082
  console.log("");
20749
- console.log(import_chalk26.default.bold("Policy Evaluation:"));
21083
+ console.log(import_chalk27.default.bold("Policy Evaluation:"));
20750
21084
  for (const step of result.steps) {
20751
21085
  const isFinal = step.isFinal;
20752
21086
  let icon;
20753
- if (step.outcome === "allow") icon = import_chalk26.default.green(" \u2705");
20754
- else if (step.outcome === "review") icon = import_chalk26.default.red(" \u{1F534}");
20755
- else if (step.outcome === "skip") icon = import_chalk26.default.gray(" \u2500 ");
20756
- else icon = import_chalk26.default.gray(" \u25CB ");
21087
+ if (step.outcome === "allow") icon = import_chalk27.default.green(" \u2705");
21088
+ else if (step.outcome === "review") icon = import_chalk27.default.red(" \u{1F534}");
21089
+ else if (step.outcome === "skip") icon = import_chalk27.default.gray(" \u2500 ");
21090
+ else icon = import_chalk27.default.gray(" \u25CB ");
20757
21091
  const name = step.name.padEnd(18);
20758
- const nameStr = isFinal ? import_chalk26.default.white.bold(name) : import_chalk26.default.white(name);
20759
- const detail = isFinal ? import_chalk26.default.white(step.detail) : import_chalk26.default.gray(step.detail);
20760
- const arrow = isFinal ? import_chalk26.default.yellow(" \u2190 STOP") : "";
21092
+ const nameStr = isFinal ? import_chalk27.default.white.bold(name) : import_chalk27.default.white(name);
21093
+ const detail = isFinal ? import_chalk27.default.white(step.detail) : import_chalk27.default.gray(step.detail);
21094
+ const arrow = isFinal ? import_chalk27.default.yellow(" \u2190 STOP") : "";
20761
21095
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
20762
21096
  }
20763
21097
  console.log("");
20764
21098
  if (result.decision === "allow") {
20765
- console.log(import_chalk26.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk26.default.gray(" \u2014 no approval needed"));
21099
+ console.log(import_chalk27.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk27.default.gray(" \u2014 no approval needed"));
20766
21100
  } else {
20767
21101
  console.log(
20768
- import_chalk26.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk26.default.gray(" \u2014 human approval required")
21102
+ import_chalk27.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk27.default.gray(" \u2014 human approval required")
20769
21103
  );
20770
21104
  if (result.blockedByLabel) {
20771
- console.log(import_chalk26.default.gray(` Reason: ${result.blockedByLabel}`));
21105
+ console.log(import_chalk27.default.gray(` Reason: ${result.blockedByLabel}`));
20772
21106
  }
20773
21107
  }
20774
21108
  console.log("");
@@ -20783,7 +21117,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
20783
21117
  try {
20784
21118
  await startTail2(options);
20785
21119
  } catch (err2) {
20786
- console.error(import_chalk26.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
21120
+ console.error(import_chalk27.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
20787
21121
  process.exit(1);
20788
21122
  }
20789
21123
  });
@@ -20816,14 +21150,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
20816
21150
  Run "node9 addto claude" to register it as the statusLine.`
20817
21151
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
20818
21152
  if (subcommand === "debug") {
20819
- const flagFile = import_path44.default.join(import_os37.default.homedir(), ".node9", "hud-debug");
21153
+ const flagFile = import_path45.default.join(import_os38.default.homedir(), ".node9", "hud-debug");
20820
21154
  if (state === "on") {
20821
- import_fs41.default.mkdirSync(import_path44.default.dirname(flagFile), { recursive: true });
20822
- import_fs41.default.writeFileSync(flagFile, "");
21155
+ import_fs42.default.mkdirSync(import_path45.default.dirname(flagFile), { recursive: true });
21156
+ import_fs42.default.writeFileSync(flagFile, "");
20823
21157
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
20824
21158
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
20825
21159
  } else if (state === "off") {
20826
- if (import_fs41.default.existsSync(flagFile)) import_fs41.default.unlinkSync(flagFile);
21160
+ if (import_fs42.default.existsSync(flagFile)) import_fs42.default.unlinkSync(flagFile);
20827
21161
  console.log("HUD debug logging disabled.");
20828
21162
  } else {
20829
21163
  console.error("Usage: node9 hud debug on|off");
@@ -20838,7 +21172,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20838
21172
  const ms = parseDuration(options.duration);
20839
21173
  if (ms === null) {
20840
21174
  console.error(
20841
- import_chalk26.default.red(`
21175
+ import_chalk27.default.red(`
20842
21176
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
20843
21177
  `)
20844
21178
  );
@@ -20846,20 +21180,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20846
21180
  }
20847
21181
  pauseNode9(ms, options.duration);
20848
21182
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
20849
- console.log(import_chalk26.default.yellow(`
21183
+ console.log(import_chalk27.default.yellow(`
20850
21184
  \u23F8 Node9 paused until ${expiresAt}`));
20851
- console.log(import_chalk26.default.gray(` All tool calls will be allowed without review.`));
20852
- console.log(import_chalk26.default.gray(` Run "node9 resume" to re-enable early.
21185
+ console.log(import_chalk27.default.gray(` All tool calls will be allowed without review.`));
21186
+ console.log(import_chalk27.default.gray(` Run "node9 resume" to re-enable early.
20853
21187
  `));
20854
21188
  });
20855
21189
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
20856
21190
  const { paused } = checkPause();
20857
21191
  if (!paused) {
20858
- console.log(import_chalk26.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
21192
+ console.log(import_chalk27.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
20859
21193
  return;
20860
21194
  }
20861
21195
  resumeNode9();
20862
- console.log(import_chalk26.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
21196
+ console.log(import_chalk27.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
20863
21197
  });
20864
21198
  var HOOK_BASED_AGENTS = {
20865
21199
  claude: "claude",
@@ -20872,15 +21206,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20872
21206
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
20873
21207
  const target = HOOK_BASED_AGENTS[firstArg2];
20874
21208
  console.error(
20875
- import_chalk26.default.yellow(`
21209
+ import_chalk27.default.yellow(`
20876
21210
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
20877
21211
  );
20878
- console.error(import_chalk26.default.white(`
21212
+ console.error(import_chalk27.default.white(`
20879
21213
  "${target}" uses its own hook system. Use:`));
20880
21214
  console.error(
20881
- import_chalk26.default.green(` node9 addto ${target} `) + import_chalk26.default.gray("# one-time setup")
21215
+ import_chalk27.default.green(` node9 addto ${target} `) + import_chalk27.default.gray("# one-time setup")
20882
21216
  );
20883
- console.error(import_chalk26.default.green(` ${target} `) + import_chalk26.default.gray("# run normally"));
21217
+ console.error(import_chalk27.default.green(` ${target} `) + import_chalk27.default.gray("# run normally"));
20884
21218
  process.exit(1);
20885
21219
  }
20886
21220
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -20897,7 +21231,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20897
21231
  }
20898
21232
  );
20899
21233
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
20900
- console.error(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
21234
+ console.error(import_chalk27.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
20901
21235
  const daemonReady = await autoStartDaemonAndWait();
20902
21236
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
20903
21237
  }
@@ -20910,12 +21244,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20910
21244
  }
20911
21245
  if (!result.approved) {
20912
21246
  console.error(
20913
- import_chalk26.default.red(`
21247
+ import_chalk27.default.red(`
20914
21248
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
20915
21249
  );
20916
21250
  process.exit(1);
20917
21251
  }
20918
- console.error(import_chalk26.default.green("\n\u2705 Approved \u2014 running command...\n"));
21252
+ console.error(import_chalk27.default.green("\n\u2705 Approved \u2014 running command...\n"));
20919
21253
  await runProxy(fullCommand);
20920
21254
  } else {
20921
21255
  program.help();
@@ -20930,14 +21264,15 @@ registerAgentsCommand(program);
20930
21264
  registerScanCommand(program);
20931
21265
  registerSessionsCommand(program);
20932
21266
  registerDlpCommand(program);
21267
+ registerMaskCommand(program);
20933
21268
  if (process.argv[2] !== "daemon") {
20934
21269
  process.on("unhandledRejection", (reason) => {
20935
21270
  const isCheckHook = process.argv[2] === "check";
20936
21271
  if (isCheckHook) {
20937
21272
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
20938
- const logPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "hook-debug.log");
21273
+ const logPath = import_path45.default.join(import_os38.default.homedir(), ".node9", "hook-debug.log");
20939
21274
  const msg = reason instanceof Error ? reason.message : String(reason);
20940
- import_fs41.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
21275
+ import_fs42.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
20941
21276
  `);
20942
21277
  }
20943
21278
  process.exit(0);