@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.mjs CHANGED
@@ -147,8 +147,8 @@ function sanitizeConfig(raw) {
147
147
  }
148
148
  }
149
149
  const lines = result.error.issues.map((issue) => {
150
- const path45 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
- return ` \u2022 ${path45}: ${issue.message}`;
150
+ const path46 = issue.path.length > 0 ? issue.path.join(".") : "root";
151
+ return ` \u2022 ${path46}: ${issue.message}`;
152
152
  });
153
153
  return {
154
154
  sanitized,
@@ -1200,7 +1200,26 @@ function scanText(text) {
1200
1200
  }
1201
1201
  return null;
1202
1202
  }
1203
- var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
1203
+ function redactText(text) {
1204
+ const t = text.length > MAX_STRING_BYTES ? text.slice(0, MAX_STRING_BYTES) : text;
1205
+ let result = t;
1206
+ const found = [];
1207
+ const lower = t.toLowerCase();
1208
+ for (const { pattern, globalRegex } of DLP_PATTERNS_GLOBAL) {
1209
+ if (pattern.keywords && !pattern.keywords.some((kw) => lower.includes(kw.toLowerCase()))) {
1210
+ continue;
1211
+ }
1212
+ result = result.replace(globalRegex, (match) => {
1213
+ if (DLP_STOPWORDS.some((sw) => match.toLowerCase().includes(sw))) return match;
1214
+ if (pattern.minEntropy !== void 0 && shannonEntropy(match) < pattern.minEntropy)
1215
+ return match;
1216
+ if (!found.includes(pattern.name)) found.push(pattern.name);
1217
+ return `[node9-redacted:${pattern.name}]`;
1218
+ });
1219
+ }
1220
+ return { result, found };
1221
+ }
1222
+ var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
1204
1223
  var init_dlp = __esm({
1205
1224
  "src/dlp.ts"() {
1206
1225
  "use strict";
@@ -1244,7 +1263,8 @@ var init_dlp = __esm({
1244
1263
  name: "GitHub Token",
1245
1264
  regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/,
1246
1265
  severity: "block",
1247
- keywords: ["ghp_", "gho_", "ghu_", "ghs_"]
1266
+ keywords: ["ghp_", "gho_", "ghu_", "ghs_"],
1267
+ minEntropy: 3
1248
1268
  },
1249
1269
  {
1250
1270
  name: "GitHub Fine-Grained PAT",
@@ -1295,7 +1315,8 @@ var init_dlp = __esm({
1295
1315
  name: "GCP API Key",
1296
1316
  regex: /\bAIza[0-9A-Za-z_-]{35}\b/,
1297
1317
  severity: "block",
1298
- keywords: ["aiza"]
1318
+ keywords: ["aiza"],
1319
+ minEntropy: 3
1299
1320
  },
1300
1321
  {
1301
1322
  name: "GCP Service Account",
@@ -1542,7 +1563,8 @@ var init_dlp = __esm({
1542
1563
  name: "Mapbox Access Token",
1543
1564
  regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
1544
1565
  severity: "block",
1545
- keywords: ["pk.eyj1"]
1566
+ keywords: ["pk.eyj1"],
1567
+ minEntropy: 3
1546
1568
  },
1547
1569
  // ── Notion ────────────────────────────────────────────────────────────────
1548
1570
  {
@@ -1615,6 +1637,15 @@ var init_dlp = __esm({
1615
1637
  keywords: ["age-secret-key-"]
1616
1638
  }
1617
1639
  ];
1640
+ DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
1641
+ (p) => ({
1642
+ pattern: p,
1643
+ globalRegex: new RegExp(
1644
+ p.regex.source,
1645
+ p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g"
1646
+ )
1647
+ })
1648
+ );
1618
1649
  SENSITIVE_PATH_PATTERNS = [
1619
1650
  /[/\\]\.ssh[/\\]/i,
1620
1651
  /[/\\]\.aws[/\\]/i,
@@ -2181,9 +2212,9 @@ function matchesPattern(text, patterns) {
2181
2212
  const withoutDotSlash = text.replace(/^\.\//, "");
2182
2213
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
2183
2214
  }
2184
- function getNestedValue(obj, path45) {
2215
+ function getNestedValue(obj, path46) {
2185
2216
  if (!obj || typeof obj !== "object") return null;
2186
- return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
2217
+ return path46.split(".").reduce((prev, curr) => prev?.[curr], obj);
2187
2218
  }
2188
2219
  function normalizeCommandForPolicy(command) {
2189
2220
  try {
@@ -9150,14 +9181,30 @@ var init_ui = __esm({
9150
9181
  // \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
9151
9182
  if (leaksByPattern.length) {
9152
9183
  html +=
9153
- '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in tool calls</div>';
9184
+ '<div class="scan-rule-section-label" style="color:#e5534b">\u{1F511} Credential Leaks \u2014 secrets found in history or shell config</div>';
9154
9185
  html += leaksByPattern
9155
9186
  .map(([pattern, group]) => {
9156
9187
  const count = group.length;
9157
9188
  const barPct = Math.round((count / maxBar) * 100);
9158
9189
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
9159
9190
  const rows = group
9160
- .map((l) => findingRow(l.timestamp, esc(l.redactedSample || '')))
9191
+ .map((l) => {
9192
+ const agentBadge =
9193
+ l.agent === 'gemini'
9194
+ ? '[Gemini]'
9195
+ : l.agent === 'codex'
9196
+ ? '[Codex]'
9197
+ : l.agent === 'shell'
9198
+ ? '[Shell]'
9199
+ : '[Claude]';
9200
+ return findingRow(
9201
+ l.timestamp,
9202
+ '<span style="opacity:0.6;margin-right:6px">' +
9203
+ esc(agentBadge) +
9204
+ '</span>' +
9205
+ esc(l.redactedSample || '')
9206
+ );
9207
+ })
9161
9208
  .join('');
9162
9209
  return (
9163
9210
  '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
@@ -9909,6 +9956,7 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9909
9956
  continue;
9910
9957
  }
9911
9958
  const sessionCalls = [];
9959
+ const toolUseFilePaths = /* @__PURE__ */ new Map();
9912
9960
  for (const line of raw.split("\n")) {
9913
9961
  if (!line.trim()) continue;
9914
9962
  onLine?.();
@@ -9951,6 +9999,33 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9951
9999
  }
9952
10000
  }
9953
10001
  }
10002
+ for (const block of content2) {
10003
+ if (block.type !== "tool_result") continue;
10004
+ const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
10005
+ if (filePath) {
10006
+ const ext = path17.extname(filePath).toLowerCase();
10007
+ if (CODE_EXTENSIONS.has(ext)) continue;
10008
+ }
10009
+ const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
10010
+ if (!resultText) continue;
10011
+ const dlpMatch = scanArgs({ text: resultText });
10012
+ if (dlpMatch) {
10013
+ const isDupe = result.dlpFindings.some(
10014
+ (f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
10015
+ );
10016
+ if (!isDupe) {
10017
+ result.dlpFindings.push({
10018
+ patternName: dlpMatch.patternName,
10019
+ redactedSample: dlpMatch.redactedSample,
10020
+ toolName: "tool-result",
10021
+ timestamp: entry.timestamp ?? "",
10022
+ project: projLabel,
10023
+ sessionId,
10024
+ agent: "claude"
10025
+ });
10026
+ }
10027
+ }
10028
+ }
9954
10029
  }
9955
10030
  continue;
9956
10031
  }
@@ -9970,6 +10045,9 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9970
10045
  const toolName = block.name ?? "";
9971
10046
  const toolNameLower = toolName.toLowerCase();
9972
10047
  const input = block.input ?? {};
10048
+ if (block.id && typeof input.file_path === "string") {
10049
+ toolUseFilePaths.set(block.id, input.file_path);
10050
+ }
9973
10051
  sessionCalls.push({ toolName, input, timestamp: entry.timestamp ?? "" });
9974
10052
  if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
9975
10053
  result.bashCalls++;
@@ -9977,6 +10055,9 @@ function scanClaudeHistory(startDate, onProgress, onLine) {
9977
10055
  const rawCmd = String(input.command ?? "").trimStart();
9978
10056
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
9979
10057
  continue;
10058
+ const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
10059
+ const inputFileExt = inputFilePath ? path17.extname(inputFilePath).toLowerCase() : "";
10060
+ if (CODE_EXTENSIONS.has(inputFileExt)) continue;
9980
10061
  const dlpMatch = scanArgs(input);
9981
10062
  if (dlpMatch) {
9982
10063
  const isDupe = result.dlpFindings.some(
@@ -10473,6 +10554,44 @@ function scanCodexHistory(startDate, onProgress, onLine) {
10473
10554
  }
10474
10555
  return result;
10475
10556
  }
10557
+ function scanShellConfig() {
10558
+ const home = os12.homedir();
10559
+ const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
10560
+ (f) => path17.join(home, f)
10561
+ );
10562
+ const findings = [];
10563
+ for (const filePath of configFiles) {
10564
+ if (!fs14.existsSync(filePath)) continue;
10565
+ let lines;
10566
+ try {
10567
+ lines = fs14.readFileSync(filePath, "utf-8").split("\n");
10568
+ } catch {
10569
+ continue;
10570
+ }
10571
+ const shortPath = filePath.replace(home, "~");
10572
+ for (const line of lines) {
10573
+ const trimmed = line.trim();
10574
+ if (!trimmed || trimmed.startsWith("#")) continue;
10575
+ const dlpMatch = scanArgs({ text: trimmed });
10576
+ if (!dlpMatch) continue;
10577
+ const isDupe = findings.some(
10578
+ (f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === shortPath
10579
+ );
10580
+ if (!isDupe) {
10581
+ findings.push({
10582
+ patternName: dlpMatch.patternName,
10583
+ redactedSample: dlpMatch.redactedSample,
10584
+ toolName: "shell-config",
10585
+ timestamp: "",
10586
+ project: shortPath,
10587
+ sessionId: "",
10588
+ agent: "shell"
10589
+ });
10590
+ }
10591
+ }
10592
+ }
10593
+ return findings;
10594
+ }
10476
10595
  function mergeScans(a, b) {
10477
10596
  const dates = [a.firstDate, b.firstDate].filter(Boolean);
10478
10597
  const lastDates = [a.lastDate, b.lastDate].filter(Boolean);
@@ -10587,6 +10706,7 @@ function registerScanCommand(program2) {
10587
10706
  onLine
10588
10707
  );
10589
10708
  const scan = mergeScans(mergeScans(claudeScan, geminiScan), codexScan);
10709
+ scan.dlpFindings.push(...scanShellConfig());
10590
10710
  const summary = buildScanSummary([
10591
10711
  { id: "claude", label: "Claude", icon: "\u{1F916}", scan: claudeScan },
10592
10712
  { id: "gemini", label: "Gemini", icon: "\u264A", scan: geminiScan },
@@ -10640,7 +10760,7 @@ function registerScanCommand(program2) {
10640
10760
  }
10641
10761
  if (scan.dlpFindings.length > 0) {
10642
10762
  console.log(
10643
- " " + chalk2.red("\u{1F511} Credential leak") + " " + chalk2.red.bold(String(scan.dlpFindings.length).padStart(5)) + chalk2.dim(" secret detected in tool call")
10763
+ " " + chalk2.red("\u{1F511} Credential leak") + " " + chalk2.red.bold(String(scan.dlpFindings.length).padStart(5)) + chalk2.dim(" secret detected in history or shell config")
10644
10764
  );
10645
10765
  }
10646
10766
  if (blockedCount > 0) {
@@ -10649,8 +10769,9 @@ function registerScanCommand(program2) {
10649
10769
  );
10650
10770
  }
10651
10771
  if (scan.loopFindings.length > 0) {
10772
+ const loopCost = summary.loopWastedUSD > 0 ? chalk2.dim(" \xB7 ") + chalk2.yellow("~" + fmtCost(summary.loopWastedUSD) + " wasted") : "";
10652
10773
  console.log(
10653
- " " + chalk2.yellow("\u{1F501} Loop detected") + " " + chalk2.yellow.bold(String(scan.loopFindings.length).padStart(5)) + chalk2.dim(" repeated tool call patterns found")
10774
+ " " + chalk2.yellow("\u{1F501} Loop detected") + " " + chalk2.yellow.bold(String(scan.loopFindings.length).padStart(5)) + chalk2.dim(" repeated tool call patterns found") + loopCost
10654
10775
  );
10655
10776
  }
10656
10777
  if (reviewCount > 0) {
@@ -10670,7 +10791,7 @@ function registerScanCommand(program2) {
10670
10791
  for (const f of shownDlp) {
10671
10792
  const ts = f.timestamp ? chalk2.dim(fmtTs(f.timestamp) + " ") : "";
10672
10793
  const proj = chalk2.dim(f.project.slice(0, 22).padEnd(22) + " ");
10673
- const agentBadge = f.agent === "gemini" ? chalk2.blue("[Gemini] ") : f.agent === "codex" ? chalk2.magenta("[Codex] ") : chalk2.cyan("[Claude] ");
10794
+ const agentBadge = f.agent === "gemini" ? chalk2.blue("[Gemini] ") : f.agent === "codex" ? chalk2.magenta("[Codex] ") : f.agent === "shell" ? chalk2.yellow("[Shell] ") : chalk2.cyan("[Claude] ");
10674
10795
  const sessionSuffix = f.sessionId ? chalk2.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
10675
10796
  console.log(
10676
10797
  ` \u{1F6A8} ${ts}${proj}${agentBadge}` + chalk2.yellow(f.patternName) + chalk2.dim(" ") + chalk2.gray(f.redactedSample) + sessionSuffix
@@ -10702,10 +10823,11 @@ function registerScanCommand(program2) {
10702
10823
  }
10703
10824
  if (scan.loopFindings.length > 0) {
10704
10825
  console.log(" " + chalk2.dim("\u2500".repeat(70)));
10826
+ const loopCostLabel = summary.loopWastedUSD > 0 ? chalk2.dim(" \xB7 ") + chalk2.yellow("~" + fmtCost(summary.loopWastedUSD) + " wasted") : "";
10705
10827
  console.log(
10706
10828
  " " + chalk2.yellow.bold("\u{1F501} Agent Loops") + chalk2.dim(" \xB7 ") + chalk2.yellow(
10707
10829
  `${num(scan.loopFindings.length)} repeated pattern${scan.loopFindings.length !== 1 ? "s" : ""} found`
10708
- )
10830
+ ) + loopCostLabel
10709
10831
  );
10710
10832
  const shownLoops = drillDown ? scan.loopFindings : scan.loopFindings.slice(0, topN);
10711
10833
  for (const f of shownLoops) {
@@ -10828,7 +10950,7 @@ function registerScanCommand(program2) {
10828
10950
  }
10829
10951
  });
10830
10952
  }
10831
- var CLAUDE_PRICING, GEMINI_PRICING, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
10953
+ var CLAUDE_PRICING, GEMINI_PRICING, CODE_EXTENSIONS, LOOP_TOOLS, LOOP_THRESHOLD, DEFAULT_RULE_NAMES;
10832
10954
  var init_scan = __esm({
10833
10955
  "src/cli/commands/scan.ts"() {
10834
10956
  "use strict";
@@ -10860,6 +10982,33 @@ var init_scan = __esm({
10860
10982
  "gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
10861
10983
  "gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
10862
10984
  };
10985
+ CODE_EXTENSIONS = /* @__PURE__ */ new Set([
10986
+ ".ts",
10987
+ ".tsx",
10988
+ ".js",
10989
+ ".jsx",
10990
+ ".mjs",
10991
+ ".cjs",
10992
+ ".py",
10993
+ ".rb",
10994
+ ".go",
10995
+ ".rs",
10996
+ ".java",
10997
+ ".kt",
10998
+ ".swift",
10999
+ ".c",
11000
+ ".cpp",
11001
+ ".h",
11002
+ ".cs",
11003
+ ".php",
11004
+ ".sh",
11005
+ ".bash",
11006
+ ".html",
11007
+ ".css",
11008
+ ".scss",
11009
+ ".vue",
11010
+ ".svelte"
11011
+ ]);
10863
11012
  LOOP_TOOLS = /* @__PURE__ */ new Set([
10864
11013
  "bash",
10865
11014
  "execute_bash",
@@ -13550,10 +13699,10 @@ __export(tail_exports, {
13550
13699
  startTail: () => startTail
13551
13700
  });
13552
13701
  import http2 from "http";
13553
- import chalk25 from "chalk";
13554
- import fs39 from "fs";
13555
- import os35 from "os";
13556
- import path42 from "path";
13702
+ import chalk26 from "chalk";
13703
+ import fs40 from "fs";
13704
+ import os36 from "os";
13705
+ import path43 from "path";
13557
13706
  import readline5 from "readline";
13558
13707
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
13559
13708
  function getIcon(tool) {
@@ -13571,20 +13720,20 @@ function getModelContextLimit(model) {
13571
13720
  return 2e5;
13572
13721
  }
13573
13722
  function readSessionUsage() {
13574
- const projectsDir = path42.join(os35.homedir(), ".claude", "projects");
13575
- if (!fs39.existsSync(projectsDir)) return null;
13723
+ const projectsDir = path43.join(os36.homedir(), ".claude", "projects");
13724
+ if (!fs40.existsSync(projectsDir)) return null;
13576
13725
  let latestFile = null;
13577
13726
  let latestMtime = 0;
13578
13727
  try {
13579
- for (const dir of fs39.readdirSync(projectsDir)) {
13580
- const dirPath = path42.join(projectsDir, dir);
13728
+ for (const dir of fs40.readdirSync(projectsDir)) {
13729
+ const dirPath = path43.join(projectsDir, dir);
13581
13730
  try {
13582
- if (!fs39.statSync(dirPath).isDirectory()) continue;
13583
- for (const file of fs39.readdirSync(dirPath)) {
13731
+ if (!fs40.statSync(dirPath).isDirectory()) continue;
13732
+ for (const file of fs40.readdirSync(dirPath)) {
13584
13733
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
13585
- const filePath = path42.join(dirPath, file);
13734
+ const filePath = path43.join(dirPath, file);
13586
13735
  try {
13587
- const mtime = fs39.statSync(filePath).mtimeMs;
13736
+ const mtime = fs40.statSync(filePath).mtimeMs;
13588
13737
  if (mtime > latestMtime) {
13589
13738
  latestMtime = mtime;
13590
13739
  latestFile = filePath;
@@ -13599,7 +13748,7 @@ function readSessionUsage() {
13599
13748
  }
13600
13749
  if (!latestFile) return null;
13601
13750
  try {
13602
- const lines = fs39.readFileSync(latestFile, "utf-8").split("\n");
13751
+ const lines = fs40.readFileSync(latestFile, "utf-8").split("\n");
13603
13752
  let lastModel = "";
13604
13753
  let lastInput = 0;
13605
13754
  let lastOutput = 0;
@@ -13624,10 +13773,10 @@ function readSessionUsage() {
13624
13773
  }
13625
13774
  }
13626
13775
  function formatContextStat(stat) {
13627
- const pctColor = stat.fillPct >= 80 ? chalk25.red : stat.fillPct >= 50 ? chalk25.yellow : chalk25.cyan;
13776
+ const pctColor = stat.fillPct >= 80 ? chalk26.red : stat.fillPct >= 50 ? chalk26.yellow : chalk26.cyan;
13628
13777
  const k = (n) => `${Math.round(n / 1e3)}k`;
13629
13778
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
13630
- return chalk25.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk25.dim(
13779
+ return chalk26.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk26.dim(
13631
13780
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
13632
13781
  );
13633
13782
  }
@@ -13643,28 +13792,28 @@ function wrappedLineCount(text) {
13643
13792
  function agentLabel(agent) {
13644
13793
  if (!agent || agent === "Terminal") return "";
13645
13794
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
13646
- return short ? chalk25.dim(`[${short}] `) : "";
13795
+ return short ? chalk26.dim(`[${short}] `) : "";
13647
13796
  }
13648
13797
  function formatBase(activity) {
13649
13798
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
13650
13799
  const icon = getIcon(activity.tool);
13651
13800
  const toolName = activity.tool.slice(0, 16).padEnd(16);
13652
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os35.homedir(), "~");
13801
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os36.homedir(), "~");
13653
13802
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
13654
- return `${chalk25.gray(time)} ${icon} ${agentLabel(activity.agent)}${chalk25.white.bold(toolName)} ${chalk25.dim(argsPreview)}`;
13803
+ return `${chalk26.gray(time)} ${icon} ${agentLabel(activity.agent)}${chalk26.white.bold(toolName)} ${chalk26.dim(argsPreview)}`;
13655
13804
  }
13656
13805
  function renderResult(activity, result) {
13657
13806
  const base = formatBase(activity);
13658
13807
  let status;
13659
13808
  if (result.status === "allow") {
13660
- status = chalk25.green("\u2713 ALLOW");
13809
+ status = chalk26.green("\u2713 ALLOW");
13661
13810
  } else if (result.status === "dlp") {
13662
- status = chalk25.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13811
+ status = chalk26.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13663
13812
  } else {
13664
- status = chalk25.red("\u2717 BLOCK");
13813
+ status = chalk26.red("\u2717 BLOCK");
13665
13814
  }
13666
13815
  const cost = result.costEstimate ?? activity.costEstimate;
13667
- const costSuffix = cost == null ? "" : chalk25.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
13816
+ const costSuffix = cost == null ? "" : chalk26.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
13668
13817
  if (process.stdout.isTTY) {
13669
13818
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
13670
13819
  readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -13681,19 +13830,19 @@ function renderResult(activity, result) {
13681
13830
  }
13682
13831
  function renderPending(activity) {
13683
13832
  if (!process.stdout.isTTY) return;
13684
- const line = `${formatBase(activity)} ${chalk25.yellow("\u25CF \u2026")}`;
13833
+ const line = `${formatBase(activity)} ${chalk26.yellow("\u25CF \u2026")}`;
13685
13834
  pendingShownForId = activity.id;
13686
13835
  pendingWrappedLines = wrappedLineCount(line);
13687
13836
  process.stdout.write(`${line}\r`);
13688
13837
  }
13689
13838
  async function ensureDaemon() {
13690
13839
  let pidPort = null;
13691
- if (fs39.existsSync(PID_FILE)) {
13840
+ if (fs40.existsSync(PID_FILE)) {
13692
13841
  try {
13693
- const { port } = JSON.parse(fs39.readFileSync(PID_FILE, "utf-8"));
13842
+ const { port } = JSON.parse(fs40.readFileSync(PID_FILE, "utf-8"));
13694
13843
  pidPort = port;
13695
13844
  } catch {
13696
- console.error(chalk25.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
13845
+ console.error(chalk26.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
13697
13846
  }
13698
13847
  }
13699
13848
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -13704,7 +13853,7 @@ async function ensureDaemon() {
13704
13853
  if (res.ok) return checkPort;
13705
13854
  } catch {
13706
13855
  }
13707
- console.log(chalk25.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
13856
+ console.log(chalk26.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
13708
13857
  const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
13709
13858
  detached: true,
13710
13859
  stdio: "ignore",
@@ -13721,7 +13870,7 @@ async function ensureDaemon() {
13721
13870
  } catch {
13722
13871
  }
13723
13872
  }
13724
- console.error(chalk25.red("\u274C Daemon failed to start. Try: node9 daemon start"));
13873
+ console.error(chalk26.red("\u274C Daemon failed to start. Try: node9 daemon start"));
13725
13874
  process.exit(1);
13726
13875
  }
13727
13876
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -13787,7 +13936,7 @@ function buildCardLines(req, localCount = 0) {
13787
13936
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
13788
13937
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
13789
13938
  const description = rawDesc ? cleanReason(rawDesc) : "";
13790
- const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk25.dim(`(${req.agent})`)}` : "";
13939
+ const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk26.dim(`(${req.agent})`)}` : "";
13791
13940
  const lines = [
13792
13941
  ``,
13793
13942
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -13843,9 +13992,9 @@ function buildRecoveryCardLines(req) {
13843
13992
  ];
13844
13993
  }
13845
13994
  function readApproversFromDisk() {
13846
- const configPath = path42.join(os35.homedir(), ".node9", "config.json");
13995
+ const configPath = path43.join(os36.homedir(), ".node9", "config.json");
13847
13996
  try {
13848
- const raw = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
13997
+ const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
13849
13998
  const settings = raw.settings ?? {};
13850
13999
  return settings.approvers ?? {};
13851
14000
  } catch {
@@ -13856,20 +14005,20 @@ function approverStatusLine() {
13856
14005
  const a = readApproversFromDisk();
13857
14006
  const fmt = (label, key) => {
13858
14007
  const on = a[key] !== false;
13859
- return `[${key[0]}]${label.slice(1)} ${on ? chalk25.green("\u2713") : chalk25.dim("\u2717")}`;
14008
+ return `[${key[0]}]${label.slice(1)} ${on ? chalk26.green("\u2713") : chalk26.dim("\u2717")}`;
13860
14009
  };
13861
14010
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
13862
14011
  }
13863
14012
  function toggleApprover(channel) {
13864
- const configPath = path42.join(os35.homedir(), ".node9", "config.json");
14013
+ const configPath = path43.join(os36.homedir(), ".node9", "config.json");
13865
14014
  try {
13866
- const raw = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
14015
+ const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
13867
14016
  const settings = raw.settings ?? {};
13868
14017
  const approvers = settings.approvers ?? {};
13869
14018
  approvers[channel] = approvers[channel] === false;
13870
14019
  settings.approvers = approvers;
13871
14020
  raw.settings = settings;
13872
- fs39.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
14021
+ fs40.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
13873
14022
  } catch (err2) {
13874
14023
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
13875
14024
  `);
@@ -13901,7 +14050,7 @@ async function startTail(options = {}) {
13901
14050
  req2.end();
13902
14051
  });
13903
14052
  if (result.ok) {
13904
- console.log(chalk25.green("\u2713 Flight Recorder buffer cleared."));
14053
+ console.log(chalk26.green("\u2713 Flight Recorder buffer cleared."));
13905
14054
  } else if (result.code === "ECONNREFUSED") {
13906
14055
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
13907
14056
  } else if (result.code === "ETIMEDOUT") {
@@ -13945,7 +14094,7 @@ async function startTail(options = {}) {
13945
14094
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
13946
14095
  if (channel) {
13947
14096
  toggleApprover(channel);
13948
- console.log(chalk25.dim(` Approvers: ${approverStatusLine()}`));
14097
+ console.log(chalk26.dim(` Approvers: ${approverStatusLine()}`));
13949
14098
  }
13950
14099
  };
13951
14100
  process.stdin.on("keypress", idleKeypressHandler);
@@ -14011,7 +14160,7 @@ async function startTail(options = {}) {
14011
14160
  localAllowCounts.get(req2.toolName) ?? 0
14012
14161
  )
14013
14162
  );
14014
- 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");
14163
+ const decisionStamp = action === "always-allow" ? chalk26.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk26.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk26.green("\u2713 ALLOWED") : action === "redirect" ? chalk26.yellow("\u21A9 REDIRECT AI") : chalk26.red("\u2717 DENIED");
14015
14164
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
14016
14165
  for (const line of stampedLines) process.stdout.write(line + "\n");
14017
14166
  process.stdout.write(SHOW_CURSOR);
@@ -14039,8 +14188,8 @@ async function startTail(options = {}) {
14039
14188
  }
14040
14189
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
14041
14190
  try {
14042
- fs39.appendFileSync(
14043
- path42.join(os35.homedir(), ".node9", "hook-debug.log"),
14191
+ fs40.appendFileSync(
14192
+ path43.join(os36.homedir(), ".node9", "hook-debug.log"),
14044
14193
  `[tail] POST /decision failed: ${String(err2)}
14045
14194
  `
14046
14195
  );
@@ -14062,7 +14211,7 @@ async function startTail(options = {}) {
14062
14211
  );
14063
14212
  const stampedLines = buildCardLines(req2, priorCount);
14064
14213
  if (externalDecision) {
14065
- const source = externalDecision === "allow" ? chalk25.green("\u2713 ALLOWED") : chalk25.red("\u2717 DENIED");
14214
+ const source = externalDecision === "allow" ? chalk26.green("\u2713 ALLOWED") : chalk26.red("\u2717 DENIED");
14066
14215
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
14067
14216
  }
14068
14217
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -14121,31 +14270,31 @@ async function startTail(options = {}) {
14121
14270
  }
14122
14271
  } catch {
14123
14272
  }
14124
- const auditLog = path42.join(os35.homedir(), ".node9", "audit.log");
14273
+ const auditLog = path43.join(os36.homedir(), ".node9", "audit.log");
14125
14274
  try {
14126
- const unackedDlp = fs39.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
14275
+ const unackedDlp = fs40.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
14127
14276
  if (unackedDlp > 0) {
14128
14277
  console.log("");
14129
14278
  console.log(
14130
- chalk25.bgRed.white.bold(
14279
+ chalk26.bgRed.white.bold(
14131
14280
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
14132
14281
  )
14133
14282
  );
14134
14283
  }
14135
14284
  } catch {
14136
14285
  }
14137
- console.log(chalk25.cyan.bold(`
14138
- \u{1F6F0}\uFE0F Node9 tail `) + chalk25.dim(`\u2192 ${dashboardUrl}`));
14286
+ console.log(chalk26.cyan.bold(`
14287
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk26.dim(`\u2192 ${dashboardUrl}`));
14139
14288
  if (canApprove) {
14140
- console.log(chalk25.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
14141
- console.log(chalk25.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
14289
+ console.log(chalk26.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
14290
+ console.log(chalk26.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
14142
14291
  }
14143
14292
  const ctxStat = readSessionUsage();
14144
14293
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
14145
14294
  if (options.history) {
14146
- console.log(chalk25.dim("Showing history + live events.\n"));
14295
+ console.log(chalk26.dim("Showing history + live events.\n"));
14147
14296
  } else {
14148
- console.log(chalk25.dim("Showing live events only. Use --history to include past.\n"));
14297
+ console.log(chalk26.dim("Showing live events only. Use --history to include past.\n"));
14149
14298
  }
14150
14299
  process.on("SIGINT", () => {
14151
14300
  exitIdleMode();
@@ -14155,13 +14304,13 @@ async function startTail(options = {}) {
14155
14304
  readline5.clearLine(process.stdout, 0);
14156
14305
  readline5.cursorTo(process.stdout, 0);
14157
14306
  }
14158
- console.log(chalk25.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14307
+ console.log(chalk26.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14159
14308
  process.exit(0);
14160
14309
  });
14161
14310
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
14162
14311
  const req = http2.get(sseUrl, (res) => {
14163
14312
  if (res.statusCode !== 200) {
14164
- console.error(chalk25.red(`Failed to connect: HTTP ${res.statusCode}`));
14313
+ console.error(chalk26.red(`Failed to connect: HTTP ${res.statusCode}`));
14165
14314
  process.exit(1);
14166
14315
  }
14167
14316
  if (canApprove) enterIdleMode();
@@ -14192,7 +14341,7 @@ async function startTail(options = {}) {
14192
14341
  readline5.clearLine(process.stdout, 0);
14193
14342
  readline5.cursorTo(process.stdout, 0);
14194
14343
  }
14195
- console.log(chalk25.red("\n\u274C Daemon disconnected."));
14344
+ console.log(chalk26.red("\n\u274C Daemon disconnected."));
14196
14345
  process.exit(1);
14197
14346
  });
14198
14347
  });
@@ -14284,9 +14433,9 @@ async function startTail(options = {}) {
14284
14433
  const hash = data.hash ?? "";
14285
14434
  const summary = data.argsSummary ?? data.tool;
14286
14435
  const fileCount = data.fileCount ?? 0;
14287
- const files = fileCount > 0 ? chalk25.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
14436
+ const files = fileCount > 0 ? chalk26.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
14288
14437
  process.stdout.write(
14289
- `${chalk25.dim(time)} ${chalk25.cyan("\u{1F4F8} snapshot")} ${chalk25.dim(hash)} ${summary}${files}
14438
+ `${chalk26.dim(time)} ${chalk26.cyan("\u{1F4F8} snapshot")} ${chalk26.dim(hash)} ${summary}${files}
14290
14439
  `
14291
14440
  );
14292
14441
  return;
@@ -14303,7 +14452,7 @@ async function startTail(options = {}) {
14303
14452
  }
14304
14453
  req.on("error", (err2) => {
14305
14454
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
14306
- console.error(chalk25.red(`
14455
+ console.error(chalk26.red(`
14307
14456
  \u274C ${msg}`));
14308
14457
  process.exit(1);
14309
14458
  });
@@ -14315,7 +14464,7 @@ var init_tail = __esm({
14315
14464
  init_daemon2();
14316
14465
  init_daemon();
14317
14466
  init_core();
14318
- PID_FILE = path42.join(os35.homedir(), ".node9", "daemon.pid");
14467
+ PID_FILE = path43.join(os36.homedir(), ".node9", "daemon.pid");
14319
14468
  ICONS = {
14320
14469
  bash: "\u{1F4BB}",
14321
14470
  shell: "\u{1F4BB}",
@@ -14363,9 +14512,9 @@ __export(hud_exports, {
14363
14512
  main: () => main,
14364
14513
  renderEnvironmentLine: () => renderEnvironmentLine
14365
14514
  });
14366
- import fs40 from "fs";
14367
- import path43 from "path";
14368
- import os36 from "os";
14515
+ import fs41 from "fs";
14516
+ import path44 from "path";
14517
+ import os37 from "os";
14369
14518
  import http3 from "http";
14370
14519
  async function readStdin() {
14371
14520
  const chunks = [];
@@ -14441,9 +14590,9 @@ function formatTimeLeft(resetsAt) {
14441
14590
  return ` (${m}m left)`;
14442
14591
  }
14443
14592
  function safeReadJson(filePath) {
14444
- if (!fs40.existsSync(filePath)) return null;
14593
+ if (!fs41.existsSync(filePath)) return null;
14445
14594
  try {
14446
- return JSON.parse(fs40.readFileSync(filePath, "utf-8"));
14595
+ return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
14447
14596
  } catch {
14448
14597
  return null;
14449
14598
  }
@@ -14464,12 +14613,12 @@ function countHooksInFile(filePath) {
14464
14613
  return Object.keys(cfg.hooks).length;
14465
14614
  }
14466
14615
  function countRulesInDir(rulesDir) {
14467
- if (!fs40.existsSync(rulesDir)) return 0;
14616
+ if (!fs41.existsSync(rulesDir)) return 0;
14468
14617
  let count = 0;
14469
14618
  try {
14470
- for (const entry of fs40.readdirSync(rulesDir, { withFileTypes: true })) {
14619
+ for (const entry of fs41.readdirSync(rulesDir, { withFileTypes: true })) {
14471
14620
  if (entry.isDirectory()) {
14472
- count += countRulesInDir(path43.join(rulesDir, entry.name));
14621
+ count += countRulesInDir(path44.join(rulesDir, entry.name));
14473
14622
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
14474
14623
  count++;
14475
14624
  }
@@ -14480,46 +14629,46 @@ function countRulesInDir(rulesDir) {
14480
14629
  }
14481
14630
  function isSamePath(a, b) {
14482
14631
  try {
14483
- return path43.resolve(a) === path43.resolve(b);
14632
+ return path44.resolve(a) === path44.resolve(b);
14484
14633
  } catch {
14485
14634
  return false;
14486
14635
  }
14487
14636
  }
14488
14637
  function countConfigs(cwd) {
14489
- const homeDir2 = os36.homedir();
14490
- const claudeDir = path43.join(homeDir2, ".claude");
14638
+ const homeDir2 = os37.homedir();
14639
+ const claudeDir = path44.join(homeDir2, ".claude");
14491
14640
  let claudeMdCount = 0;
14492
14641
  let rulesCount = 0;
14493
14642
  let hooksCount = 0;
14494
14643
  const userMcpServers = /* @__PURE__ */ new Set();
14495
14644
  const projectMcpServers = /* @__PURE__ */ new Set();
14496
- if (fs40.existsSync(path43.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
14497
- rulesCount += countRulesInDir(path43.join(claudeDir, "rules"));
14498
- const userSettings = path43.join(claudeDir, "settings.json");
14645
+ if (fs41.existsSync(path44.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
14646
+ rulesCount += countRulesInDir(path44.join(claudeDir, "rules"));
14647
+ const userSettings = path44.join(claudeDir, "settings.json");
14499
14648
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
14500
14649
  hooksCount += countHooksInFile(userSettings);
14501
- const userClaudeJson = path43.join(homeDir2, ".claude.json");
14650
+ const userClaudeJson = path44.join(homeDir2, ".claude.json");
14502
14651
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
14503
14652
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
14504
14653
  userMcpServers.delete(name);
14505
14654
  }
14506
14655
  if (cwd) {
14507
- if (fs40.existsSync(path43.join(cwd, "CLAUDE.md"))) claudeMdCount++;
14508
- if (fs40.existsSync(path43.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
14509
- const projectClaudeDir = path43.join(cwd, ".claude");
14656
+ if (fs41.existsSync(path44.join(cwd, "CLAUDE.md"))) claudeMdCount++;
14657
+ if (fs41.existsSync(path44.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
14658
+ const projectClaudeDir = path44.join(cwd, ".claude");
14510
14659
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
14511
14660
  if (!overlapsUserScope) {
14512
- if (fs40.existsSync(path43.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
14513
- rulesCount += countRulesInDir(path43.join(projectClaudeDir, "rules"));
14514
- const projSettings = path43.join(projectClaudeDir, "settings.json");
14661
+ if (fs41.existsSync(path44.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
14662
+ rulesCount += countRulesInDir(path44.join(projectClaudeDir, "rules"));
14663
+ const projSettings = path44.join(projectClaudeDir, "settings.json");
14515
14664
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
14516
14665
  hooksCount += countHooksInFile(projSettings);
14517
14666
  }
14518
- if (fs40.existsSync(path43.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
14519
- const localSettings = path43.join(projectClaudeDir, "settings.local.json");
14667
+ if (fs41.existsSync(path44.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
14668
+ const localSettings = path44.join(projectClaudeDir, "settings.local.json");
14520
14669
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
14521
14670
  hooksCount += countHooksInFile(localSettings);
14522
- const mcpJsonServers = getMcpServerNames(path43.join(cwd, ".mcp.json"));
14671
+ const mcpJsonServers = getMcpServerNames(path44.join(cwd, ".mcp.json"));
14523
14672
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
14524
14673
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
14525
14674
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -14552,12 +14701,12 @@ function readActiveShieldsHud() {
14552
14701
  return shieldsCache.value;
14553
14702
  }
14554
14703
  try {
14555
- const shieldsPath = path43.join(os36.homedir(), ".node9", "shields.json");
14556
- if (!fs40.existsSync(shieldsPath)) {
14704
+ const shieldsPath = path44.join(os37.homedir(), ".node9", "shields.json");
14705
+ if (!fs41.existsSync(shieldsPath)) {
14557
14706
  shieldsCache = { value: [], ts: now };
14558
14707
  return [];
14559
14708
  }
14560
- const parsed = JSON.parse(fs40.readFileSync(shieldsPath, "utf-8"));
14709
+ const parsed = JSON.parse(fs41.readFileSync(shieldsPath, "utf-8"));
14561
14710
  if (!Array.isArray(parsed.active)) {
14562
14711
  shieldsCache = { value: [], ts: now };
14563
14712
  return [];
@@ -14659,17 +14808,17 @@ function renderContextLine(stdin) {
14659
14808
  async function main() {
14660
14809
  try {
14661
14810
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
14662
- if (fs40.existsSync(path43.join(os36.homedir(), ".node9", "hud-debug"))) {
14811
+ if (fs41.existsSync(path44.join(os37.homedir(), ".node9", "hud-debug"))) {
14663
14812
  try {
14664
- const logPath = path43.join(os36.homedir(), ".node9", "hud-debug.log");
14813
+ const logPath = path44.join(os37.homedir(), ".node9", "hud-debug.log");
14665
14814
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
14666
14815
  let size = 0;
14667
14816
  try {
14668
- size = fs40.statSync(logPath).size;
14817
+ size = fs41.statSync(logPath).size;
14669
14818
  } catch {
14670
14819
  }
14671
14820
  if (size < MAX_LOG_SIZE) {
14672
- fs40.appendFileSync(
14821
+ fs41.appendFileSync(
14673
14822
  logPath,
14674
14823
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
14675
14824
  );
@@ -14690,11 +14839,11 @@ async function main() {
14690
14839
  try {
14691
14840
  const cwd = stdin.cwd ?? process.cwd();
14692
14841
  for (const configPath of [
14693
- path43.join(cwd, "node9.config.json"),
14694
- path43.join(os36.homedir(), ".node9", "config.json")
14842
+ path44.join(cwd, "node9.config.json"),
14843
+ path44.join(os37.homedir(), ".node9", "config.json")
14695
14844
  ]) {
14696
- if (!fs40.existsSync(configPath)) continue;
14697
- const cfg = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
14845
+ if (!fs41.existsSync(configPath)) continue;
14846
+ const cfg = JSON.parse(fs41.readFileSync(configPath, "utf-8"));
14698
14847
  const hud = cfg.settings?.hud;
14699
14848
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
14700
14849
  }
@@ -14740,10 +14889,10 @@ init_core();
14740
14889
  init_setup();
14741
14890
  init_daemon2();
14742
14891
  import { Command } from "commander";
14743
- import chalk26 from "chalk";
14744
- import fs41 from "fs";
14745
- import path44 from "path";
14746
- import os37 from "os";
14892
+ import chalk27 from "chalk";
14893
+ import fs42 from "fs";
14894
+ import path45 from "path";
14895
+ import os38 from "os";
14747
14896
  import { confirm as confirm2 } from "@inquirer/prompts";
14748
14897
 
14749
14898
  // src/utils/duration.ts
@@ -20453,22 +20602,207 @@ function registerDlpCommand(program2) {
20453
20602
  });
20454
20603
  }
20455
20604
 
20605
+ // src/cli/commands/mask.ts
20606
+ init_dlp();
20607
+ import chalk25 from "chalk";
20608
+ import fs39 from "fs";
20609
+ import path42 from "path";
20610
+ import os35 from "os";
20611
+ function findJsonlFiles(dir) {
20612
+ const results = [];
20613
+ if (!fs39.existsSync(dir)) return results;
20614
+ for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
20615
+ const full = path42.join(dir, entry.name);
20616
+ if (entry.isDirectory()) results.push(...findJsonlFiles(full));
20617
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
20618
+ }
20619
+ return results;
20620
+ }
20621
+ function redactJson(obj) {
20622
+ if (typeof obj === "string") {
20623
+ const { result, found } = redactText(obj);
20624
+ return { value: result, modified: result !== obj, found };
20625
+ }
20626
+ if (Array.isArray(obj)) {
20627
+ let modified = false;
20628
+ const found = [];
20629
+ const value = obj.map((item) => {
20630
+ const r = redactJson(item);
20631
+ if (r.modified) modified = true;
20632
+ r.found.forEach((f) => {
20633
+ if (!found.includes(f)) found.push(f);
20634
+ });
20635
+ return r.value;
20636
+ });
20637
+ return { value, modified, found };
20638
+ }
20639
+ if (obj !== null && typeof obj === "object") {
20640
+ let modified = false;
20641
+ const found = [];
20642
+ const value = {};
20643
+ for (const [k, v] of Object.entries(obj)) {
20644
+ const r = redactJson(v);
20645
+ value[k] = r.value;
20646
+ if (r.modified) modified = true;
20647
+ r.found.forEach((f) => {
20648
+ if (!found.includes(f)) found.push(f);
20649
+ });
20650
+ }
20651
+ return { value, modified, found };
20652
+ }
20653
+ return { value: obj, modified: false, found: [] };
20654
+ }
20655
+ function processFile(filePath, dryRun) {
20656
+ let raw;
20657
+ try {
20658
+ raw = fs39.readFileSync(filePath, "utf-8");
20659
+ } catch {
20660
+ return { redactedLines: 0, patterns: [] };
20661
+ }
20662
+ const lines = raw.split("\n");
20663
+ let redactedLines = 0;
20664
+ const patterns = [];
20665
+ const newLines = [];
20666
+ for (const line of lines) {
20667
+ if (!line.trim()) {
20668
+ newLines.push(line);
20669
+ continue;
20670
+ }
20671
+ let parsed;
20672
+ try {
20673
+ parsed = JSON.parse(line);
20674
+ } catch {
20675
+ newLines.push(line);
20676
+ continue;
20677
+ }
20678
+ const { value, modified, found } = redactJson(parsed);
20679
+ if (modified) {
20680
+ redactedLines++;
20681
+ found.forEach((f) => {
20682
+ if (!patterns.includes(f)) patterns.push(f);
20683
+ });
20684
+ newLines.push(JSON.stringify(value));
20685
+ } else {
20686
+ newLines.push(line);
20687
+ }
20688
+ }
20689
+ if (!dryRun && redactedLines > 0) {
20690
+ fs39.writeFileSync(filePath, newLines.join("\n"), "utf-8");
20691
+ }
20692
+ return { redactedLines, patterns };
20693
+ }
20694
+ function processJsonFile(filePath, dryRun) {
20695
+ let raw;
20696
+ try {
20697
+ raw = fs39.readFileSync(filePath, "utf-8");
20698
+ } catch {
20699
+ return { redactedLines: 0, patterns: [] };
20700
+ }
20701
+ let parsed;
20702
+ try {
20703
+ parsed = JSON.parse(raw);
20704
+ } catch {
20705
+ return { redactedLines: 0, patterns: [] };
20706
+ }
20707
+ const { value, modified, found } = redactJson(parsed);
20708
+ if (!modified) return { redactedLines: 0, patterns: [] };
20709
+ if (!dryRun) {
20710
+ fs39.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
20711
+ }
20712
+ return { redactedLines: 1, patterns: found };
20713
+ }
20714
+ function findJsonFiles(dir) {
20715
+ const results = [];
20716
+ if (!fs39.existsSync(dir)) return results;
20717
+ for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
20718
+ const full = path42.join(dir, entry.name);
20719
+ if (entry.isDirectory()) results.push(...findJsonFiles(full));
20720
+ else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
20721
+ }
20722
+ return results;
20723
+ }
20724
+ function registerMaskCommand(program2) {
20725
+ 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) => {
20726
+ const dryRun = !!options.dryRun;
20727
+ const home = os35.homedir();
20728
+ const claudeDir = path42.join(home, ".claude", "projects");
20729
+ const geminiDir = path42.join(home, ".gemini", "tmp");
20730
+ const allFiles = [
20731
+ ...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
20732
+ ...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
20733
+ ];
20734
+ const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
20735
+ const filtered = cutoff ? allFiles.filter((f) => {
20736
+ try {
20737
+ return fs39.statSync(f.path).mtime >= cutoff;
20738
+ } catch {
20739
+ return false;
20740
+ }
20741
+ }) : allFiles;
20742
+ if (filtered.length === 0) {
20743
+ console.log(chalk25.yellow(" No session files found."));
20744
+ return;
20745
+ }
20746
+ console.log("");
20747
+ if (dryRun) {
20748
+ console.log(chalk25.dim(" Dry run \u2014 no files will be modified.\n"));
20749
+ }
20750
+ let totalFiles = 0;
20751
+ let totalLines = 0;
20752
+ const totalPatterns = [];
20753
+ for (const file of filtered) {
20754
+ const shortPath = file.path.replace(home, "~");
20755
+ const { redactedLines, patterns } = file.type === "jsonl" ? processFile(file.path, dryRun) : processJsonFile(file.path, dryRun);
20756
+ if (redactedLines > 0) {
20757
+ totalFiles++;
20758
+ totalLines += redactedLines;
20759
+ patterns.forEach((p) => {
20760
+ if (!totalPatterns.includes(p)) totalPatterns.push(p);
20761
+ });
20762
+ const verb = dryRun ? "Would redact" : "Redacted";
20763
+ console.log(
20764
+ " " + chalk25.dim(shortPath.slice(0, 60).padEnd(62)) + chalk25.red(`${verb}: `) + chalk25.yellow(patterns.join(", ")) + chalk25.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
20765
+ );
20766
+ }
20767
+ }
20768
+ console.log("");
20769
+ if (totalFiles === 0) {
20770
+ console.log(chalk25.green(" No secrets found in session history."));
20771
+ } else {
20772
+ const verb = dryRun ? "would be modified" : "modified";
20773
+ console.log(
20774
+ chalk25.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk25.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
20775
+ );
20776
+ console.log(" Patterns: " + chalk25.yellow(totalPatterns.join(", ")));
20777
+ if (!dryRun) {
20778
+ console.log("");
20779
+ console.log(
20780
+ chalk25.dim(
20781
+ " Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
20782
+ )
20783
+ );
20784
+ }
20785
+ }
20786
+ console.log("");
20787
+ });
20788
+ }
20789
+
20456
20790
  // src/cli.ts
20457
20791
  var { version } = JSON.parse(
20458
- fs41.readFileSync(path44.join(__dirname, "../package.json"), "utf-8")
20792
+ fs42.readFileSync(path45.join(__dirname, "../package.json"), "utf-8")
20459
20793
  );
20460
20794
  var program = new Command();
20461
20795
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
20462
20796
  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) => {
20463
20797
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
20464
- const credPath = path44.join(os37.homedir(), ".node9", "credentials.json");
20465
- if (!fs41.existsSync(path44.dirname(credPath)))
20466
- fs41.mkdirSync(path44.dirname(credPath), { recursive: true });
20798
+ const credPath = path45.join(os38.homedir(), ".node9", "credentials.json");
20799
+ if (!fs42.existsSync(path45.dirname(credPath)))
20800
+ fs42.mkdirSync(path45.dirname(credPath), { recursive: true });
20467
20801
  const profileName = options.profile || "default";
20468
20802
  let existingCreds = {};
20469
20803
  try {
20470
- if (fs41.existsSync(credPath)) {
20471
- const raw = JSON.parse(fs41.readFileSync(credPath, "utf-8"));
20804
+ if (fs42.existsSync(credPath)) {
20805
+ const raw = JSON.parse(fs42.readFileSync(credPath, "utf-8"));
20472
20806
  if (raw.apiKey) {
20473
20807
  existingCreds = {
20474
20808
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -20480,13 +20814,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20480
20814
  } catch {
20481
20815
  }
20482
20816
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
20483
- fs41.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20817
+ fs42.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20484
20818
  if (profileName === "default") {
20485
- const configPath = path44.join(os37.homedir(), ".node9", "config.json");
20819
+ const configPath = path45.join(os38.homedir(), ".node9", "config.json");
20486
20820
  let config = {};
20487
20821
  try {
20488
- if (fs41.existsSync(configPath))
20489
- config = JSON.parse(fs41.readFileSync(configPath, "utf-8"));
20822
+ if (fs42.existsSync(configPath))
20823
+ config = JSON.parse(fs42.readFileSync(configPath, "utf-8"));
20490
20824
  } catch {
20491
20825
  }
20492
20826
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -20501,19 +20835,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20501
20835
  approvers.cloud = false;
20502
20836
  }
20503
20837
  s.approvers = approvers;
20504
- if (!fs41.existsSync(path44.dirname(configPath)))
20505
- fs41.mkdirSync(path44.dirname(configPath), { recursive: true });
20506
- fs41.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
20838
+ if (!fs42.existsSync(path45.dirname(configPath)))
20839
+ fs42.mkdirSync(path45.dirname(configPath), { recursive: true });
20840
+ fs42.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
20507
20841
  }
20508
20842
  if (options.profile && profileName !== "default") {
20509
- console.log(chalk26.green(`\u2705 Profile "${profileName}" saved`));
20510
- console.log(chalk26.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
20843
+ console.log(chalk27.green(`\u2705 Profile "${profileName}" saved`));
20844
+ console.log(chalk27.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
20511
20845
  } else if (options.local) {
20512
- console.log(chalk26.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
20513
- console.log(chalk26.gray(` All decisions stay on this machine.`));
20846
+ console.log(chalk27.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
20847
+ console.log(chalk27.gray(` All decisions stay on this machine.`));
20514
20848
  } else {
20515
- console.log(chalk26.green(`\u2705 Logged in \u2014 agent mode`));
20516
- console.log(chalk26.gray(` Team policy enforced for all calls via Node9 cloud.`));
20849
+ console.log(chalk27.green(`\u2705 Logged in \u2014 agent mode`));
20850
+ console.log(chalk27.gray(` Team policy enforced for all calls via Node9 cloud.`));
20517
20851
  }
20518
20852
  });
20519
20853
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
@@ -20531,7 +20865,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
20531
20865
  if (target === "vscode") return await setupVSCode();
20532
20866
  if (target === "hud") return setupHud();
20533
20867
  console.error(
20534
- chalk26.red(
20868
+ chalk27.red(
20535
20869
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20536
20870
  )
20537
20871
  );
@@ -20545,17 +20879,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20545
20879
  "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
20546
20880
  ).action(async (target) => {
20547
20881
  if (!target) {
20548
- console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
20549
- console.log(" Usage: " + chalk26.white("node9 setup <target>") + "\n");
20882
+ console.log(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
20883
+ console.log(" Usage: " + chalk27.white("node9 setup <target>") + "\n");
20550
20884
  console.log(" Targets:");
20551
- console.log(" " + chalk26.green("claude") + " \u2014 Claude Code (hook mode)");
20552
- console.log(" " + chalk26.green("gemini") + " \u2014 Gemini CLI (hook mode)");
20553
- console.log(" " + chalk26.green("cursor") + " \u2014 Cursor (MCP proxy)");
20554
- console.log(" " + chalk26.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
20555
- console.log(" " + chalk26.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
20556
- console.log(" " + chalk26.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
20885
+ console.log(" " + chalk27.green("claude") + " \u2014 Claude Code (hook mode)");
20886
+ console.log(" " + chalk27.green("gemini") + " \u2014 Gemini CLI (hook mode)");
20887
+ console.log(" " + chalk27.green("cursor") + " \u2014 Cursor (MCP proxy)");
20888
+ console.log(" " + chalk27.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
20889
+ console.log(" " + chalk27.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
20890
+ console.log(" " + chalk27.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
20557
20891
  process.stdout.write(
20558
- " " + chalk26.green("hud") + " \u2014 Claude Code security statusline\n"
20892
+ " " + chalk27.green("hud") + " \u2014 Claude Code security statusline\n"
20559
20893
  );
20560
20894
  console.log("");
20561
20895
  return;
@@ -20569,7 +20903,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20569
20903
  if (t === "vscode") return await setupVSCode();
20570
20904
  if (t === "hud") return setupHud();
20571
20905
  console.error(
20572
- chalk26.red(
20906
+ chalk27.red(
20573
20907
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20574
20908
  )
20575
20909
  );
@@ -20592,33 +20926,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
20592
20926
  else if (target === "hud") fn = teardownHud;
20593
20927
  else {
20594
20928
  console.error(
20595
- chalk26.red(
20929
+ chalk27.red(
20596
20930
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20597
20931
  )
20598
20932
  );
20599
20933
  process.exit(1);
20600
20934
  }
20601
- console.log(chalk26.cyan(`
20935
+ console.log(chalk27.cyan(`
20602
20936
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
20603
20937
  `));
20604
20938
  try {
20605
20939
  fn();
20606
20940
  } catch (err2) {
20607
- console.error(chalk26.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
20941
+ console.error(chalk27.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
20608
20942
  process.exit(1);
20609
20943
  }
20610
- console.log(chalk26.gray("\n Restart the agent for changes to take effect."));
20944
+ console.log(chalk27.gray("\n Restart the agent for changes to take effect."));
20611
20945
  });
20612
20946
  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) => {
20613
- console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
20614
- console.log(chalk26.bold("Stopping daemon..."));
20947
+ console.log(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
20948
+ console.log(chalk27.bold("Stopping daemon..."));
20615
20949
  try {
20616
20950
  stopDaemon();
20617
- console.log(chalk26.green(" \u2705 Daemon stopped"));
20951
+ console.log(chalk27.green(" \u2705 Daemon stopped"));
20618
20952
  } catch {
20619
- console.log(chalk26.blue(" \u2139\uFE0F Daemon was not running"));
20953
+ console.log(chalk27.blue(" \u2139\uFE0F Daemon was not running"));
20620
20954
  }
20621
- console.log(chalk26.bold("\nRemoving hooks..."));
20955
+ console.log(chalk27.bold("\nRemoving hooks..."));
20622
20956
  let teardownFailed = false;
20623
20957
  for (const [label, fn] of [
20624
20958
  ["Claude", teardownClaude],
@@ -20633,45 +20967,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
20633
20967
  } catch (err2) {
20634
20968
  teardownFailed = true;
20635
20969
  console.error(
20636
- chalk26.red(
20970
+ chalk27.red(
20637
20971
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
20638
20972
  )
20639
20973
  );
20640
20974
  }
20641
20975
  }
20642
20976
  if (options.purge) {
20643
- const node9Dir = path44.join(os37.homedir(), ".node9");
20644
- if (fs41.existsSync(node9Dir)) {
20977
+ const node9Dir = path45.join(os38.homedir(), ".node9");
20978
+ if (fs42.existsSync(node9Dir)) {
20645
20979
  const confirmed = await confirm2({
20646
20980
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
20647
20981
  default: false
20648
20982
  });
20649
20983
  if (confirmed) {
20650
- fs41.rmSync(node9Dir, { recursive: true });
20651
- if (fs41.existsSync(node9Dir)) {
20984
+ fs42.rmSync(node9Dir, { recursive: true });
20985
+ if (fs42.existsSync(node9Dir)) {
20652
20986
  console.error(
20653
- chalk26.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
20987
+ chalk27.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
20654
20988
  );
20655
20989
  } else {
20656
- console.log(chalk26.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
20990
+ console.log(chalk27.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
20657
20991
  }
20658
20992
  } else {
20659
- console.log(chalk26.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
20993
+ console.log(chalk27.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
20660
20994
  }
20661
20995
  } else {
20662
- console.log(chalk26.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
20996
+ console.log(chalk27.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
20663
20997
  }
20664
20998
  } else {
20665
20999
  console.log(
20666
- chalk26.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
21000
+ chalk27.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
20667
21001
  );
20668
21002
  }
20669
21003
  if (teardownFailed) {
20670
- console.error(chalk26.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
21004
+ console.error(chalk27.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
20671
21005
  process.exit(1);
20672
21006
  }
20673
- console.log(chalk26.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
20674
- console.log(chalk26.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
21007
+ console.log(chalk27.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
21008
+ console.log(chalk27.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
20675
21009
  });
20676
21010
  registerDoctorCommand(program, version);
20677
21011
  program.command("explain").description(
@@ -20684,7 +21018,7 @@ program.command("explain").description(
20684
21018
  try {
20685
21019
  args = JSON.parse(trimmed);
20686
21020
  } catch {
20687
- console.error(chalk26.red(`
21021
+ console.error(chalk27.red(`
20688
21022
  \u274C Invalid JSON: ${trimmed}
20689
21023
  `));
20690
21024
  process.exit(1);
@@ -20695,54 +21029,54 @@ program.command("explain").description(
20695
21029
  }
20696
21030
  const result = await explainPolicy(tool, args);
20697
21031
  console.log("");
20698
- console.log(chalk26.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
21032
+ console.log(chalk27.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
20699
21033
  console.log("");
20700
- console.log(` ${chalk26.bold("Tool:")} ${chalk26.white(result.tool)}`);
21034
+ console.log(` ${chalk27.bold("Tool:")} ${chalk27.white(result.tool)}`);
20701
21035
  if (argsRaw) {
20702
21036
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
20703
- console.log(` ${chalk26.bold("Input:")} ${chalk26.gray(preview2)}`);
21037
+ console.log(` ${chalk27.bold("Input:")} ${chalk27.gray(preview2)}`);
20704
21038
  }
20705
21039
  console.log("");
20706
- console.log(chalk26.bold("Config Sources (Waterfall):"));
21040
+ console.log(chalk27.bold("Config Sources (Waterfall):"));
20707
21041
  for (const tier of result.waterfall) {
20708
- const num3 = chalk26.gray(` ${tier.tier}.`);
21042
+ const num3 = chalk27.gray(` ${tier.tier}.`);
20709
21043
  const label = tier.label.padEnd(16);
20710
21044
  let statusStr;
20711
21045
  if (tier.tier === 1) {
20712
- statusStr = chalk26.gray(tier.note ?? "");
21046
+ statusStr = chalk27.gray(tier.note ?? "");
20713
21047
  } else if (tier.status === "active") {
20714
- const loc = tier.path ? chalk26.gray(tier.path) : "";
20715
- const note = tier.note ? chalk26.gray(`(${tier.note})`) : "";
20716
- statusStr = chalk26.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
21048
+ const loc = tier.path ? chalk27.gray(tier.path) : "";
21049
+ const note = tier.note ? chalk27.gray(`(${tier.note})`) : "";
21050
+ statusStr = chalk27.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
20717
21051
  } else {
20718
- statusStr = chalk26.gray("\u25CB " + (tier.note ?? "not found"));
21052
+ statusStr = chalk27.gray("\u25CB " + (tier.note ?? "not found"));
20719
21053
  }
20720
- console.log(`${num3} ${chalk26.white(label)} ${statusStr}`);
21054
+ console.log(`${num3} ${chalk27.white(label)} ${statusStr}`);
20721
21055
  }
20722
21056
  console.log("");
20723
- console.log(chalk26.bold("Policy Evaluation:"));
21057
+ console.log(chalk27.bold("Policy Evaluation:"));
20724
21058
  for (const step of result.steps) {
20725
21059
  const isFinal = step.isFinal;
20726
21060
  let icon;
20727
- if (step.outcome === "allow") icon = chalk26.green(" \u2705");
20728
- else if (step.outcome === "review") icon = chalk26.red(" \u{1F534}");
20729
- else if (step.outcome === "skip") icon = chalk26.gray(" \u2500 ");
20730
- else icon = chalk26.gray(" \u25CB ");
21061
+ if (step.outcome === "allow") icon = chalk27.green(" \u2705");
21062
+ else if (step.outcome === "review") icon = chalk27.red(" \u{1F534}");
21063
+ else if (step.outcome === "skip") icon = chalk27.gray(" \u2500 ");
21064
+ else icon = chalk27.gray(" \u25CB ");
20731
21065
  const name = step.name.padEnd(18);
20732
- const nameStr = isFinal ? chalk26.white.bold(name) : chalk26.white(name);
20733
- const detail = isFinal ? chalk26.white(step.detail) : chalk26.gray(step.detail);
20734
- const arrow = isFinal ? chalk26.yellow(" \u2190 STOP") : "";
21066
+ const nameStr = isFinal ? chalk27.white.bold(name) : chalk27.white(name);
21067
+ const detail = isFinal ? chalk27.white(step.detail) : chalk27.gray(step.detail);
21068
+ const arrow = isFinal ? chalk27.yellow(" \u2190 STOP") : "";
20735
21069
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
20736
21070
  }
20737
21071
  console.log("");
20738
21072
  if (result.decision === "allow") {
20739
- console.log(chalk26.green.bold(" Decision: \u2705 ALLOW") + chalk26.gray(" \u2014 no approval needed"));
21073
+ console.log(chalk27.green.bold(" Decision: \u2705 ALLOW") + chalk27.gray(" \u2014 no approval needed"));
20740
21074
  } else {
20741
21075
  console.log(
20742
- chalk26.red.bold(" Decision: \u{1F534} REVIEW") + chalk26.gray(" \u2014 human approval required")
21076
+ chalk27.red.bold(" Decision: \u{1F534} REVIEW") + chalk27.gray(" \u2014 human approval required")
20743
21077
  );
20744
21078
  if (result.blockedByLabel) {
20745
- console.log(chalk26.gray(` Reason: ${result.blockedByLabel}`));
21079
+ console.log(chalk27.gray(` Reason: ${result.blockedByLabel}`));
20746
21080
  }
20747
21081
  }
20748
21082
  console.log("");
@@ -20757,7 +21091,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
20757
21091
  try {
20758
21092
  await startTail2(options);
20759
21093
  } catch (err2) {
20760
- console.error(chalk26.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
21094
+ console.error(chalk27.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
20761
21095
  process.exit(1);
20762
21096
  }
20763
21097
  });
@@ -20790,14 +21124,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
20790
21124
  Run "node9 addto claude" to register it as the statusLine.`
20791
21125
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
20792
21126
  if (subcommand === "debug") {
20793
- const flagFile = path44.join(os37.homedir(), ".node9", "hud-debug");
21127
+ const flagFile = path45.join(os38.homedir(), ".node9", "hud-debug");
20794
21128
  if (state === "on") {
20795
- fs41.mkdirSync(path44.dirname(flagFile), { recursive: true });
20796
- fs41.writeFileSync(flagFile, "");
21129
+ fs42.mkdirSync(path45.dirname(flagFile), { recursive: true });
21130
+ fs42.writeFileSync(flagFile, "");
20797
21131
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
20798
21132
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
20799
21133
  } else if (state === "off") {
20800
- if (fs41.existsSync(flagFile)) fs41.unlinkSync(flagFile);
21134
+ if (fs42.existsSync(flagFile)) fs42.unlinkSync(flagFile);
20801
21135
  console.log("HUD debug logging disabled.");
20802
21136
  } else {
20803
21137
  console.error("Usage: node9 hud debug on|off");
@@ -20812,7 +21146,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20812
21146
  const ms = parseDuration(options.duration);
20813
21147
  if (ms === null) {
20814
21148
  console.error(
20815
- chalk26.red(`
21149
+ chalk27.red(`
20816
21150
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
20817
21151
  `)
20818
21152
  );
@@ -20820,20 +21154,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20820
21154
  }
20821
21155
  pauseNode9(ms, options.duration);
20822
21156
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
20823
- console.log(chalk26.yellow(`
21157
+ console.log(chalk27.yellow(`
20824
21158
  \u23F8 Node9 paused until ${expiresAt}`));
20825
- console.log(chalk26.gray(` All tool calls will be allowed without review.`));
20826
- console.log(chalk26.gray(` Run "node9 resume" to re-enable early.
21159
+ console.log(chalk27.gray(` All tool calls will be allowed without review.`));
21160
+ console.log(chalk27.gray(` Run "node9 resume" to re-enable early.
20827
21161
  `));
20828
21162
  });
20829
21163
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
20830
21164
  const { paused } = checkPause();
20831
21165
  if (!paused) {
20832
- console.log(chalk26.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
21166
+ console.log(chalk27.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
20833
21167
  return;
20834
21168
  }
20835
21169
  resumeNode9();
20836
- console.log(chalk26.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
21170
+ console.log(chalk27.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
20837
21171
  });
20838
21172
  var HOOK_BASED_AGENTS = {
20839
21173
  claude: "claude",
@@ -20846,15 +21180,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20846
21180
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
20847
21181
  const target = HOOK_BASED_AGENTS[firstArg2];
20848
21182
  console.error(
20849
- chalk26.yellow(`
21183
+ chalk27.yellow(`
20850
21184
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
20851
21185
  );
20852
- console.error(chalk26.white(`
21186
+ console.error(chalk27.white(`
20853
21187
  "${target}" uses its own hook system. Use:`));
20854
21188
  console.error(
20855
- chalk26.green(` node9 addto ${target} `) + chalk26.gray("# one-time setup")
21189
+ chalk27.green(` node9 addto ${target} `) + chalk27.gray("# one-time setup")
20856
21190
  );
20857
- console.error(chalk26.green(` ${target} `) + chalk26.gray("# run normally"));
21191
+ console.error(chalk27.green(` ${target} `) + chalk27.gray("# run normally"));
20858
21192
  process.exit(1);
20859
21193
  }
20860
21194
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -20871,7 +21205,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20871
21205
  }
20872
21206
  );
20873
21207
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
20874
- console.error(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
21208
+ console.error(chalk27.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
20875
21209
  const daemonReady = await autoStartDaemonAndWait();
20876
21210
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
20877
21211
  }
@@ -20884,12 +21218,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20884
21218
  }
20885
21219
  if (!result.approved) {
20886
21220
  console.error(
20887
- chalk26.red(`
21221
+ chalk27.red(`
20888
21222
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
20889
21223
  );
20890
21224
  process.exit(1);
20891
21225
  }
20892
- console.error(chalk26.green("\n\u2705 Approved \u2014 running command...\n"));
21226
+ console.error(chalk27.green("\n\u2705 Approved \u2014 running command...\n"));
20893
21227
  await runProxy(fullCommand);
20894
21228
  } else {
20895
21229
  program.help();
@@ -20904,14 +21238,15 @@ registerAgentsCommand(program);
20904
21238
  registerScanCommand(program);
20905
21239
  registerSessionsCommand(program);
20906
21240
  registerDlpCommand(program);
21241
+ registerMaskCommand(program);
20907
21242
  if (process.argv[2] !== "daemon") {
20908
21243
  process.on("unhandledRejection", (reason) => {
20909
21244
  const isCheckHook = process.argv[2] === "check";
20910
21245
  if (isCheckHook) {
20911
21246
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
20912
- const logPath = path44.join(os37.homedir(), ".node9", "hook-debug.log");
21247
+ const logPath = path45.join(os38.homedir(), ".node9", "hook-debug.log");
20913
21248
  const msg = reason instanceof Error ? reason.message : String(reason);
20914
- fs41.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
21249
+ fs42.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
20915
21250
  `);
20916
21251
  }
20917
21252
  process.exit(0);