@node9/proxy 1.13.0 → 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";
@@ -1640,6 +1659,15 @@ var init_dlp = __esm({
1640
1659
  keywords: ["age-secret-key-"]
1641
1660
  }
1642
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
+ );
1643
1671
  SENSITIVE_PATH_PATTERNS = [
1644
1672
  /[/\\]\.ssh[/\\]/i,
1645
1673
  /[/\\]\.aws[/\\]/i,
@@ -2201,9 +2229,9 @@ function matchesPattern(text, patterns) {
2201
2229
  const withoutDotSlash = text.replace(/^\.\//, "");
2202
2230
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
2203
2231
  }
2204
- function getNestedValue(obj, path45) {
2232
+ function getNestedValue(obj, path46) {
2205
2233
  if (!obj || typeof obj !== "object") return null;
2206
- return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
2234
+ return path46.split(".").reduce((prev, curr) => prev?.[curr], obj);
2207
2235
  }
2208
2236
  function normalizeCommandForPolicy(command) {
2209
2237
  try {
@@ -9176,14 +9204,30 @@ var init_ui = __esm({
9176
9204
  // \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
9177
9205
  if (leaksByPattern.length) {
9178
9206
  html +=
9179
- '<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>';
9180
9208
  html += leaksByPattern
9181
9209
  .map(([pattern, group]) => {
9182
9210
  const count = group.length;
9183
9211
  const barPct = Math.round((count / maxBar) * 100);
9184
9212
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
9185
9213
  const rows = group
9186
- .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
+ })
9187
9231
  .join('');
9188
9232
  return (
9189
9233
  '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
@@ -13695,20 +13739,20 @@ function getModelContextLimit(model) {
13695
13739
  return 2e5;
13696
13740
  }
13697
13741
  function readSessionUsage() {
13698
- const projectsDir = import_path42.default.join(import_os35.default.homedir(), ".claude", "projects");
13699
- 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;
13700
13744
  let latestFile = null;
13701
13745
  let latestMtime = 0;
13702
13746
  try {
13703
- for (const dir of import_fs39.default.readdirSync(projectsDir)) {
13704
- 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);
13705
13749
  try {
13706
- if (!import_fs39.default.statSync(dirPath).isDirectory()) continue;
13707
- 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)) {
13708
13752
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
13709
- const filePath = import_path42.default.join(dirPath, file);
13753
+ const filePath = import_path43.default.join(dirPath, file);
13710
13754
  try {
13711
- const mtime = import_fs39.default.statSync(filePath).mtimeMs;
13755
+ const mtime = import_fs40.default.statSync(filePath).mtimeMs;
13712
13756
  if (mtime > latestMtime) {
13713
13757
  latestMtime = mtime;
13714
13758
  latestFile = filePath;
@@ -13723,7 +13767,7 @@ function readSessionUsage() {
13723
13767
  }
13724
13768
  if (!latestFile) return null;
13725
13769
  try {
13726
- const lines = import_fs39.default.readFileSync(latestFile, "utf-8").split("\n");
13770
+ const lines = import_fs40.default.readFileSync(latestFile, "utf-8").split("\n");
13727
13771
  let lastModel = "";
13728
13772
  let lastInput = 0;
13729
13773
  let lastOutput = 0;
@@ -13748,10 +13792,10 @@ function readSessionUsage() {
13748
13792
  }
13749
13793
  }
13750
13794
  function formatContextStat(stat) {
13751
- 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;
13752
13796
  const k = (n) => `${Math.round(n / 1e3)}k`;
13753
13797
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
13754
- 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(
13755
13799
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
13756
13800
  );
13757
13801
  }
@@ -13767,28 +13811,28 @@ function wrappedLineCount(text) {
13767
13811
  function agentLabel(agent) {
13768
13812
  if (!agent || agent === "Terminal") return "";
13769
13813
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
13770
- return short ? import_chalk25.default.dim(`[${short}] `) : "";
13814
+ return short ? import_chalk26.default.dim(`[${short}] `) : "";
13771
13815
  }
13772
13816
  function formatBase(activity) {
13773
13817
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
13774
13818
  const icon = getIcon(activity.tool);
13775
13819
  const toolName = activity.tool.slice(0, 16).padEnd(16);
13776
- 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(), "~");
13777
13821
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
13778
- 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)}`;
13779
13823
  }
13780
13824
  function renderResult(activity, result) {
13781
13825
  const base = formatBase(activity);
13782
13826
  let status;
13783
13827
  if (result.status === "allow") {
13784
- status = import_chalk25.default.green("\u2713 ALLOW");
13828
+ status = import_chalk26.default.green("\u2713 ALLOW");
13785
13829
  } else if (result.status === "dlp") {
13786
- status = import_chalk25.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13830
+ status = import_chalk26.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13787
13831
  } else {
13788
- status = import_chalk25.default.red("\u2717 BLOCK");
13832
+ status = import_chalk26.default.red("\u2717 BLOCK");
13789
13833
  }
13790
13834
  const cost = result.costEstimate ?? activity.costEstimate;
13791
- 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"}`);
13792
13836
  if (process.stdout.isTTY) {
13793
13837
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
13794
13838
  import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -13805,19 +13849,19 @@ function renderResult(activity, result) {
13805
13849
  }
13806
13850
  function renderPending(activity) {
13807
13851
  if (!process.stdout.isTTY) return;
13808
- const line = `${formatBase(activity)} ${import_chalk25.default.yellow("\u25CF \u2026")}`;
13852
+ const line = `${formatBase(activity)} ${import_chalk26.default.yellow("\u25CF \u2026")}`;
13809
13853
  pendingShownForId = activity.id;
13810
13854
  pendingWrappedLines = wrappedLineCount(line);
13811
13855
  process.stdout.write(`${line}\r`);
13812
13856
  }
13813
13857
  async function ensureDaemon() {
13814
13858
  let pidPort = null;
13815
- if (import_fs39.default.existsSync(PID_FILE)) {
13859
+ if (import_fs40.default.existsSync(PID_FILE)) {
13816
13860
  try {
13817
- 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"));
13818
13862
  pidPort = port;
13819
13863
  } catch {
13820
- 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."));
13821
13865
  }
13822
13866
  }
13823
13867
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -13828,7 +13872,7 @@ async function ensureDaemon() {
13828
13872
  if (res.ok) return checkPort;
13829
13873
  } catch {
13830
13874
  }
13831
- 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..."));
13832
13876
  const child = (0, import_child_process16.spawn)(process.execPath, [process.argv[1], "daemon"], {
13833
13877
  detached: true,
13834
13878
  stdio: "ignore",
@@ -13845,7 +13889,7 @@ async function ensureDaemon() {
13845
13889
  } catch {
13846
13890
  }
13847
13891
  }
13848
- 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"));
13849
13893
  process.exit(1);
13850
13894
  }
13851
13895
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -13911,7 +13955,7 @@ function buildCardLines(req, localCount = 0) {
13911
13955
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
13912
13956
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
13913
13957
  const description = rawDesc ? cleanReason(rawDesc) : "";
13914
- 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})`)}` : "";
13915
13959
  const lines = [
13916
13960
  ``,
13917
13961
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -13967,9 +14011,9 @@ function buildRecoveryCardLines(req) {
13967
14011
  ];
13968
14012
  }
13969
14013
  function readApproversFromDisk() {
13970
- 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");
13971
14015
  try {
13972
- const raw = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
14016
+ const raw = JSON.parse(import_fs40.default.readFileSync(configPath, "utf-8"));
13973
14017
  const settings = raw.settings ?? {};
13974
14018
  return settings.approvers ?? {};
13975
14019
  } catch {
@@ -13980,20 +14024,20 @@ function approverStatusLine() {
13980
14024
  const a = readApproversFromDisk();
13981
14025
  const fmt = (label, key) => {
13982
14026
  const on = a[key] !== false;
13983
- 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")}`;
13984
14028
  };
13985
14029
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
13986
14030
  }
13987
14031
  function toggleApprover(channel) {
13988
- 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");
13989
14033
  try {
13990
- const raw = JSON.parse(import_fs39.default.readFileSync(configPath, "utf-8"));
14034
+ const raw = JSON.parse(import_fs40.default.readFileSync(configPath, "utf-8"));
13991
14035
  const settings = raw.settings ?? {};
13992
14036
  const approvers = settings.approvers ?? {};
13993
14037
  approvers[channel] = approvers[channel] === false;
13994
14038
  settings.approvers = approvers;
13995
14039
  raw.settings = settings;
13996
- import_fs39.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
14040
+ import_fs40.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
13997
14041
  } catch (err2) {
13998
14042
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
13999
14043
  `);
@@ -14025,7 +14069,7 @@ async function startTail(options = {}) {
14025
14069
  req2.end();
14026
14070
  });
14027
14071
  if (result.ok) {
14028
- console.log(import_chalk25.default.green("\u2713 Flight Recorder buffer cleared."));
14072
+ console.log(import_chalk26.default.green("\u2713 Flight Recorder buffer cleared."));
14029
14073
  } else if (result.code === "ECONNREFUSED") {
14030
14074
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
14031
14075
  } else if (result.code === "ETIMEDOUT") {
@@ -14069,7 +14113,7 @@ async function startTail(options = {}) {
14069
14113
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
14070
14114
  if (channel) {
14071
14115
  toggleApprover(channel);
14072
- console.log(import_chalk25.default.dim(` Approvers: ${approverStatusLine()}`));
14116
+ console.log(import_chalk26.default.dim(` Approvers: ${approverStatusLine()}`));
14073
14117
  }
14074
14118
  };
14075
14119
  process.stdin.on("keypress", idleKeypressHandler);
@@ -14135,7 +14179,7 @@ async function startTail(options = {}) {
14135
14179
  localAllowCounts.get(req2.toolName) ?? 0
14136
14180
  )
14137
14181
  );
14138
- 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");
14139
14183
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
14140
14184
  for (const line of stampedLines) process.stdout.write(line + "\n");
14141
14185
  process.stdout.write(SHOW_CURSOR);
@@ -14163,8 +14207,8 @@ async function startTail(options = {}) {
14163
14207
  }
14164
14208
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
14165
14209
  try {
14166
- import_fs39.default.appendFileSync(
14167
- 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"),
14168
14212
  `[tail] POST /decision failed: ${String(err2)}
14169
14213
  `
14170
14214
  );
@@ -14186,7 +14230,7 @@ async function startTail(options = {}) {
14186
14230
  );
14187
14231
  const stampedLines = buildCardLines(req2, priorCount);
14188
14232
  if (externalDecision) {
14189
- 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");
14190
14234
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
14191
14235
  }
14192
14236
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -14245,31 +14289,31 @@ async function startTail(options = {}) {
14245
14289
  }
14246
14290
  } catch {
14247
14291
  }
14248
- 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");
14249
14293
  try {
14250
- 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;
14251
14295
  if (unackedDlp > 0) {
14252
14296
  console.log("");
14253
14297
  console.log(
14254
- import_chalk25.default.bgRed.white.bold(
14298
+ import_chalk26.default.bgRed.white.bold(
14255
14299
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
14256
14300
  )
14257
14301
  );
14258
14302
  }
14259
14303
  } catch {
14260
14304
  }
14261
- console.log(import_chalk25.default.cyan.bold(`
14262
- \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}`));
14263
14307
  if (canApprove) {
14264
- console.log(import_chalk25.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
14265
- 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`));
14266
14310
  }
14267
14311
  const ctxStat = readSessionUsage();
14268
14312
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
14269
14313
  if (options.history) {
14270
- console.log(import_chalk25.default.dim("Showing history + live events.\n"));
14314
+ console.log(import_chalk26.default.dim("Showing history + live events.\n"));
14271
14315
  } else {
14272
- 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"));
14273
14317
  }
14274
14318
  process.on("SIGINT", () => {
14275
14319
  exitIdleMode();
@@ -14279,13 +14323,13 @@ async function startTail(options = {}) {
14279
14323
  import_readline5.default.clearLine(process.stdout, 0);
14280
14324
  import_readline5.default.cursorTo(process.stdout, 0);
14281
14325
  }
14282
- console.log(import_chalk25.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14326
+ console.log(import_chalk26.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14283
14327
  process.exit(0);
14284
14328
  });
14285
14329
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
14286
14330
  const req = import_http2.default.get(sseUrl, (res) => {
14287
14331
  if (res.statusCode !== 200) {
14288
- 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}`));
14289
14333
  process.exit(1);
14290
14334
  }
14291
14335
  if (canApprove) enterIdleMode();
@@ -14316,7 +14360,7 @@ async function startTail(options = {}) {
14316
14360
  import_readline5.default.clearLine(process.stdout, 0);
14317
14361
  import_readline5.default.cursorTo(process.stdout, 0);
14318
14362
  }
14319
- console.log(import_chalk25.default.red("\n\u274C Daemon disconnected."));
14363
+ console.log(import_chalk26.default.red("\n\u274C Daemon disconnected."));
14320
14364
  process.exit(1);
14321
14365
  });
14322
14366
  });
@@ -14408,9 +14452,9 @@ async function startTail(options = {}) {
14408
14452
  const hash = data.hash ?? "";
14409
14453
  const summary = data.argsSummary ?? data.tool;
14410
14454
  const fileCount = data.fileCount ?? 0;
14411
- 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"}`) : "";
14412
14456
  process.stdout.write(
14413
- `${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}
14414
14458
  `
14415
14459
  );
14416
14460
  return;
@@ -14427,26 +14471,26 @@ async function startTail(options = {}) {
14427
14471
  }
14428
14472
  req.on("error", (err2) => {
14429
14473
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
14430
- console.error(import_chalk25.default.red(`
14474
+ console.error(import_chalk26.default.red(`
14431
14475
  \u274C ${msg}`));
14432
14476
  process.exit(1);
14433
14477
  });
14434
14478
  }
14435
- 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;
14436
14480
  var init_tail = __esm({
14437
14481
  "src/tui/tail.ts"() {
14438
14482
  "use strict";
14439
14483
  import_http2 = __toESM(require("http"));
14440
- import_chalk25 = __toESM(require("chalk"));
14441
- import_fs39 = __toESM(require("fs"));
14442
- import_os35 = __toESM(require("os"));
14443
- 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"));
14444
14488
  import_readline5 = __toESM(require("readline"));
14445
14489
  import_child_process16 = require("child_process");
14446
14490
  init_daemon2();
14447
14491
  init_daemon();
14448
14492
  init_core();
14449
- 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");
14450
14494
  ICONS = {
14451
14495
  bash: "\u{1F4BB}",
14452
14496
  shell: "\u{1F4BB}",
@@ -14568,9 +14612,9 @@ function formatTimeLeft(resetsAt) {
14568
14612
  return ` (${m}m left)`;
14569
14613
  }
14570
14614
  function safeReadJson(filePath) {
14571
- if (!import_fs40.default.existsSync(filePath)) return null;
14615
+ if (!import_fs41.default.existsSync(filePath)) return null;
14572
14616
  try {
14573
- return JSON.parse(import_fs40.default.readFileSync(filePath, "utf-8"));
14617
+ return JSON.parse(import_fs41.default.readFileSync(filePath, "utf-8"));
14574
14618
  } catch {
14575
14619
  return null;
14576
14620
  }
@@ -14591,12 +14635,12 @@ function countHooksInFile(filePath) {
14591
14635
  return Object.keys(cfg.hooks).length;
14592
14636
  }
14593
14637
  function countRulesInDir(rulesDir) {
14594
- if (!import_fs40.default.existsSync(rulesDir)) return 0;
14638
+ if (!import_fs41.default.existsSync(rulesDir)) return 0;
14595
14639
  let count = 0;
14596
14640
  try {
14597
- for (const entry of import_fs40.default.readdirSync(rulesDir, { withFileTypes: true })) {
14641
+ for (const entry of import_fs41.default.readdirSync(rulesDir, { withFileTypes: true })) {
14598
14642
  if (entry.isDirectory()) {
14599
- count += countRulesInDir(import_path43.default.join(rulesDir, entry.name));
14643
+ count += countRulesInDir(import_path44.default.join(rulesDir, entry.name));
14600
14644
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
14601
14645
  count++;
14602
14646
  }
@@ -14607,46 +14651,46 @@ function countRulesInDir(rulesDir) {
14607
14651
  }
14608
14652
  function isSamePath(a, b) {
14609
14653
  try {
14610
- return import_path43.default.resolve(a) === import_path43.default.resolve(b);
14654
+ return import_path44.default.resolve(a) === import_path44.default.resolve(b);
14611
14655
  } catch {
14612
14656
  return false;
14613
14657
  }
14614
14658
  }
14615
14659
  function countConfigs(cwd) {
14616
- const homeDir2 = import_os36.default.homedir();
14617
- const claudeDir = import_path43.default.join(homeDir2, ".claude");
14660
+ const homeDir2 = import_os37.default.homedir();
14661
+ const claudeDir = import_path44.default.join(homeDir2, ".claude");
14618
14662
  let claudeMdCount = 0;
14619
14663
  let rulesCount = 0;
14620
14664
  let hooksCount = 0;
14621
14665
  const userMcpServers = /* @__PURE__ */ new Set();
14622
14666
  const projectMcpServers = /* @__PURE__ */ new Set();
14623
- if (import_fs40.default.existsSync(import_path43.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
14624
- rulesCount += countRulesInDir(import_path43.default.join(claudeDir, "rules"));
14625
- 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");
14626
14670
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
14627
14671
  hooksCount += countHooksInFile(userSettings);
14628
- const userClaudeJson = import_path43.default.join(homeDir2, ".claude.json");
14672
+ const userClaudeJson = import_path44.default.join(homeDir2, ".claude.json");
14629
14673
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
14630
14674
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
14631
14675
  userMcpServers.delete(name);
14632
14676
  }
14633
14677
  if (cwd) {
14634
- if (import_fs40.default.existsSync(import_path43.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
14635
- if (import_fs40.default.existsSync(import_path43.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
14636
- 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");
14637
14681
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
14638
14682
  if (!overlapsUserScope) {
14639
- if (import_fs40.default.existsSync(import_path43.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
14640
- rulesCount += countRulesInDir(import_path43.default.join(projectClaudeDir, "rules"));
14641
- 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");
14642
14686
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
14643
14687
  hooksCount += countHooksInFile(projSettings);
14644
14688
  }
14645
- if (import_fs40.default.existsSync(import_path43.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
14646
- 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");
14647
14691
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
14648
14692
  hooksCount += countHooksInFile(localSettings);
14649
- const mcpJsonServers = getMcpServerNames(import_path43.default.join(cwd, ".mcp.json"));
14693
+ const mcpJsonServers = getMcpServerNames(import_path44.default.join(cwd, ".mcp.json"));
14650
14694
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
14651
14695
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
14652
14696
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -14679,12 +14723,12 @@ function readActiveShieldsHud() {
14679
14723
  return shieldsCache.value;
14680
14724
  }
14681
14725
  try {
14682
- const shieldsPath = import_path43.default.join(import_os36.default.homedir(), ".node9", "shields.json");
14683
- 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)) {
14684
14728
  shieldsCache = { value: [], ts: now };
14685
14729
  return [];
14686
14730
  }
14687
- const parsed = JSON.parse(import_fs40.default.readFileSync(shieldsPath, "utf-8"));
14731
+ const parsed = JSON.parse(import_fs41.default.readFileSync(shieldsPath, "utf-8"));
14688
14732
  if (!Array.isArray(parsed.active)) {
14689
14733
  shieldsCache = { value: [], ts: now };
14690
14734
  return [];
@@ -14786,17 +14830,17 @@ function renderContextLine(stdin) {
14786
14830
  async function main() {
14787
14831
  try {
14788
14832
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
14789
- 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"))) {
14790
14834
  try {
14791
- 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");
14792
14836
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
14793
14837
  let size = 0;
14794
14838
  try {
14795
- size = import_fs40.default.statSync(logPath).size;
14839
+ size = import_fs41.default.statSync(logPath).size;
14796
14840
  } catch {
14797
14841
  }
14798
14842
  if (size < MAX_LOG_SIZE) {
14799
- import_fs40.default.appendFileSync(
14843
+ import_fs41.default.appendFileSync(
14800
14844
  logPath,
14801
14845
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
14802
14846
  );
@@ -14817,11 +14861,11 @@ async function main() {
14817
14861
  try {
14818
14862
  const cwd = stdin.cwd ?? process.cwd();
14819
14863
  for (const configPath of [
14820
- import_path43.default.join(cwd, "node9.config.json"),
14821
- 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")
14822
14866
  ]) {
14823
- if (!import_fs40.default.existsSync(configPath)) continue;
14824
- 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"));
14825
14869
  const hud = cfg.settings?.hud;
14826
14870
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
14827
14871
  }
@@ -14839,13 +14883,13 @@ async function main() {
14839
14883
  renderOffline();
14840
14884
  }
14841
14885
  }
14842
- 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;
14843
14887
  var init_hud = __esm({
14844
14888
  "src/cli/hud.ts"() {
14845
14889
  "use strict";
14846
- import_fs40 = __toESM(require("fs"));
14847
- import_path43 = __toESM(require("path"));
14848
- import_os36 = __toESM(require("os"));
14890
+ import_fs41 = __toESM(require("fs"));
14891
+ import_path44 = __toESM(require("path"));
14892
+ import_os37 = __toESM(require("os"));
14849
14893
  import_http3 = __toESM(require("http"));
14850
14894
  init_daemon();
14851
14895
  RESET3 = "\x1B[0m";
@@ -14871,10 +14915,10 @@ var import_commander = require("commander");
14871
14915
  init_core();
14872
14916
  init_setup();
14873
14917
  init_daemon2();
14874
- var import_chalk26 = __toESM(require("chalk"));
14875
- var import_fs41 = __toESM(require("fs"));
14876
- var import_path44 = __toESM(require("path"));
14877
- 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"));
14878
14922
  var import_prompts2 = require("@inquirer/prompts");
14879
14923
 
14880
14924
  // src/utils/duration.ts
@@ -20584,22 +20628,207 @@ function registerDlpCommand(program2) {
20584
20628
  });
20585
20629
  }
20586
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
+
20587
20816
  // src/cli.ts
20588
20817
  var { version } = JSON.parse(
20589
- 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")
20590
20819
  );
20591
20820
  var program = new import_commander.Command();
20592
20821
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
20593
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) => {
20594
20823
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
20595
- const credPath = import_path44.default.join(import_os37.default.homedir(), ".node9", "credentials.json");
20596
- if (!import_fs41.default.existsSync(import_path44.default.dirname(credPath)))
20597
- 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 });
20598
20827
  const profileName = options.profile || "default";
20599
20828
  let existingCreds = {};
20600
20829
  try {
20601
- if (import_fs41.default.existsSync(credPath)) {
20602
- 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"));
20603
20832
  if (raw.apiKey) {
20604
20833
  existingCreds = {
20605
20834
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -20611,13 +20840,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20611
20840
  } catch {
20612
20841
  }
20613
20842
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
20614
- 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 });
20615
20844
  if (profileName === "default") {
20616
- 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");
20617
20846
  let config = {};
20618
20847
  try {
20619
- if (import_fs41.default.existsSync(configPath))
20620
- 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"));
20621
20850
  } catch {
20622
20851
  }
20623
20852
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -20632,19 +20861,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20632
20861
  approvers.cloud = false;
20633
20862
  }
20634
20863
  s.approvers = approvers;
20635
- if (!import_fs41.default.existsSync(import_path44.default.dirname(configPath)))
20636
- import_fs41.default.mkdirSync(import_path44.default.dirname(configPath), { recursive: true });
20637
- 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 });
20638
20867
  }
20639
20868
  if (options.profile && profileName !== "default") {
20640
- console.log(import_chalk26.default.green(`\u2705 Profile "${profileName}" saved`));
20641
- 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`));
20642
20871
  } else if (options.local) {
20643
- console.log(import_chalk26.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
20644
- 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.`));
20645
20874
  } else {
20646
- console.log(import_chalk26.default.green(`\u2705 Logged in \u2014 agent mode`));
20647
- 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.`));
20648
20877
  }
20649
20878
  });
20650
20879
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
@@ -20662,7 +20891,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
20662
20891
  if (target === "vscode") return await setupVSCode();
20663
20892
  if (target === "hud") return setupHud();
20664
20893
  console.error(
20665
- import_chalk26.default.red(
20894
+ import_chalk27.default.red(
20666
20895
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20667
20896
  )
20668
20897
  );
@@ -20676,17 +20905,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20676
20905
  "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
20677
20906
  ).action(async (target) => {
20678
20907
  if (!target) {
20679
- console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
20680
- 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");
20681
20910
  console.log(" Targets:");
20682
- console.log(" " + import_chalk26.default.green("claude") + " \u2014 Claude Code (hook mode)");
20683
- console.log(" " + import_chalk26.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
20684
- console.log(" " + import_chalk26.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
20685
- console.log(" " + import_chalk26.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
20686
- console.log(" " + import_chalk26.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
20687
- 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)");
20688
20917
  process.stdout.write(
20689
- " " + import_chalk26.default.green("hud") + " \u2014 Claude Code security statusline\n"
20918
+ " " + import_chalk27.default.green("hud") + " \u2014 Claude Code security statusline\n"
20690
20919
  );
20691
20920
  console.log("");
20692
20921
  return;
@@ -20700,7 +20929,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20700
20929
  if (t === "vscode") return await setupVSCode();
20701
20930
  if (t === "hud") return setupHud();
20702
20931
  console.error(
20703
- import_chalk26.default.red(
20932
+ import_chalk27.default.red(
20704
20933
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20705
20934
  )
20706
20935
  );
@@ -20723,33 +20952,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
20723
20952
  else if (target === "hud") fn = teardownHud;
20724
20953
  else {
20725
20954
  console.error(
20726
- import_chalk26.default.red(
20955
+ import_chalk27.default.red(
20727
20956
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20728
20957
  )
20729
20958
  );
20730
20959
  process.exit(1);
20731
20960
  }
20732
- console.log(import_chalk26.default.cyan(`
20961
+ console.log(import_chalk27.default.cyan(`
20733
20962
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
20734
20963
  `));
20735
20964
  try {
20736
20965
  fn();
20737
20966
  } catch (err2) {
20738
- 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)}`));
20739
20968
  process.exit(1);
20740
20969
  }
20741
- 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."));
20742
20971
  });
20743
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) => {
20744
- console.log(import_chalk26.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
20745
- 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..."));
20746
20975
  try {
20747
20976
  stopDaemon();
20748
- console.log(import_chalk26.default.green(" \u2705 Daemon stopped"));
20977
+ console.log(import_chalk27.default.green(" \u2705 Daemon stopped"));
20749
20978
  } catch {
20750
- 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"));
20751
20980
  }
20752
- console.log(import_chalk26.default.bold("\nRemoving hooks..."));
20981
+ console.log(import_chalk27.default.bold("\nRemoving hooks..."));
20753
20982
  let teardownFailed = false;
20754
20983
  for (const [label, fn] of [
20755
20984
  ["Claude", teardownClaude],
@@ -20764,45 +20993,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
20764
20993
  } catch (err2) {
20765
20994
  teardownFailed = true;
20766
20995
  console.error(
20767
- import_chalk26.default.red(
20996
+ import_chalk27.default.red(
20768
20997
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
20769
20998
  )
20770
20999
  );
20771
21000
  }
20772
21001
  }
20773
21002
  if (options.purge) {
20774
- const node9Dir = import_path44.default.join(import_os37.default.homedir(), ".node9");
20775
- 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)) {
20776
21005
  const confirmed = await (0, import_prompts2.confirm)({
20777
21006
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
20778
21007
  default: false
20779
21008
  });
20780
21009
  if (confirmed) {
20781
- import_fs41.default.rmSync(node9Dir, { recursive: true });
20782
- if (import_fs41.default.existsSync(node9Dir)) {
21010
+ import_fs42.default.rmSync(node9Dir, { recursive: true });
21011
+ if (import_fs42.default.existsSync(node9Dir)) {
20783
21012
  console.error(
20784
- 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.")
20785
21014
  );
20786
21015
  } else {
20787
- 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)"));
20788
21017
  }
20789
21018
  } else {
20790
- 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."));
20791
21020
  }
20792
21021
  } else {
20793
- 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"));
20794
21023
  }
20795
21024
  } else {
20796
21025
  console.log(
20797
- 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")
20798
21027
  );
20799
21028
  }
20800
21029
  if (teardownFailed) {
20801
- 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."));
20802
21031
  process.exit(1);
20803
21032
  }
20804
- console.log(import_chalk26.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
20805
- 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"));
20806
21035
  });
20807
21036
  registerDoctorCommand(program, version);
20808
21037
  program.command("explain").description(
@@ -20815,7 +21044,7 @@ program.command("explain").description(
20815
21044
  try {
20816
21045
  args = JSON.parse(trimmed);
20817
21046
  } catch {
20818
- console.error(import_chalk26.default.red(`
21047
+ console.error(import_chalk27.default.red(`
20819
21048
  \u274C Invalid JSON: ${trimmed}
20820
21049
  `));
20821
21050
  process.exit(1);
@@ -20826,54 +21055,54 @@ program.command("explain").description(
20826
21055
  }
20827
21056
  const result = await explainPolicy(tool, args);
20828
21057
  console.log("");
20829
- 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"));
20830
21059
  console.log("");
20831
- 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)}`);
20832
21061
  if (argsRaw) {
20833
21062
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
20834
- 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)}`);
20835
21064
  }
20836
21065
  console.log("");
20837
- console.log(import_chalk26.default.bold("Config Sources (Waterfall):"));
21066
+ console.log(import_chalk27.default.bold("Config Sources (Waterfall):"));
20838
21067
  for (const tier of result.waterfall) {
20839
- const num3 = import_chalk26.default.gray(` ${tier.tier}.`);
21068
+ const num3 = import_chalk27.default.gray(` ${tier.tier}.`);
20840
21069
  const label = tier.label.padEnd(16);
20841
21070
  let statusStr;
20842
21071
  if (tier.tier === 1) {
20843
- statusStr = import_chalk26.default.gray(tier.note ?? "");
21072
+ statusStr = import_chalk27.default.gray(tier.note ?? "");
20844
21073
  } else if (tier.status === "active") {
20845
- const loc = tier.path ? import_chalk26.default.gray(tier.path) : "";
20846
- const note = tier.note ? import_chalk26.default.gray(`(${tier.note})`) : "";
20847
- 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 : "");
20848
21077
  } else {
20849
- statusStr = import_chalk26.default.gray("\u25CB " + (tier.note ?? "not found"));
21078
+ statusStr = import_chalk27.default.gray("\u25CB " + (tier.note ?? "not found"));
20850
21079
  }
20851
- console.log(`${num3} ${import_chalk26.default.white(label)} ${statusStr}`);
21080
+ console.log(`${num3} ${import_chalk27.default.white(label)} ${statusStr}`);
20852
21081
  }
20853
21082
  console.log("");
20854
- console.log(import_chalk26.default.bold("Policy Evaluation:"));
21083
+ console.log(import_chalk27.default.bold("Policy Evaluation:"));
20855
21084
  for (const step of result.steps) {
20856
21085
  const isFinal = step.isFinal;
20857
21086
  let icon;
20858
- if (step.outcome === "allow") icon = import_chalk26.default.green(" \u2705");
20859
- else if (step.outcome === "review") icon = import_chalk26.default.red(" \u{1F534}");
20860
- else if (step.outcome === "skip") icon = import_chalk26.default.gray(" \u2500 ");
20861
- 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 ");
20862
21091
  const name = step.name.padEnd(18);
20863
- const nameStr = isFinal ? import_chalk26.default.white.bold(name) : import_chalk26.default.white(name);
20864
- const detail = isFinal ? import_chalk26.default.white(step.detail) : import_chalk26.default.gray(step.detail);
20865
- 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") : "";
20866
21095
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
20867
21096
  }
20868
21097
  console.log("");
20869
21098
  if (result.decision === "allow") {
20870
- 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"));
20871
21100
  } else {
20872
21101
  console.log(
20873
- 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")
20874
21103
  );
20875
21104
  if (result.blockedByLabel) {
20876
- console.log(import_chalk26.default.gray(` Reason: ${result.blockedByLabel}`));
21105
+ console.log(import_chalk27.default.gray(` Reason: ${result.blockedByLabel}`));
20877
21106
  }
20878
21107
  }
20879
21108
  console.log("");
@@ -20888,7 +21117,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
20888
21117
  try {
20889
21118
  await startTail2(options);
20890
21119
  } catch (err2) {
20891
- 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)}`));
20892
21121
  process.exit(1);
20893
21122
  }
20894
21123
  });
@@ -20921,14 +21150,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
20921
21150
  Run "node9 addto claude" to register it as the statusLine.`
20922
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) => {
20923
21152
  if (subcommand === "debug") {
20924
- 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");
20925
21154
  if (state === "on") {
20926
- import_fs41.default.mkdirSync(import_path44.default.dirname(flagFile), { recursive: true });
20927
- import_fs41.default.writeFileSync(flagFile, "");
21155
+ import_fs42.default.mkdirSync(import_path45.default.dirname(flagFile), { recursive: true });
21156
+ import_fs42.default.writeFileSync(flagFile, "");
20928
21157
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
20929
21158
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
20930
21159
  } else if (state === "off") {
20931
- if (import_fs41.default.existsSync(flagFile)) import_fs41.default.unlinkSync(flagFile);
21160
+ if (import_fs42.default.existsSync(flagFile)) import_fs42.default.unlinkSync(flagFile);
20932
21161
  console.log("HUD debug logging disabled.");
20933
21162
  } else {
20934
21163
  console.error("Usage: node9 hud debug on|off");
@@ -20943,7 +21172,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20943
21172
  const ms = parseDuration(options.duration);
20944
21173
  if (ms === null) {
20945
21174
  console.error(
20946
- import_chalk26.default.red(`
21175
+ import_chalk27.default.red(`
20947
21176
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
20948
21177
  `)
20949
21178
  );
@@ -20951,20 +21180,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20951
21180
  }
20952
21181
  pauseNode9(ms, options.duration);
20953
21182
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
20954
- console.log(import_chalk26.default.yellow(`
21183
+ console.log(import_chalk27.default.yellow(`
20955
21184
  \u23F8 Node9 paused until ${expiresAt}`));
20956
- console.log(import_chalk26.default.gray(` All tool calls will be allowed without review.`));
20957
- 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.
20958
21187
  `));
20959
21188
  });
20960
21189
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
20961
21190
  const { paused } = checkPause();
20962
21191
  if (!paused) {
20963
- 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"));
20964
21193
  return;
20965
21194
  }
20966
21195
  resumeNode9();
20967
- 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"));
20968
21197
  });
20969
21198
  var HOOK_BASED_AGENTS = {
20970
21199
  claude: "claude",
@@ -20977,15 +21206,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20977
21206
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
20978
21207
  const target = HOOK_BASED_AGENTS[firstArg2];
20979
21208
  console.error(
20980
- import_chalk26.default.yellow(`
21209
+ import_chalk27.default.yellow(`
20981
21210
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
20982
21211
  );
20983
- console.error(import_chalk26.default.white(`
21212
+ console.error(import_chalk27.default.white(`
20984
21213
  "${target}" uses its own hook system. Use:`));
20985
21214
  console.error(
20986
- 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")
20987
21216
  );
20988
- 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"));
20989
21218
  process.exit(1);
20990
21219
  }
20991
21220
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -21002,7 +21231,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
21002
21231
  }
21003
21232
  );
21004
21233
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
21005
- 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..."));
21006
21235
  const daemonReady = await autoStartDaemonAndWait();
21007
21236
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
21008
21237
  }
@@ -21015,12 +21244,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
21015
21244
  }
21016
21245
  if (!result.approved) {
21017
21246
  console.error(
21018
- import_chalk26.default.red(`
21247
+ import_chalk27.default.red(`
21019
21248
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
21020
21249
  );
21021
21250
  process.exit(1);
21022
21251
  }
21023
- 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"));
21024
21253
  await runProxy(fullCommand);
21025
21254
  } else {
21026
21255
  program.help();
@@ -21035,14 +21264,15 @@ registerAgentsCommand(program);
21035
21264
  registerScanCommand(program);
21036
21265
  registerSessionsCommand(program);
21037
21266
  registerDlpCommand(program);
21267
+ registerMaskCommand(program);
21038
21268
  if (process.argv[2] !== "daemon") {
21039
21269
  process.on("unhandledRejection", (reason) => {
21040
21270
  const isCheckHook = process.argv[2] === "check";
21041
21271
  if (isCheckHook) {
21042
21272
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
21043
- 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");
21044
21274
  const msg = reason instanceof Error ? reason.message : String(reason);
21045
- import_fs41.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
21275
+ import_fs42.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
21046
21276
  `);
21047
21277
  }
21048
21278
  process.exit(0);