@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.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";
@@ -1618,6 +1637,15 @@ var init_dlp = __esm({
1618
1637
  keywords: ["age-secret-key-"]
1619
1638
  }
1620
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
+ );
1621
1649
  SENSITIVE_PATH_PATTERNS = [
1622
1650
  /[/\\]\.ssh[/\\]/i,
1623
1651
  /[/\\]\.aws[/\\]/i,
@@ -2184,9 +2212,9 @@ function matchesPattern(text, patterns) {
2184
2212
  const withoutDotSlash = text.replace(/^\.\//, "");
2185
2213
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
2186
2214
  }
2187
- function getNestedValue(obj, path45) {
2215
+ function getNestedValue(obj, path46) {
2188
2216
  if (!obj || typeof obj !== "object") return null;
2189
- return path45.split(".").reduce((prev, curr) => prev?.[curr], obj);
2217
+ return path46.split(".").reduce((prev, curr) => prev?.[curr], obj);
2190
2218
  }
2191
2219
  function normalizeCommandForPolicy(command) {
2192
2220
  try {
@@ -9153,14 +9181,30 @@ var init_ui = __esm({
9153
9181
  // \u2500\u2500 Leaks (shown first: credential exposure is highest-severity) \u2500\u2500\u2500\u2500\u2500
9154
9182
  if (leaksByPattern.length) {
9155
9183
  html +=
9156
- '<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>';
9157
9185
  html += leaksByPattern
9158
9186
  .map(([pattern, group]) => {
9159
9187
  const count = group.length;
9160
9188
  const barPct = Math.round((count / maxBar) * 100);
9161
9189
  const detailId = 'detail-' + Math.random().toString(36).slice(2);
9162
9190
  const rows = group
9163
- .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
+ })
9164
9208
  .join('');
9165
9209
  return (
9166
9210
  '<div class="scan-rule-row" onclick="var d=document.getElementById(\\'' +
@@ -13655,10 +13699,10 @@ __export(tail_exports, {
13655
13699
  startTail: () => startTail
13656
13700
  });
13657
13701
  import http2 from "http";
13658
- import chalk25 from "chalk";
13659
- import fs39 from "fs";
13660
- import os35 from "os";
13661
- import path42 from "path";
13702
+ import chalk26 from "chalk";
13703
+ import fs40 from "fs";
13704
+ import os36 from "os";
13705
+ import path43 from "path";
13662
13706
  import readline5 from "readline";
13663
13707
  import { spawn as spawn10, execSync as execSync3 } from "child_process";
13664
13708
  function getIcon(tool) {
@@ -13676,20 +13720,20 @@ function getModelContextLimit(model) {
13676
13720
  return 2e5;
13677
13721
  }
13678
13722
  function readSessionUsage() {
13679
- const projectsDir = path42.join(os35.homedir(), ".claude", "projects");
13680
- if (!fs39.existsSync(projectsDir)) return null;
13723
+ const projectsDir = path43.join(os36.homedir(), ".claude", "projects");
13724
+ if (!fs40.existsSync(projectsDir)) return null;
13681
13725
  let latestFile = null;
13682
13726
  let latestMtime = 0;
13683
13727
  try {
13684
- for (const dir of fs39.readdirSync(projectsDir)) {
13685
- const dirPath = path42.join(projectsDir, dir);
13728
+ for (const dir of fs40.readdirSync(projectsDir)) {
13729
+ const dirPath = path43.join(projectsDir, dir);
13686
13730
  try {
13687
- if (!fs39.statSync(dirPath).isDirectory()) continue;
13688
- for (const file of fs39.readdirSync(dirPath)) {
13731
+ if (!fs40.statSync(dirPath).isDirectory()) continue;
13732
+ for (const file of fs40.readdirSync(dirPath)) {
13689
13733
  if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
13690
- const filePath = path42.join(dirPath, file);
13734
+ const filePath = path43.join(dirPath, file);
13691
13735
  try {
13692
- const mtime = fs39.statSync(filePath).mtimeMs;
13736
+ const mtime = fs40.statSync(filePath).mtimeMs;
13693
13737
  if (mtime > latestMtime) {
13694
13738
  latestMtime = mtime;
13695
13739
  latestFile = filePath;
@@ -13704,7 +13748,7 @@ function readSessionUsage() {
13704
13748
  }
13705
13749
  if (!latestFile) return null;
13706
13750
  try {
13707
- const lines = fs39.readFileSync(latestFile, "utf-8").split("\n");
13751
+ const lines = fs40.readFileSync(latestFile, "utf-8").split("\n");
13708
13752
  let lastModel = "";
13709
13753
  let lastInput = 0;
13710
13754
  let lastOutput = 0;
@@ -13729,10 +13773,10 @@ function readSessionUsage() {
13729
13773
  }
13730
13774
  }
13731
13775
  function formatContextStat(stat) {
13732
- 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;
13733
13777
  const k = (n) => `${Math.round(n / 1e3)}k`;
13734
13778
  const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
13735
- return chalk25.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk25.dim(
13779
+ return chalk26.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk26.dim(
13736
13780
  ` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
13737
13781
  );
13738
13782
  }
@@ -13748,28 +13792,28 @@ function wrappedLineCount(text) {
13748
13792
  function agentLabel(agent) {
13749
13793
  if (!agent || agent === "Terminal") return "";
13750
13794
  const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
13751
- return short ? chalk25.dim(`[${short}] `) : "";
13795
+ return short ? chalk26.dim(`[${short}] `) : "";
13752
13796
  }
13753
13797
  function formatBase(activity) {
13754
13798
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
13755
13799
  const icon = getIcon(activity.tool);
13756
13800
  const toolName = activity.tool.slice(0, 16).padEnd(16);
13757
- 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(), "~");
13758
13802
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
13759
- 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)}`;
13760
13804
  }
13761
13805
  function renderResult(activity, result) {
13762
13806
  const base = formatBase(activity);
13763
13807
  let status;
13764
13808
  if (result.status === "allow") {
13765
- status = chalk25.green("\u2713 ALLOW");
13809
+ status = chalk26.green("\u2713 ALLOW");
13766
13810
  } else if (result.status === "dlp") {
13767
- status = chalk25.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13811
+ status = chalk26.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
13768
13812
  } else {
13769
- status = chalk25.red("\u2717 BLOCK");
13813
+ status = chalk26.red("\u2717 BLOCK");
13770
13814
  }
13771
13815
  const cost = result.costEstimate ?? activity.costEstimate;
13772
- 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"}`);
13773
13817
  if (process.stdout.isTTY) {
13774
13818
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
13775
13819
  readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -13786,19 +13830,19 @@ function renderResult(activity, result) {
13786
13830
  }
13787
13831
  function renderPending(activity) {
13788
13832
  if (!process.stdout.isTTY) return;
13789
- const line = `${formatBase(activity)} ${chalk25.yellow("\u25CF \u2026")}`;
13833
+ const line = `${formatBase(activity)} ${chalk26.yellow("\u25CF \u2026")}`;
13790
13834
  pendingShownForId = activity.id;
13791
13835
  pendingWrappedLines = wrappedLineCount(line);
13792
13836
  process.stdout.write(`${line}\r`);
13793
13837
  }
13794
13838
  async function ensureDaemon() {
13795
13839
  let pidPort = null;
13796
- if (fs39.existsSync(PID_FILE)) {
13840
+ if (fs40.existsSync(PID_FILE)) {
13797
13841
  try {
13798
- const { port } = JSON.parse(fs39.readFileSync(PID_FILE, "utf-8"));
13842
+ const { port } = JSON.parse(fs40.readFileSync(PID_FILE, "utf-8"));
13799
13843
  pidPort = port;
13800
13844
  } catch {
13801
- 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."));
13802
13846
  }
13803
13847
  }
13804
13848
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -13809,7 +13853,7 @@ async function ensureDaemon() {
13809
13853
  if (res.ok) return checkPort;
13810
13854
  } catch {
13811
13855
  }
13812
- console.log(chalk25.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
13856
+ console.log(chalk26.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
13813
13857
  const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
13814
13858
  detached: true,
13815
13859
  stdio: "ignore",
@@ -13826,7 +13870,7 @@ async function ensureDaemon() {
13826
13870
  } catch {
13827
13871
  }
13828
13872
  }
13829
- 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"));
13830
13874
  process.exit(1);
13831
13875
  }
13832
13876
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -13892,7 +13936,7 @@ function buildCardLines(req, localCount = 0) {
13892
13936
  const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
13893
13937
  const rawDesc = req.riskMetadata?.ruleDescription ?? "";
13894
13938
  const description = rawDesc ? cleanReason(rawDesc) : "";
13895
- 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})`)}` : "";
13896
13940
  const lines = [
13897
13941
  ``,
13898
13942
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
@@ -13948,9 +13992,9 @@ function buildRecoveryCardLines(req) {
13948
13992
  ];
13949
13993
  }
13950
13994
  function readApproversFromDisk() {
13951
- const configPath = path42.join(os35.homedir(), ".node9", "config.json");
13995
+ const configPath = path43.join(os36.homedir(), ".node9", "config.json");
13952
13996
  try {
13953
- const raw = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
13997
+ const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
13954
13998
  const settings = raw.settings ?? {};
13955
13999
  return settings.approvers ?? {};
13956
14000
  } catch {
@@ -13961,20 +14005,20 @@ function approverStatusLine() {
13961
14005
  const a = readApproversFromDisk();
13962
14006
  const fmt = (label, key) => {
13963
14007
  const on = a[key] !== false;
13964
- 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")}`;
13965
14009
  };
13966
14010
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
13967
14011
  }
13968
14012
  function toggleApprover(channel) {
13969
- const configPath = path42.join(os35.homedir(), ".node9", "config.json");
14013
+ const configPath = path43.join(os36.homedir(), ".node9", "config.json");
13970
14014
  try {
13971
- const raw = JSON.parse(fs39.readFileSync(configPath, "utf-8"));
14015
+ const raw = JSON.parse(fs40.readFileSync(configPath, "utf-8"));
13972
14016
  const settings = raw.settings ?? {};
13973
14017
  const approvers = settings.approvers ?? {};
13974
14018
  approvers[channel] = approvers[channel] === false;
13975
14019
  settings.approvers = approvers;
13976
14020
  raw.settings = settings;
13977
- fs39.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
14021
+ fs40.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
13978
14022
  } catch (err2) {
13979
14023
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
13980
14024
  `);
@@ -14006,7 +14050,7 @@ async function startTail(options = {}) {
14006
14050
  req2.end();
14007
14051
  });
14008
14052
  if (result.ok) {
14009
- console.log(chalk25.green("\u2713 Flight Recorder buffer cleared."));
14053
+ console.log(chalk26.green("\u2713 Flight Recorder buffer cleared."));
14010
14054
  } else if (result.code === "ECONNREFUSED") {
14011
14055
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
14012
14056
  } else if (result.code === "ETIMEDOUT") {
@@ -14050,7 +14094,7 @@ async function startTail(options = {}) {
14050
14094
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
14051
14095
  if (channel) {
14052
14096
  toggleApprover(channel);
14053
- console.log(chalk25.dim(` Approvers: ${approverStatusLine()}`));
14097
+ console.log(chalk26.dim(` Approvers: ${approverStatusLine()}`));
14054
14098
  }
14055
14099
  };
14056
14100
  process.stdin.on("keypress", idleKeypressHandler);
@@ -14116,7 +14160,7 @@ async function startTail(options = {}) {
14116
14160
  localAllowCounts.get(req2.toolName) ?? 0
14117
14161
  )
14118
14162
  );
14119
- 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");
14120
14164
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
14121
14165
  for (const line of stampedLines) process.stdout.write(line + "\n");
14122
14166
  process.stdout.write(SHOW_CURSOR);
@@ -14144,8 +14188,8 @@ async function startTail(options = {}) {
14144
14188
  }
14145
14189
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
14146
14190
  try {
14147
- fs39.appendFileSync(
14148
- path42.join(os35.homedir(), ".node9", "hook-debug.log"),
14191
+ fs40.appendFileSync(
14192
+ path43.join(os36.homedir(), ".node9", "hook-debug.log"),
14149
14193
  `[tail] POST /decision failed: ${String(err2)}
14150
14194
  `
14151
14195
  );
@@ -14167,7 +14211,7 @@ async function startTail(options = {}) {
14167
14211
  );
14168
14212
  const stampedLines = buildCardLines(req2, priorCount);
14169
14213
  if (externalDecision) {
14170
- 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");
14171
14215
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
14172
14216
  }
14173
14217
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -14226,31 +14270,31 @@ async function startTail(options = {}) {
14226
14270
  }
14227
14271
  } catch {
14228
14272
  }
14229
- const auditLog = path42.join(os35.homedir(), ".node9", "audit.log");
14273
+ const auditLog = path43.join(os36.homedir(), ".node9", "audit.log");
14230
14274
  try {
14231
- 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;
14232
14276
  if (unackedDlp > 0) {
14233
14277
  console.log("");
14234
14278
  console.log(
14235
- chalk25.bgRed.white.bold(
14279
+ chalk26.bgRed.white.bold(
14236
14280
  ` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
14237
14281
  )
14238
14282
  );
14239
14283
  }
14240
14284
  } catch {
14241
14285
  }
14242
- console.log(chalk25.cyan.bold(`
14243
- \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}`));
14244
14288
  if (canApprove) {
14245
- console.log(chalk25.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
14246
- 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`));
14247
14291
  }
14248
14292
  const ctxStat = readSessionUsage();
14249
14293
  if (ctxStat) console.log(" " + formatContextStat(ctxStat));
14250
14294
  if (options.history) {
14251
- console.log(chalk25.dim("Showing history + live events.\n"));
14295
+ console.log(chalk26.dim("Showing history + live events.\n"));
14252
14296
  } else {
14253
- 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"));
14254
14298
  }
14255
14299
  process.on("SIGINT", () => {
14256
14300
  exitIdleMode();
@@ -14260,13 +14304,13 @@ async function startTail(options = {}) {
14260
14304
  readline5.clearLine(process.stdout, 0);
14261
14305
  readline5.cursorTo(process.stdout, 0);
14262
14306
  }
14263
- console.log(chalk25.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14307
+ console.log(chalk26.dim("\n\u{1F6F0}\uFE0F Disconnected."));
14264
14308
  process.exit(0);
14265
14309
  });
14266
14310
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
14267
14311
  const req = http2.get(sseUrl, (res) => {
14268
14312
  if (res.statusCode !== 200) {
14269
- console.error(chalk25.red(`Failed to connect: HTTP ${res.statusCode}`));
14313
+ console.error(chalk26.red(`Failed to connect: HTTP ${res.statusCode}`));
14270
14314
  process.exit(1);
14271
14315
  }
14272
14316
  if (canApprove) enterIdleMode();
@@ -14297,7 +14341,7 @@ async function startTail(options = {}) {
14297
14341
  readline5.clearLine(process.stdout, 0);
14298
14342
  readline5.cursorTo(process.stdout, 0);
14299
14343
  }
14300
- console.log(chalk25.red("\n\u274C Daemon disconnected."));
14344
+ console.log(chalk26.red("\n\u274C Daemon disconnected."));
14301
14345
  process.exit(1);
14302
14346
  });
14303
14347
  });
@@ -14389,9 +14433,9 @@ async function startTail(options = {}) {
14389
14433
  const hash = data.hash ?? "";
14390
14434
  const summary = data.argsSummary ?? data.tool;
14391
14435
  const fileCount = data.fileCount ?? 0;
14392
- 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"}`) : "";
14393
14437
  process.stdout.write(
14394
- `${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}
14395
14439
  `
14396
14440
  );
14397
14441
  return;
@@ -14408,7 +14452,7 @@ async function startTail(options = {}) {
14408
14452
  }
14409
14453
  req.on("error", (err2) => {
14410
14454
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
14411
- console.error(chalk25.red(`
14455
+ console.error(chalk26.red(`
14412
14456
  \u274C ${msg}`));
14413
14457
  process.exit(1);
14414
14458
  });
@@ -14420,7 +14464,7 @@ var init_tail = __esm({
14420
14464
  init_daemon2();
14421
14465
  init_daemon();
14422
14466
  init_core();
14423
- PID_FILE = path42.join(os35.homedir(), ".node9", "daemon.pid");
14467
+ PID_FILE = path43.join(os36.homedir(), ".node9", "daemon.pid");
14424
14468
  ICONS = {
14425
14469
  bash: "\u{1F4BB}",
14426
14470
  shell: "\u{1F4BB}",
@@ -14468,9 +14512,9 @@ __export(hud_exports, {
14468
14512
  main: () => main,
14469
14513
  renderEnvironmentLine: () => renderEnvironmentLine
14470
14514
  });
14471
- import fs40 from "fs";
14472
- import path43 from "path";
14473
- import os36 from "os";
14515
+ import fs41 from "fs";
14516
+ import path44 from "path";
14517
+ import os37 from "os";
14474
14518
  import http3 from "http";
14475
14519
  async function readStdin() {
14476
14520
  const chunks = [];
@@ -14546,9 +14590,9 @@ function formatTimeLeft(resetsAt) {
14546
14590
  return ` (${m}m left)`;
14547
14591
  }
14548
14592
  function safeReadJson(filePath) {
14549
- if (!fs40.existsSync(filePath)) return null;
14593
+ if (!fs41.existsSync(filePath)) return null;
14550
14594
  try {
14551
- return JSON.parse(fs40.readFileSync(filePath, "utf-8"));
14595
+ return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
14552
14596
  } catch {
14553
14597
  return null;
14554
14598
  }
@@ -14569,12 +14613,12 @@ function countHooksInFile(filePath) {
14569
14613
  return Object.keys(cfg.hooks).length;
14570
14614
  }
14571
14615
  function countRulesInDir(rulesDir) {
14572
- if (!fs40.existsSync(rulesDir)) return 0;
14616
+ if (!fs41.existsSync(rulesDir)) return 0;
14573
14617
  let count = 0;
14574
14618
  try {
14575
- for (const entry of fs40.readdirSync(rulesDir, { withFileTypes: true })) {
14619
+ for (const entry of fs41.readdirSync(rulesDir, { withFileTypes: true })) {
14576
14620
  if (entry.isDirectory()) {
14577
- count += countRulesInDir(path43.join(rulesDir, entry.name));
14621
+ count += countRulesInDir(path44.join(rulesDir, entry.name));
14578
14622
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
14579
14623
  count++;
14580
14624
  }
@@ -14585,46 +14629,46 @@ function countRulesInDir(rulesDir) {
14585
14629
  }
14586
14630
  function isSamePath(a, b) {
14587
14631
  try {
14588
- return path43.resolve(a) === path43.resolve(b);
14632
+ return path44.resolve(a) === path44.resolve(b);
14589
14633
  } catch {
14590
14634
  return false;
14591
14635
  }
14592
14636
  }
14593
14637
  function countConfigs(cwd) {
14594
- const homeDir2 = os36.homedir();
14595
- const claudeDir = path43.join(homeDir2, ".claude");
14638
+ const homeDir2 = os37.homedir();
14639
+ const claudeDir = path44.join(homeDir2, ".claude");
14596
14640
  let claudeMdCount = 0;
14597
14641
  let rulesCount = 0;
14598
14642
  let hooksCount = 0;
14599
14643
  const userMcpServers = /* @__PURE__ */ new Set();
14600
14644
  const projectMcpServers = /* @__PURE__ */ new Set();
14601
- if (fs40.existsSync(path43.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
14602
- rulesCount += countRulesInDir(path43.join(claudeDir, "rules"));
14603
- 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");
14604
14648
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
14605
14649
  hooksCount += countHooksInFile(userSettings);
14606
- const userClaudeJson = path43.join(homeDir2, ".claude.json");
14650
+ const userClaudeJson = path44.join(homeDir2, ".claude.json");
14607
14651
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
14608
14652
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
14609
14653
  userMcpServers.delete(name);
14610
14654
  }
14611
14655
  if (cwd) {
14612
- if (fs40.existsSync(path43.join(cwd, "CLAUDE.md"))) claudeMdCount++;
14613
- if (fs40.existsSync(path43.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
14614
- 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");
14615
14659
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
14616
14660
  if (!overlapsUserScope) {
14617
- if (fs40.existsSync(path43.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
14618
- rulesCount += countRulesInDir(path43.join(projectClaudeDir, "rules"));
14619
- 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");
14620
14664
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
14621
14665
  hooksCount += countHooksInFile(projSettings);
14622
14666
  }
14623
- if (fs40.existsSync(path43.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
14624
- 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");
14625
14669
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
14626
14670
  hooksCount += countHooksInFile(localSettings);
14627
- const mcpJsonServers = getMcpServerNames(path43.join(cwd, ".mcp.json"));
14671
+ const mcpJsonServers = getMcpServerNames(path44.join(cwd, ".mcp.json"));
14628
14672
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
14629
14673
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
14630
14674
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -14657,12 +14701,12 @@ function readActiveShieldsHud() {
14657
14701
  return shieldsCache.value;
14658
14702
  }
14659
14703
  try {
14660
- const shieldsPath = path43.join(os36.homedir(), ".node9", "shields.json");
14661
- if (!fs40.existsSync(shieldsPath)) {
14704
+ const shieldsPath = path44.join(os37.homedir(), ".node9", "shields.json");
14705
+ if (!fs41.existsSync(shieldsPath)) {
14662
14706
  shieldsCache = { value: [], ts: now };
14663
14707
  return [];
14664
14708
  }
14665
- const parsed = JSON.parse(fs40.readFileSync(shieldsPath, "utf-8"));
14709
+ const parsed = JSON.parse(fs41.readFileSync(shieldsPath, "utf-8"));
14666
14710
  if (!Array.isArray(parsed.active)) {
14667
14711
  shieldsCache = { value: [], ts: now };
14668
14712
  return [];
@@ -14764,17 +14808,17 @@ function renderContextLine(stdin) {
14764
14808
  async function main() {
14765
14809
  try {
14766
14810
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
14767
- if (fs40.existsSync(path43.join(os36.homedir(), ".node9", "hud-debug"))) {
14811
+ if (fs41.existsSync(path44.join(os37.homedir(), ".node9", "hud-debug"))) {
14768
14812
  try {
14769
- const logPath = path43.join(os36.homedir(), ".node9", "hud-debug.log");
14813
+ const logPath = path44.join(os37.homedir(), ".node9", "hud-debug.log");
14770
14814
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
14771
14815
  let size = 0;
14772
14816
  try {
14773
- size = fs40.statSync(logPath).size;
14817
+ size = fs41.statSync(logPath).size;
14774
14818
  } catch {
14775
14819
  }
14776
14820
  if (size < MAX_LOG_SIZE) {
14777
- fs40.appendFileSync(
14821
+ fs41.appendFileSync(
14778
14822
  logPath,
14779
14823
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
14780
14824
  );
@@ -14795,11 +14839,11 @@ async function main() {
14795
14839
  try {
14796
14840
  const cwd = stdin.cwd ?? process.cwd();
14797
14841
  for (const configPath of [
14798
- path43.join(cwd, "node9.config.json"),
14799
- path43.join(os36.homedir(), ".node9", "config.json")
14842
+ path44.join(cwd, "node9.config.json"),
14843
+ path44.join(os37.homedir(), ".node9", "config.json")
14800
14844
  ]) {
14801
- if (!fs40.existsSync(configPath)) continue;
14802
- 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"));
14803
14847
  const hud = cfg.settings?.hud;
14804
14848
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
14805
14849
  }
@@ -14845,10 +14889,10 @@ init_core();
14845
14889
  init_setup();
14846
14890
  init_daemon2();
14847
14891
  import { Command } from "commander";
14848
- import chalk26 from "chalk";
14849
- import fs41 from "fs";
14850
- import path44 from "path";
14851
- import os37 from "os";
14892
+ import chalk27 from "chalk";
14893
+ import fs42 from "fs";
14894
+ import path45 from "path";
14895
+ import os38 from "os";
14852
14896
  import { confirm as confirm2 } from "@inquirer/prompts";
14853
14897
 
14854
14898
  // src/utils/duration.ts
@@ -20558,22 +20602,207 @@ function registerDlpCommand(program2) {
20558
20602
  });
20559
20603
  }
20560
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
+
20561
20790
  // src/cli.ts
20562
20791
  var { version } = JSON.parse(
20563
- fs41.readFileSync(path44.join(__dirname, "../package.json"), "utf-8")
20792
+ fs42.readFileSync(path45.join(__dirname, "../package.json"), "utf-8")
20564
20793
  );
20565
20794
  var program = new Command();
20566
20795
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
20567
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) => {
20568
20797
  const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
20569
- const credPath = path44.join(os37.homedir(), ".node9", "credentials.json");
20570
- if (!fs41.existsSync(path44.dirname(credPath)))
20571
- 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 });
20572
20801
  const profileName = options.profile || "default";
20573
20802
  let existingCreds = {};
20574
20803
  try {
20575
- if (fs41.existsSync(credPath)) {
20576
- const raw = JSON.parse(fs41.readFileSync(credPath, "utf-8"));
20804
+ if (fs42.existsSync(credPath)) {
20805
+ const raw = JSON.parse(fs42.readFileSync(credPath, "utf-8"));
20577
20806
  if (raw.apiKey) {
20578
20807
  existingCreds = {
20579
20808
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
@@ -20585,13 +20814,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20585
20814
  } catch {
20586
20815
  }
20587
20816
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
20588
- fs41.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20817
+ fs42.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
20589
20818
  if (profileName === "default") {
20590
- const configPath = path44.join(os37.homedir(), ".node9", "config.json");
20819
+ const configPath = path45.join(os38.homedir(), ".node9", "config.json");
20591
20820
  let config = {};
20592
20821
  try {
20593
- if (fs41.existsSync(configPath))
20594
- config = JSON.parse(fs41.readFileSync(configPath, "utf-8"));
20822
+ if (fs42.existsSync(configPath))
20823
+ config = JSON.parse(fs42.readFileSync(configPath, "utf-8"));
20595
20824
  } catch {
20596
20825
  }
20597
20826
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -20606,19 +20835,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
20606
20835
  approvers.cloud = false;
20607
20836
  }
20608
20837
  s.approvers = approvers;
20609
- if (!fs41.existsSync(path44.dirname(configPath)))
20610
- fs41.mkdirSync(path44.dirname(configPath), { recursive: true });
20611
- 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 });
20612
20841
  }
20613
20842
  if (options.profile && profileName !== "default") {
20614
- console.log(chalk26.green(`\u2705 Profile "${profileName}" saved`));
20615
- 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`));
20616
20845
  } else if (options.local) {
20617
- console.log(chalk26.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
20618
- 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.`));
20619
20848
  } else {
20620
- console.log(chalk26.green(`\u2705 Logged in \u2014 agent mode`));
20621
- 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.`));
20622
20851
  }
20623
20852
  });
20624
20853
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
@@ -20636,7 +20865,7 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
20636
20865
  if (target === "vscode") return await setupVSCode();
20637
20866
  if (target === "hud") return setupHud();
20638
20867
  console.error(
20639
- chalk26.red(
20868
+ chalk27.red(
20640
20869
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20641
20870
  )
20642
20871
  );
@@ -20650,17 +20879,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20650
20879
  "The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
20651
20880
  ).action(async (target) => {
20652
20881
  if (!target) {
20653
- console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
20654
- 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");
20655
20884
  console.log(" Targets:");
20656
- console.log(" " + chalk26.green("claude") + " \u2014 Claude Code (hook mode)");
20657
- console.log(" " + chalk26.green("gemini") + " \u2014 Gemini CLI (hook mode)");
20658
- console.log(" " + chalk26.green("cursor") + " \u2014 Cursor (MCP proxy)");
20659
- console.log(" " + chalk26.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
20660
- console.log(" " + chalk26.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
20661
- 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)");
20662
20891
  process.stdout.write(
20663
- " " + chalk26.green("hud") + " \u2014 Claude Code security statusline\n"
20892
+ " " + chalk27.green("hud") + " \u2014 Claude Code security statusline\n"
20664
20893
  );
20665
20894
  console.log("");
20666
20895
  return;
@@ -20674,7 +20903,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
20674
20903
  if (t === "vscode") return await setupVSCode();
20675
20904
  if (t === "hud") return setupHud();
20676
20905
  console.error(
20677
- chalk26.red(
20906
+ chalk27.red(
20678
20907
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20679
20908
  )
20680
20909
  );
@@ -20697,33 +20926,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
20697
20926
  else if (target === "hud") fn = teardownHud;
20698
20927
  else {
20699
20928
  console.error(
20700
- chalk26.red(
20929
+ chalk27.red(
20701
20930
  `Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
20702
20931
  )
20703
20932
  );
20704
20933
  process.exit(1);
20705
20934
  }
20706
- console.log(chalk26.cyan(`
20935
+ console.log(chalk27.cyan(`
20707
20936
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
20708
20937
  `));
20709
20938
  try {
20710
20939
  fn();
20711
20940
  } catch (err2) {
20712
- 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)}`));
20713
20942
  process.exit(1);
20714
20943
  }
20715
- 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."));
20716
20945
  });
20717
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) => {
20718
- console.log(chalk26.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
20719
- 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..."));
20720
20949
  try {
20721
20950
  stopDaemon();
20722
- console.log(chalk26.green(" \u2705 Daemon stopped"));
20951
+ console.log(chalk27.green(" \u2705 Daemon stopped"));
20723
20952
  } catch {
20724
- console.log(chalk26.blue(" \u2139\uFE0F Daemon was not running"));
20953
+ console.log(chalk27.blue(" \u2139\uFE0F Daemon was not running"));
20725
20954
  }
20726
- console.log(chalk26.bold("\nRemoving hooks..."));
20955
+ console.log(chalk27.bold("\nRemoving hooks..."));
20727
20956
  let teardownFailed = false;
20728
20957
  for (const [label, fn] of [
20729
20958
  ["Claude", teardownClaude],
@@ -20738,45 +20967,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
20738
20967
  } catch (err2) {
20739
20968
  teardownFailed = true;
20740
20969
  console.error(
20741
- chalk26.red(
20970
+ chalk27.red(
20742
20971
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
20743
20972
  )
20744
20973
  );
20745
20974
  }
20746
20975
  }
20747
20976
  if (options.purge) {
20748
- const node9Dir = path44.join(os37.homedir(), ".node9");
20749
- if (fs41.existsSync(node9Dir)) {
20977
+ const node9Dir = path45.join(os38.homedir(), ".node9");
20978
+ if (fs42.existsSync(node9Dir)) {
20750
20979
  const confirmed = await confirm2({
20751
20980
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
20752
20981
  default: false
20753
20982
  });
20754
20983
  if (confirmed) {
20755
- fs41.rmSync(node9Dir, { recursive: true });
20756
- if (fs41.existsSync(node9Dir)) {
20984
+ fs42.rmSync(node9Dir, { recursive: true });
20985
+ if (fs42.existsSync(node9Dir)) {
20757
20986
  console.error(
20758
- 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.")
20759
20988
  );
20760
20989
  } else {
20761
- 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)"));
20762
20991
  }
20763
20992
  } else {
20764
- console.log(chalk26.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
20993
+ console.log(chalk27.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
20765
20994
  }
20766
20995
  } else {
20767
- 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"));
20768
20997
  }
20769
20998
  } else {
20770
20999
  console.log(
20771
- 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")
20772
21001
  );
20773
21002
  }
20774
21003
  if (teardownFailed) {
20775
- 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."));
20776
21005
  process.exit(1);
20777
21006
  }
20778
- console.log(chalk26.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
20779
- 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"));
20780
21009
  });
20781
21010
  registerDoctorCommand(program, version);
20782
21011
  program.command("explain").description(
@@ -20789,7 +21018,7 @@ program.command("explain").description(
20789
21018
  try {
20790
21019
  args = JSON.parse(trimmed);
20791
21020
  } catch {
20792
- console.error(chalk26.red(`
21021
+ console.error(chalk27.red(`
20793
21022
  \u274C Invalid JSON: ${trimmed}
20794
21023
  `));
20795
21024
  process.exit(1);
@@ -20800,54 +21029,54 @@ program.command("explain").description(
20800
21029
  }
20801
21030
  const result = await explainPolicy(tool, args);
20802
21031
  console.log("");
20803
- console.log(chalk26.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
21032
+ console.log(chalk27.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
20804
21033
  console.log("");
20805
- console.log(` ${chalk26.bold("Tool:")} ${chalk26.white(result.tool)}`);
21034
+ console.log(` ${chalk27.bold("Tool:")} ${chalk27.white(result.tool)}`);
20806
21035
  if (argsRaw) {
20807
21036
  const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
20808
- console.log(` ${chalk26.bold("Input:")} ${chalk26.gray(preview2)}`);
21037
+ console.log(` ${chalk27.bold("Input:")} ${chalk27.gray(preview2)}`);
20809
21038
  }
20810
21039
  console.log("");
20811
- console.log(chalk26.bold("Config Sources (Waterfall):"));
21040
+ console.log(chalk27.bold("Config Sources (Waterfall):"));
20812
21041
  for (const tier of result.waterfall) {
20813
- const num3 = chalk26.gray(` ${tier.tier}.`);
21042
+ const num3 = chalk27.gray(` ${tier.tier}.`);
20814
21043
  const label = tier.label.padEnd(16);
20815
21044
  let statusStr;
20816
21045
  if (tier.tier === 1) {
20817
- statusStr = chalk26.gray(tier.note ?? "");
21046
+ statusStr = chalk27.gray(tier.note ?? "");
20818
21047
  } else if (tier.status === "active") {
20819
- const loc = tier.path ? chalk26.gray(tier.path) : "";
20820
- const note = tier.note ? chalk26.gray(`(${tier.note})`) : "";
20821
- 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 : "");
20822
21051
  } else {
20823
- statusStr = chalk26.gray("\u25CB " + (tier.note ?? "not found"));
21052
+ statusStr = chalk27.gray("\u25CB " + (tier.note ?? "not found"));
20824
21053
  }
20825
- console.log(`${num3} ${chalk26.white(label)} ${statusStr}`);
21054
+ console.log(`${num3} ${chalk27.white(label)} ${statusStr}`);
20826
21055
  }
20827
21056
  console.log("");
20828
- console.log(chalk26.bold("Policy Evaluation:"));
21057
+ console.log(chalk27.bold("Policy Evaluation:"));
20829
21058
  for (const step of result.steps) {
20830
21059
  const isFinal = step.isFinal;
20831
21060
  let icon;
20832
- if (step.outcome === "allow") icon = chalk26.green(" \u2705");
20833
- else if (step.outcome === "review") icon = chalk26.red(" \u{1F534}");
20834
- else if (step.outcome === "skip") icon = chalk26.gray(" \u2500 ");
20835
- 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 ");
20836
21065
  const name = step.name.padEnd(18);
20837
- const nameStr = isFinal ? chalk26.white.bold(name) : chalk26.white(name);
20838
- const detail = isFinal ? chalk26.white(step.detail) : chalk26.gray(step.detail);
20839
- 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") : "";
20840
21069
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
20841
21070
  }
20842
21071
  console.log("");
20843
21072
  if (result.decision === "allow") {
20844
- 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"));
20845
21074
  } else {
20846
21075
  console.log(
20847
- 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")
20848
21077
  );
20849
21078
  if (result.blockedByLabel) {
20850
- console.log(chalk26.gray(` Reason: ${result.blockedByLabel}`));
21079
+ console.log(chalk27.gray(` Reason: ${result.blockedByLabel}`));
20851
21080
  }
20852
21081
  }
20853
21082
  console.log("");
@@ -20862,7 +21091,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
20862
21091
  try {
20863
21092
  await startTail2(options);
20864
21093
  } catch (err2) {
20865
- 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)}`));
20866
21095
  process.exit(1);
20867
21096
  }
20868
21097
  });
@@ -20895,14 +21124,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
20895
21124
  Run "node9 addto claude" to register it as the statusLine.`
20896
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) => {
20897
21126
  if (subcommand === "debug") {
20898
- const flagFile = path44.join(os37.homedir(), ".node9", "hud-debug");
21127
+ const flagFile = path45.join(os38.homedir(), ".node9", "hud-debug");
20899
21128
  if (state === "on") {
20900
- fs41.mkdirSync(path44.dirname(flagFile), { recursive: true });
20901
- fs41.writeFileSync(flagFile, "");
21129
+ fs42.mkdirSync(path45.dirname(flagFile), { recursive: true });
21130
+ fs42.writeFileSync(flagFile, "");
20902
21131
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
20903
21132
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
20904
21133
  } else if (state === "off") {
20905
- if (fs41.existsSync(flagFile)) fs41.unlinkSync(flagFile);
21134
+ if (fs42.existsSync(flagFile)) fs42.unlinkSync(flagFile);
20906
21135
  console.log("HUD debug logging disabled.");
20907
21136
  } else {
20908
21137
  console.error("Usage: node9 hud debug on|off");
@@ -20917,7 +21146,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20917
21146
  const ms = parseDuration(options.duration);
20918
21147
  if (ms === null) {
20919
21148
  console.error(
20920
- chalk26.red(`
21149
+ chalk27.red(`
20921
21150
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
20922
21151
  `)
20923
21152
  );
@@ -20925,20 +21154,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
20925
21154
  }
20926
21155
  pauseNode9(ms, options.duration);
20927
21156
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
20928
- console.log(chalk26.yellow(`
21157
+ console.log(chalk27.yellow(`
20929
21158
  \u23F8 Node9 paused until ${expiresAt}`));
20930
- console.log(chalk26.gray(` All tool calls will be allowed without review.`));
20931
- 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.
20932
21161
  `));
20933
21162
  });
20934
21163
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
20935
21164
  const { paused } = checkPause();
20936
21165
  if (!paused) {
20937
- 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"));
20938
21167
  return;
20939
21168
  }
20940
21169
  resumeNode9();
20941
- 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"));
20942
21171
  });
20943
21172
  var HOOK_BASED_AGENTS = {
20944
21173
  claude: "claude",
@@ -20951,15 +21180,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20951
21180
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
20952
21181
  const target = HOOK_BASED_AGENTS[firstArg2];
20953
21182
  console.error(
20954
- chalk26.yellow(`
21183
+ chalk27.yellow(`
20955
21184
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
20956
21185
  );
20957
- console.error(chalk26.white(`
21186
+ console.error(chalk27.white(`
20958
21187
  "${target}" uses its own hook system. Use:`));
20959
21188
  console.error(
20960
- chalk26.green(` node9 addto ${target} `) + chalk26.gray("# one-time setup")
21189
+ chalk27.green(` node9 addto ${target} `) + chalk27.gray("# one-time setup")
20961
21190
  );
20962
- console.error(chalk26.green(` ${target} `) + chalk26.gray("# run normally"));
21191
+ console.error(chalk27.green(` ${target} `) + chalk27.gray("# run normally"));
20963
21192
  process.exit(1);
20964
21193
  }
20965
21194
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -20976,7 +21205,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20976
21205
  }
20977
21206
  );
20978
21207
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
20979
- 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..."));
20980
21209
  const daemonReady = await autoStartDaemonAndWait();
20981
21210
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
20982
21211
  }
@@ -20989,12 +21218,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
20989
21218
  }
20990
21219
  if (!result.approved) {
20991
21220
  console.error(
20992
- chalk26.red(`
21221
+ chalk27.red(`
20993
21222
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
20994
21223
  );
20995
21224
  process.exit(1);
20996
21225
  }
20997
- console.error(chalk26.green("\n\u2705 Approved \u2014 running command...\n"));
21226
+ console.error(chalk27.green("\n\u2705 Approved \u2014 running command...\n"));
20998
21227
  await runProxy(fullCommand);
20999
21228
  } else {
21000
21229
  program.help();
@@ -21009,14 +21238,15 @@ registerAgentsCommand(program);
21009
21238
  registerScanCommand(program);
21010
21239
  registerSessionsCommand(program);
21011
21240
  registerDlpCommand(program);
21241
+ registerMaskCommand(program);
21012
21242
  if (process.argv[2] !== "daemon") {
21013
21243
  process.on("unhandledRejection", (reason) => {
21014
21244
  const isCheckHook = process.argv[2] === "check";
21015
21245
  if (isCheckHook) {
21016
21246
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
21017
- const logPath = path44.join(os37.homedir(), ".node9", "hook-debug.log");
21247
+ const logPath = path45.join(os38.homedir(), ".node9", "hook-debug.log");
21018
21248
  const msg = reason instanceof Error ? reason.message : String(reason);
21019
- fs41.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
21249
+ fs42.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
21020
21250
  `);
21021
21251
  }
21022
21252
  process.exit(0);