@node9/proxy 2.43.0 → 2.44.0

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
@@ -5504,7 +5504,10 @@ function classify(inv) {
5504
5504
  out.push({ ...f, shown: inv.shown, reason: `${f.reason}: ${inv.shown}` });
5505
5505
  };
5506
5506
  if (hooksBypass(inv)) found(HOOKS_BYPASS);
5507
- if (stagesEverything(inv)) found(STAGE_ALL);
5507
+ const staged = stagesEverything(inv);
5508
+ if (staged) {
5509
+ found(staged.ifWorkTreeTop === void 0 ? STAGE_ALL : { ...STAGE_ALL, ...staged });
5510
+ }
5508
5511
  if (inv.sub === null) {
5509
5512
  found(UNREADABLE);
5510
5513
  return out;
@@ -5565,12 +5568,35 @@ function appliedValue(env, name) {
5565
5568
  const word = f?.Stmts?.[0]?.Cmd?.Args?.[1];
5566
5569
  return word ? resolveWordLiteral(word) : null;
5567
5570
  }
5571
+ function lexicalPath(p) {
5572
+ if (/^[\\/~$]/.test(p) || /^[A-Za-z]:/.test(p)) return null;
5573
+ const parts = [];
5574
+ let climbed = false;
5575
+ for (const part of p.split(/[\\/]+/)) {
5576
+ if (part === "" || part === ".") continue;
5577
+ if (part === "..") {
5578
+ if (parts.length === 0) climbed = true;
5579
+ else parts.pop();
5580
+ } else parts.push(part);
5581
+ }
5582
+ return { parts, climbed };
5583
+ }
5584
+ function hooksPathBypasses(value) {
5585
+ if (value === null || value === void 0) return true;
5586
+ const v = value.trim();
5587
+ if (v === "" || v === "true") return true;
5588
+ const lp = lexicalPath(v);
5589
+ if (lp === null || lp.climbed || lp.parts.length === 0) return true;
5590
+ return lp.parts[0] === ".git" && lp.parts[1] !== "hooks";
5591
+ }
5568
5592
  function hooksBypass(inv) {
5569
- if (inv.config.has("core.hookspath")) return true;
5593
+ if (inv.config.has("core.hookspath") && hooksPathBypasses(inv.config.get("core.hookspath")))
5594
+ return true;
5570
5595
  for (const [name, value] of inv.env) {
5571
5596
  if (name === "GIT_CONFIG_PARAMETERS" && value !== null && /core\.hookspath/i.test(value))
5572
5597
  return true;
5573
- if (/^GIT_CONFIG_KEY_\d+$/.test(name) && value !== null && value.toLowerCase() === "core.hookspath")
5598
+ const key = /^GIT_CONFIG_KEY_(\d+)$/.exec(name);
5599
+ if (key && value !== null && value.toLowerCase() === "core.hookspath" && hooksPathBypasses(inv.env.get(`GIT_CONFIG_VALUE_${key[1]}`)))
5574
5600
  return true;
5575
5601
  }
5576
5602
  for (const name of HOOK_KILL_SWITCHES) {
@@ -5612,7 +5638,10 @@ function configWritesHooksPath(args) {
5612
5638
  }
5613
5639
  const key = args[i];
5614
5640
  if (key === null || key === void 0 || key.toLowerCase() !== "core.hookspath") return false;
5615
- return write2 || i + 1 < args.length;
5641
+ const unset = args.slice(0, i).some((w) => w === "--unset" || w === "--unset-all") || args[0] === "unset";
5642
+ if (unset) return true;
5643
+ if (i + 1 < args.length) return hooksPathBypasses(args[i + 1]);
5644
+ return write2;
5616
5645
  }
5617
5646
  function isHookPath(word) {
5618
5647
  return /(^|[\\/])(\.git[\\/]hooks|\.husky)([\\/]|$)/.test(word);
@@ -5647,31 +5676,49 @@ function chmodDisarmsHook(args, shown) {
5647
5676
  function wholeTree(operand) {
5648
5677
  if (/^:\(top\)\.?$/.test(operand) || operand === ":/" || operand === "*") return true;
5649
5678
  if (operand.startsWith(":")) return false;
5679
+ const lp = lexicalPath(operand);
5680
+ return lp !== null && lp.parts.length === 0;
5681
+ }
5682
+ function chdirPrefix(inv) {
5683
+ const dirs = inv.chdir ?? [];
5684
+ if (dirs.length === 0) return null;
5650
5685
  const parts = [];
5651
- for (const part of operand.split("/")) {
5652
- if (!part || part === ".") continue;
5653
- if (part === "..") parts.pop();
5654
- else parts.push(part);
5686
+ for (const d of dirs) {
5687
+ if (d === null || d === "") return null;
5688
+ const lp = lexicalPath(d);
5689
+ if (lp === null || lp.climbed) return null;
5690
+ parts.push(...lp.parts);
5655
5691
  }
5656
- return parts.length === 0 && !operand.startsWith("/");
5692
+ return parts.length === 0 ? null : parts.join("/");
5657
5693
  }
5658
5694
  function stagesEverything(inv) {
5659
5695
  const sub = inv.sub?.toLowerCase();
5696
+ const prefix = chdirPrefix(inv);
5697
+ const judge = (operands2) => {
5698
+ let conditional = false;
5699
+ for (const operand of operands2) {
5700
+ if (!wholeTree(operand)) continue;
5701
+ if (prefix === null || operand.startsWith(":") || operand.startsWith("/")) return {};
5702
+ if (wholeTree(`${prefix}/${operand === "*" ? "." : operand}`)) return {};
5703
+ conditional = true;
5704
+ }
5705
+ return conditional && prefix !== null ? { ifWorkTreeTop: prefix } : null;
5706
+ };
5660
5707
  if (sub === "add") {
5661
5708
  const o = parseOptions(inv.args, "", ADD_LONG_VALUE);
5662
5709
  if (o.flags.has("-n") || o.flags.has("--dry-run") || o.flags.has("-i") || o.flags.has("--interactive") || o.flags.has("-p") || o.flags.has("--patch") || o.flags.has("-e") || o.flags.has("--edit"))
5663
- return false;
5710
+ return null;
5664
5711
  const all = o.flags.has("-A") || o.flags.has("--all") || o.flags.has("--no-ignore-removal") || o.flags.has("-u") || o.flags.has("--update");
5665
- if (all && o.operands.length === 0) return true;
5666
- return o.operands.some(wholeTree);
5712
+ if (all && o.operands.length === 0) return {};
5713
+ return judge(o.operands);
5667
5714
  }
5668
5715
  if (sub === "commit") {
5669
5716
  const o = parseOptions(inv.args, "mFCcut", COMMIT_LONG_VALUE);
5670
- if (o.flags.has("--dry-run")) return false;
5671
- if (o.flags.has("-a") || o.flags.has("--all")) return true;
5672
- return o.operands.some(wholeTree);
5717
+ if (o.flags.has("--dry-run")) return null;
5718
+ if (o.flags.has("-a") || o.flags.has("--all")) return {};
5719
+ return judge(o.operands);
5673
5720
  }
5674
- return false;
5721
+ return null;
5675
5722
  }
5676
5723
  function hookTextRunsNothing(text) {
5677
5724
  return text.split(/\r?\n/).every((line) => INERT_LINE_RE.test(line.trim()));
@@ -7724,7 +7771,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hostHooks =
7724
7771
  }
7725
7772
  async function evaluatePolicyCore(config, toolName, args, context = {}, hooks = {}) {
7726
7773
  const { agent, cwd, activeEnvironment } = context;
7727
- const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2, isConfiguredRegistry } = hooks;
7774
+ const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2, isConfiguredRegistry, isGitWorkTreeTop: isGitWorkTreeTop2 } = hooks;
7728
7775
  const settingsView = catalogSettings(config);
7729
7776
  const layers = inspectionLayers(config.policy);
7730
7777
  const cv = (id) => checkValue(settingsView, id);
@@ -7878,6 +7925,8 @@ async function evaluatePolicyCore(config, toolName, args, context = {}, hooks =
7878
7925
  const action = cv(id);
7879
7926
  const finding = action === "off" ? void 0 : git.findings.find((f) => f.checkId === id);
7880
7927
  if (!finding) continue;
7928
+ if (finding.ifWorkTreeTop !== void 0 && isGitWorkTreeTop2 && cwd && !isGitWorkTreeTop2(finding.ifWorkTreeTop, cwd))
7929
+ continue;
7881
7930
  const v = applyCheckValue(action, {
7882
7931
  decision: "review",
7883
7932
  blockedByLabel: `Node9 (AST): ${finding.ruleName}`,
@@ -17200,6 +17249,7 @@ zoo`.split("\n");
17200
17249
  /** The words after `git`: global options, then the subcommand. */
17201
17250
  visitGit(rest, env, depth, viaXargs, fromAlias, shown) {
17202
17251
  const config = /* @__PURE__ */ new Map();
17252
+ const chdir = [];
17203
17253
  let i = 0;
17204
17254
  while (i < rest.length) {
17205
17255
  const w = rest[i];
@@ -17214,6 +17264,7 @@ zoo`.split("\n");
17214
17264
  i++;
17215
17265
  value = i < rest.length ? rest[i] : null;
17216
17266
  }
17267
+ if (name === "-C") chdir.push(viaXargs || (value ?? "").includes("{}") ? null : value);
17217
17268
  if (name === "-c" && value !== null) {
17218
17269
  const ceq = value.indexOf("=");
17219
17270
  const key = (ceq > 0 ? value.slice(0, ceq) : value).toLowerCase();
@@ -17230,7 +17281,7 @@ zoo`.split("\n");
17230
17281
  }
17231
17282
  const subWord = i < rest.length ? rest[i] : void 0;
17232
17283
  const args = rest.slice(i + 1);
17233
- const inv = { sub: null, args, config, env, shown };
17284
+ const inv = { sub: null, args, config, env, shown, chdir };
17234
17285
  if (subWord === void 0) {
17235
17286
  if (!viaXargs) return;
17236
17287
  this.emit(inv);
@@ -17244,16 +17295,23 @@ zoo`.split("\n");
17244
17295
  const fromConfig = config.get(`alias.${subWord.toLowerCase()}`);
17245
17296
  const alias = fromConfig !== void 0 ? fromConfig : this.aliases.get(subWord);
17246
17297
  if (alias !== void 0 && !fromAlias) {
17247
- this.expandAlias(subWord, alias, args, env, depth, 0, shown);
17298
+ this.expandAlias(subWord, alias, args, env, depth, 0, shown, chdir);
17248
17299
  return;
17249
17300
  }
17250
17301
  inv.sub = subWord;
17251
17302
  this.emit(inv);
17252
17303
  this.recordAliasDefinition(inv, env, depth);
17253
17304
  }
17254
- expandAlias(name, value, args, env, depth, level, shown) {
17305
+ expandAlias(name, value, args, env, depth, level, shown, chdir) {
17255
17306
  if (value === null) {
17256
- this.emit({ sub: null, args, config: /* @__PURE__ */ new Map(), env, shown: `${shown} (alias ${name})` });
17307
+ this.emit({
17308
+ sub: null,
17309
+ args,
17310
+ config: /* @__PURE__ */ new Map(),
17311
+ env,
17312
+ shown: `${shown} (alias ${name})`,
17313
+ chdir
17314
+ });
17257
17315
  return;
17258
17316
  }
17259
17317
  if (value.startsWith("!")) {
@@ -17267,7 +17325,16 @@ zoo`.split("\n");
17267
17325
  const nested = this.aliases.get(sub);
17268
17326
  const expandedShown = `${shown} (alias ${name} = ${value})`;
17269
17327
  if (nested !== void 0 && level < MAX_ALIAS_DEPTH) {
17270
- this.expandAlias(sub, nested, [...words.slice(1), ...args], env, depth, level + 1, shown);
17328
+ this.expandAlias(
17329
+ sub,
17330
+ nested,
17331
+ [...words.slice(1), ...args],
17332
+ env,
17333
+ depth,
17334
+ level + 1,
17335
+ shown,
17336
+ chdir
17337
+ );
17271
17338
  return;
17272
17339
  }
17273
17340
  this.emit({
@@ -17275,7 +17342,8 @@ zoo`.split("\n");
17275
17342
  args: [...words.slice(1), ...args],
17276
17343
  config: /* @__PURE__ */ new Map(),
17277
17344
  env,
17278
- shown: expandedShown
17345
+ shown: expandedShown,
17346
+ chdir
17279
17347
  });
17280
17348
  }
17281
17349
  /**
@@ -17309,7 +17377,7 @@ zoo`.split("\n");
17309
17377
  if (i + 1 >= args.length) return;
17310
17378
  const value = args[i + 1];
17311
17379
  this.aliases.set(m[1], value);
17312
- this.expandAlias(m[1], value, [], env, depth, 0, `${inv.shown}`);
17380
+ this.expandAlias(m[1], value, [], env, depth, 0, `${inv.shown}`, []);
17313
17381
  }
17314
17382
  };
17315
17383
  POSITIONAL2 = "\0positional";
@@ -18855,7 +18923,7 @@ zoo`.split("\n");
18855
18923
  { view: "separators-stripped", decoder: "separators", stripped: true }
18856
18924
  ];
18857
18925
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
18858
- CANONICAL_EXTRACTOR_VERSION = "canonical-v34";
18926
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v35";
18859
18927
  DEDUPE_PREVIEW_LEN = 120;
18860
18928
  ENGINE_VERSION = "1.4.0";
18861
18929
  }
@@ -22932,10 +23000,19 @@ async function evaluatePolicy2(toolName, args, agent, cwd, opts) {
22932
23000
  // reaches a long-lived in-process authorizer (the gateway) within seconds.
22933
23001
  isTrustedHost: config.policy.trustedHostsManaged ? (host2) => matchesTrustedHost(host2, config.policy.trustedHosts) : isTrustedHost,
22934
23002
  readAgentSettings,
22935
- isConfiguredRegistry: (host2, at) => configuredRegistryHosts(at).has(host2)
23003
+ isConfiguredRegistry: (host2, at) => configuredRegistryHosts(at).has(host2),
23004
+ isGitWorkTreeTop
22936
23005
  }
22937
23006
  );
22938
23007
  }
23008
+ function isGitWorkTreeTop(dir2, cwd) {
23009
+ if (!path13.isAbsolute(cwd)) return true;
23010
+ try {
23011
+ return fs13.existsSync(path13.join(path13.resolve(cwd, dir2), ".git"));
23012
+ } catch {
23013
+ return true;
23014
+ }
23015
+ }
22939
23016
  function readAgentSettings(absPath) {
22940
23017
  const r = readCappedText(
22941
23018
  absPath,
@@ -31952,16 +32029,124 @@ var init_credentials = __esm({
31952
32029
  }
31953
32030
  });
31954
32031
 
31955
- // src/daemon/service.ts
32032
+ // src/daemon/startup-log.ts
31956
32033
  import fs38 from "fs";
31957
32034
  import path39 from "path";
31958
32035
  import os32 from "os";
32036
+ function capStartupLog(file2) {
32037
+ try {
32038
+ if (fs38.statSync(file2).size > MAX_STARTUP_LOG_BYTES) fs38.truncateSync(file2);
32039
+ } catch {
32040
+ }
32041
+ }
32042
+ function openStartupLogFd() {
32043
+ try {
32044
+ const file2 = DAEMON_STARTUP_LOG();
32045
+ const dir2 = path39.dirname(file2);
32046
+ if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32047
+ capStartupLog(file2);
32048
+ return fs38.openSync(file2, "a");
32049
+ } catch {
32050
+ return void 0;
32051
+ }
32052
+ }
32053
+ function recordStartupState(outcome, kind, detail) {
32054
+ try {
32055
+ if (outcome === "starting") {
32056
+ const prev = readStartupState();
32057
+ if (prev?.outcome === "starting") {
32058
+ const prevAt = new Date(prev.at).getTime();
32059
+ const age = Math.abs(Date.now() - prevAt);
32060
+ if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
32061
+ }
32062
+ }
32063
+ const file2 = DAEMON_STARTUP_STATE();
32064
+ const dir2 = path39.dirname(file2);
32065
+ if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32066
+ const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
32067
+ if (kind) state.kind = kind;
32068
+ if (detail) state.detail = detail.slice(0, MAX_DETAIL);
32069
+ const tmp = `${file2}.${process.pid}.tmp`;
32070
+ try {
32071
+ fs38.writeFileSync(tmp, JSON.stringify(state), "utf-8");
32072
+ fs38.renameSync(tmp, file2);
32073
+ } catch (err2) {
32074
+ try {
32075
+ fs38.unlinkSync(tmp);
32076
+ } catch {
32077
+ }
32078
+ throw err2;
32079
+ }
32080
+ } catch {
32081
+ }
32082
+ }
32083
+ function readStartupState() {
32084
+ try {
32085
+ const raw = fs38.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
32086
+ const s = JSON.parse(raw);
32087
+ if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
32088
+ return s;
32089
+ } catch {
32090
+ return null;
32091
+ }
32092
+ }
32093
+ function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
32094
+ const s = readStartupState();
32095
+ if (!s) return null;
32096
+ const at = new Date(s.at);
32097
+ if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
32098
+ switch (s.outcome) {
32099
+ case "ok":
32100
+ case "ok-elsewhere":
32101
+ return null;
32102
+ case "starting":
32103
+ if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
32104
+ return {
32105
+ kind: "did-not-start",
32106
+ detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
32107
+ at,
32108
+ // `at` is the FIRST attempt of the streak, not the most recent one.
32109
+ label: "start attempts failing since"
32110
+ };
32111
+ case "failed":
32112
+ return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
32113
+ default:
32114
+ return null;
32115
+ }
32116
+ }
32117
+ function logDaemonStartup(kind, detail) {
32118
+ try {
32119
+ const file2 = DAEMON_STARTUP_LOG();
32120
+ const dir2 = path39.dirname(file2);
32121
+ if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32122
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-startup:${kind}${detail ? ` ${detail}` : ""}
32123
+ `;
32124
+ fs38.appendFileSync(file2, line, "utf-8");
32125
+ } catch {
32126
+ }
32127
+ }
32128
+ var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
32129
+ var init_startup_log = __esm({
32130
+ "src/daemon/startup-log.ts"() {
32131
+ "use strict";
32132
+ DAEMON_STARTUP_LOG = () => path39.join(os32.homedir(), ".node9", "daemon-startup.log");
32133
+ MAX_STARTUP_LOG_BYTES = 256 * 1024;
32134
+ DAEMON_STARTUP_STATE = () => path39.join(os32.homedir(), ".node9", "daemon-startup-state.json");
32135
+ MAX_DETAIL = 200;
32136
+ STARTING_GRACE_MS = 90 * 1e3;
32137
+ }
32138
+ });
32139
+
32140
+ // src/daemon/service.ts
32141
+ import fs39 from "fs";
32142
+ import path40 from "path";
32143
+ import os33 from "os";
31959
32144
  import { spawnSync as spawnSync2, execFileSync } from "child_process";
31960
32145
  function resolveNode9Binary() {
31961
32146
  try {
31962
32147
  const script = process.argv[1];
31963
- if (typeof script === "string" && path39.isAbsolute(script) && fs38.existsSync(script)) {
31964
- return fs38.realpathSync(script);
32148
+ if (typeof script === "string" && path40.isAbsolute(script) && fs39.existsSync(script)) {
32149
+ return fs39.realpathSync(script);
31965
32150
  }
31966
32151
  } catch {
31967
32152
  }
@@ -31985,11 +32170,11 @@ function daemonServiceEnv() {
31985
32170
  return env;
31986
32171
  }
31987
32172
  function launchdPlist(binaryPath) {
31988
- const logDir = path39.join(os32.homedir(), ".node9");
32173
+ const logDir = path40.join(os33.homedir(), ".node9");
31989
32174
  const nodePath = xmlEscape(process.execPath);
31990
32175
  const scriptPath = xmlEscape(binaryPath);
31991
- const outLog = xmlEscape(path39.join(logDir, "daemon.log"));
31992
- const errLog = xmlEscape(path39.join(logDir, "daemon-error.log"));
32176
+ const outLog = xmlEscape(path40.join(logDir, "daemon.log"));
32177
+ const errLog = xmlEscape(path40.join(logDir, "daemon-error.log"));
31993
32178
  const envEntries = Object.entries(daemonServiceEnv()).map(([k, v]) => ` <key>${xmlEscape(k)}</key>
31994
32179
  <string>${xmlEscape(v)}</string>`).join("\n");
31995
32180
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -32023,9 +32208,9 @@ ${envEntries}
32023
32208
  `;
32024
32209
  }
32025
32210
  function installLaunchd(binaryPath) {
32026
- const dir2 = path39.dirname(LAUNCHD_PLIST);
32027
- if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32028
- fs38.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
32211
+ const dir2 = path40.dirname(LAUNCHD_PLIST);
32212
+ if (!fs39.existsSync(dir2)) fs39.mkdirSync(dir2, { recursive: true });
32213
+ fs39.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
32029
32214
  spawnSync2("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
32030
32215
  const r = spawnSync2("launchctl", ["load", "-w", LAUNCHD_PLIST], {
32031
32216
  encoding: "utf8",
@@ -32036,13 +32221,13 @@ function installLaunchd(binaryPath) {
32036
32221
  }
32037
32222
  }
32038
32223
  function uninstallLaunchd() {
32039
- if (fs38.existsSync(LAUNCHD_PLIST)) {
32224
+ if (fs39.existsSync(LAUNCHD_PLIST)) {
32040
32225
  spawnSync2("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
32041
- fs38.unlinkSync(LAUNCHD_PLIST);
32226
+ fs39.unlinkSync(LAUNCHD_PLIST);
32042
32227
  }
32043
32228
  }
32044
32229
  function isLaunchdInstalled() {
32045
- return fs38.existsSync(LAUNCHD_PLIST);
32230
+ return fs39.existsSync(LAUNCHD_PLIST);
32046
32231
  }
32047
32232
  function systemdUnit(binaryPath) {
32048
32233
  const envLines = Object.entries(daemonServiceEnv()).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
@@ -32062,12 +32247,12 @@ WantedBy=default.target
32062
32247
  `;
32063
32248
  }
32064
32249
  function installSystemd(binaryPath) {
32065
- if (!fs38.existsSync(SYSTEMD_UNIT_DIR)) {
32066
- fs38.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
32250
+ if (!fs39.existsSync(SYSTEMD_UNIT_DIR)) {
32251
+ fs39.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
32067
32252
  }
32068
- fs38.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
32253
+ fs39.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
32069
32254
  try {
32070
- execFileSync("loginctl", ["enable-linger", os32.userInfo().username], { timeout: 3e3 });
32255
+ execFileSync("loginctl", ["enable-linger", os33.userInfo().username], { timeout: 3e3 });
32071
32256
  } catch {
32072
32257
  }
32073
32258
  const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
@@ -32087,21 +32272,21 @@ function installSystemd(binaryPath) {
32087
32272
  }
32088
32273
  }
32089
32274
  function uninstallSystemd() {
32090
- if (fs38.existsSync(SYSTEMD_UNIT)) {
32275
+ if (fs39.existsSync(SYSTEMD_UNIT)) {
32091
32276
  spawnSync2("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
32092
32277
  encoding: "utf8",
32093
32278
  timeout: 5e3
32094
32279
  });
32095
32280
  spawnSync2("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
32096
- fs38.unlinkSync(SYSTEMD_UNIT);
32281
+ fs39.unlinkSync(SYSTEMD_UNIT);
32097
32282
  }
32098
32283
  }
32099
32284
  function isSystemdInstalled() {
32100
- return fs38.existsSync(SYSTEMD_UNIT);
32285
+ return fs39.existsSync(SYSTEMD_UNIT);
32101
32286
  }
32102
- function windowsStartupDir(homeDir2 = os32.homedir()) {
32103
- const appData = process.env.APPDATA || path39.join(homeDir2, "AppData", "Roaming");
32104
- return path39.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
32287
+ function windowsStartupDir(homeDir2 = os33.homedir()) {
32288
+ const appData = process.env.APPDATA || path40.join(homeDir2, "AppData", "Roaming");
32289
+ return path40.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
32105
32290
  }
32106
32291
  function windowsLauncherVbs(nodePath, scriptPath) {
32107
32292
  for (const p of [nodePath, scriptPath]) {
@@ -32121,23 +32306,23 @@ function windowsLauncherVbs(nodePath, scriptPath) {
32121
32306
  }
32122
32307
  function installWindowsStartup(binaryPath) {
32123
32308
  const target = WIN_LAUNCHER();
32124
- const dir2 = path39.dirname(target);
32125
- if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32126
- fs38.writeFileSync(target, windowsLauncherVbs(process.execPath, binaryPath), "utf-8");
32309
+ const dir2 = path40.dirname(target);
32310
+ if (!fs39.existsSync(dir2)) fs39.mkdirSync(dir2, { recursive: true });
32311
+ fs39.writeFileSync(target, windowsLauncherVbs(process.execPath, binaryPath), "utf-8");
32127
32312
  spawnSync2("wscript.exe", ["//B", target], { encoding: "utf8", timeout: 1e4 });
32128
32313
  }
32129
32314
  function uninstallWindowsStartup() {
32130
32315
  const target = WIN_LAUNCHER();
32131
- if (fs38.existsSync(target)) fs38.unlinkSync(target);
32316
+ if (fs39.existsSync(target)) fs39.unlinkSync(target);
32132
32317
  }
32133
32318
  function isWindowsStartupInstalled() {
32134
- return fs38.existsSync(WIN_LAUNCHER());
32319
+ return fs39.existsSync(WIN_LAUNCHER());
32135
32320
  }
32136
32321
  function stopRunningDaemon() {
32137
- const pidFile = path39.join(os32.homedir(), ".node9", "daemon.pid");
32138
- if (!fs38.existsSync(pidFile)) return;
32322
+ const pidFile = path40.join(os33.homedir(), ".node9", "daemon.pid");
32323
+ if (!fs39.existsSync(pidFile)) return;
32139
32324
  try {
32140
- const data = JSON.parse(fs38.readFileSync(pidFile, "utf-8"));
32325
+ const data = JSON.parse(fs39.readFileSync(pidFile, "utf-8"));
32141
32326
  const pid = data.pid;
32142
32327
  const MAX_PID2 = 4194304;
32143
32328
  if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
@@ -32157,7 +32342,7 @@ function stopRunningDaemon() {
32157
32342
  }
32158
32343
  }
32159
32344
  try {
32160
- fs38.unlinkSync(pidFile);
32345
+ fs39.unlinkSync(pidFile);
32161
32346
  } catch {
32162
32347
  }
32163
32348
  } catch {
@@ -32250,7 +32435,7 @@ function enableDaemonServiceQuiet() {
32250
32435
  if (process.platform === "linux") {
32251
32436
  const r = spawnSync2("systemctl", ["--user", "enable", "node9-daemon"], {
32252
32437
  encoding: "utf8",
32253
- timeout: 3e3
32438
+ timeout: 1e4
32254
32439
  });
32255
32440
  return r.status === 0;
32256
32441
  }
@@ -32268,19 +32453,35 @@ function ensureAutostartHealthy(autoStartDaemon) {
32268
32453
  enabled: isDaemonServiceEnabled(),
32269
32454
  autoStartDaemon
32270
32455
  });
32271
- if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "skipped";
32456
+ if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "failed";
32272
32457
  return decision === "ok" ? "ok" : decision === "unsupported" ? "unsupported" : "skipped";
32273
32458
  }
32274
- function scheduleAutostartSelfHeal(autoStartDaemon) {
32459
+ function logAutostartHeal(result) {
32460
+ if (result !== "repaired" && result !== "failed") return;
32461
+ try {
32462
+ const file2 = DAEMON_STARTUP_LOG();
32463
+ fs39.mkdirSync(path40.dirname(file2), { recursive: true });
32464
+ capStartupLog(file2);
32465
+ fs39.appendFileSync(file2, `[${(/* @__PURE__ */ new Date()).toISOString()}] [autostart-heal] ${result}
32466
+ `);
32467
+ } catch {
32468
+ }
32469
+ }
32470
+ function scheduleAutostartSelfHeal(autoStartDaemon, heal = ensureAutostartHealthy) {
32275
32471
  if (process.env.NODE9_TESTING === "1") return;
32472
+ let failures = 0;
32276
32473
  const run = () => {
32474
+ let result = "skipped";
32277
32475
  try {
32278
- ensureAutostartHealthy(autoStartDaemon());
32476
+ result = heal(autoStartDaemon());
32279
32477
  } catch {
32280
32478
  }
32479
+ logAutostartHeal(result);
32480
+ failures = result === "failed" ? failures + 1 : 0;
32481
+ const next = failures > 0 && failures <= AUTOSTART_HEAL_RETRY_MS.length ? AUTOSTART_HEAL_RETRY_MS[failures - 1] : AUTOSTART_HEAL_INTERVAL_MS;
32482
+ setTimeout(run, next).unref();
32281
32483
  };
32282
32484
  setTimeout(run, 6e4).unref();
32283
- setInterval(run, AUTOSTART_HEAL_INTERVAL_MS).unref();
32284
32485
  }
32285
32486
  function restartDaemonService() {
32286
32487
  try {
@@ -32355,39 +32556,41 @@ function autostartState() {
32355
32556
  if (!isDaemonServiceInstalled()) return "absent";
32356
32557
  return isDaemonServiceEnabled() ? "enabled" : "disabled";
32357
32558
  }
32358
- var LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT, STARTUP_FILE, WIN_LAUNCHER, AUTOSTART_HEAL_INTERVAL_MS;
32559
+ var LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT, STARTUP_FILE, WIN_LAUNCHER, AUTOSTART_HEAL_INTERVAL_MS, AUTOSTART_HEAL_RETRY_MS;
32359
32560
  var init_service = __esm({
32360
32561
  "src/daemon/service.ts"() {
32361
32562
  "use strict";
32563
+ init_startup_log();
32362
32564
  init_api_url();
32363
32565
  LAUNCHD_LABEL = "ai.node9.daemon";
32364
- LAUNCHD_PLIST = path39.join(os32.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
32365
- SYSTEMD_UNIT_DIR = path39.join(os32.homedir(), ".config", "systemd", "user");
32366
- SYSTEMD_UNIT = path39.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
32566
+ LAUNCHD_PLIST = path40.join(os33.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
32567
+ SYSTEMD_UNIT_DIR = path40.join(os33.homedir(), ".config", "systemd", "user");
32568
+ SYSTEMD_UNIT = path40.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
32367
32569
  STARTUP_FILE = "node9-daemon.vbs";
32368
- WIN_LAUNCHER = () => path39.join(windowsStartupDir(), STARTUP_FILE);
32570
+ WIN_LAUNCHER = () => path40.join(windowsStartupDir(), STARTUP_FILE);
32369
32571
  AUTOSTART_HEAL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
32572
+ AUTOSTART_HEAL_RETRY_MS = [10 * 60 * 1e3, 60 * 60 * 1e3];
32370
32573
  }
32371
32574
  });
32372
32575
 
32373
32576
  // src/apps/detect.ts
32374
- import fs39 from "fs";
32375
- import os33 from "os";
32376
- import path40 from "path";
32577
+ import fs40 from "fs";
32578
+ import os34 from "os";
32579
+ import path41 from "path";
32377
32580
  function readTail(file2, bytes) {
32378
32581
  let fd;
32379
32582
  try {
32380
- fd = fs39.openSync(file2, "r");
32381
- const size = fs39.fstatSync(fd).size;
32583
+ fd = fs40.openSync(file2, "r");
32584
+ const size = fs40.fstatSync(fd).size;
32382
32585
  const start = Math.max(0, size - bytes);
32383
32586
  const buf = Buffer.alloc(size - start);
32384
- fs39.readSync(fd, buf, 0, buf.length, start);
32587
+ fs40.readSync(fd, buf, 0, buf.length, start);
32385
32588
  const text = buf.toString("utf8");
32386
32589
  return start > 0 ? text.slice(text.indexOf("\n") + 1) : text;
32387
32590
  } catch {
32388
32591
  return "";
32389
32592
  } finally {
32390
- if (fd !== void 0) fs39.closeSync(fd);
32593
+ if (fd !== void 0) fs40.closeSync(fd);
32391
32594
  }
32392
32595
  }
32393
32596
  function programsOf(command) {
@@ -32401,7 +32604,7 @@ function programsOf(command) {
32401
32604
  i++;
32402
32605
  continue;
32403
32606
  }
32404
- const base2 = path40.basename(w.replace(/^["']|["']$/g, ""));
32607
+ const base2 = path41.basename(w.replace(/^["']|["']$/g, ""));
32405
32608
  if (WRAPPERS.has(base2)) {
32406
32609
  i++;
32407
32610
  continue;
@@ -32436,7 +32639,7 @@ function observeAudit(auditFile) {
32436
32639
  for (const p of programsOf(row.argsPreview)) commands.add(p);
32437
32640
  }
32438
32641
  const dir2 = typeof row.workingDir === "string" ? row.workingDir : "";
32439
- if (dir2 && path40.isAbsolute(dir2) && !seenDirs.has(dir2) && dirs.length < MAX_PROJECTS) {
32642
+ if (dir2 && path41.isAbsolute(dir2) && !seenDirs.has(dir2) && dirs.length < MAX_PROJECTS) {
32440
32643
  seenDirs.add(dir2);
32441
32644
  dirs.push(dir2);
32442
32645
  }
@@ -32446,21 +32649,21 @@ function observeAudit(auditFile) {
32446
32649
  function readSmall(file2) {
32447
32650
  let fd;
32448
32651
  try {
32449
- fd = fs39.openSync(file2, READ_FLAGS);
32450
- const st = fs39.fstatSync(fd);
32652
+ fd = fs40.openSync(file2, READ_FLAGS);
32653
+ const st = fs40.fstatSync(fd);
32451
32654
  if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
32452
32655
  const buf = Buffer.alloc(st.size);
32453
- const n = fs39.readSync(fd, buf, 0, st.size, 0);
32656
+ const n = fs40.readSync(fd, buf, 0, st.size, 0);
32454
32657
  return buf.toString("utf8", 0, n);
32455
32658
  } catch {
32456
32659
  return null;
32457
32660
  } finally {
32458
- if (fd !== void 0) fs39.closeSync(fd);
32661
+ if (fd !== void 0) fs40.closeSync(fd);
32459
32662
  }
32460
32663
  }
32461
32664
  function projectPackages(dir2) {
32462
32665
  const out = /* @__PURE__ */ new Set();
32463
- const pkg = readSmall(path40.join(dir2, "package.json"));
32666
+ const pkg = readSmall(path41.join(dir2, "package.json"));
32464
32667
  if (pkg) {
32465
32668
  try {
32466
32669
  const j = JSON.parse(pkg);
@@ -32477,7 +32680,7 @@ function projectPackages(dir2) {
32477
32680
  } catch {
32478
32681
  }
32479
32682
  }
32480
- const req = readSmall(path40.join(dir2, "requirements.txt"));
32683
+ const req = readSmall(path41.join(dir2, "requirements.txt"));
32481
32684
  if (req) {
32482
32685
  for (const line of req.split("\n")) {
32483
32686
  const m = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line);
@@ -32485,7 +32688,7 @@ function projectPackages(dir2) {
32485
32688
  out.add(m[1].toLowerCase());
32486
32689
  }
32487
32690
  }
32488
- const py = readSmall(path40.join(dir2, "pyproject.toml"));
32691
+ const py = readSmall(path41.join(dir2, "pyproject.toml"));
32489
32692
  if (py) {
32490
32693
  for (const m of py.matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:[<>=!~;[\]]|["'])/g))
32491
32694
  out.add(m[1].toLowerCase());
@@ -32505,7 +32708,7 @@ function projectPackages(dir2) {
32505
32708
  function projectEnvNames(dir2) {
32506
32709
  const out = /* @__PURE__ */ new Set();
32507
32710
  for (const f of [".env", ".env.local", ".env.development"]) {
32508
- const text = readSmall(path40.join(dir2, f));
32711
+ const text = readSmall(path41.join(dir2, f));
32509
32712
  if (!text) continue;
32510
32713
  for (const line of text.split("\n")) {
32511
32714
  const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
@@ -32541,14 +32744,14 @@ function matchApps(obs, projectFacts, catalog = APP_CATALOG) {
32541
32744
  }
32542
32745
  return found;
32543
32746
  }
32544
- function detectApps(home = os33.homedir()) {
32545
- const obs = observeAudit(path40.join(home, ".node9", "audit.log"));
32747
+ function detectApps(home = os34.homedir()) {
32748
+ const obs = observeAudit(path41.join(home, ".node9", "audit.log"));
32546
32749
  for (const cfg of Object.values(readMcpToolsConfig())) {
32547
32750
  if (cfg?.name) obs.servers.add(cfg.name.toLowerCase());
32548
32751
  }
32549
32752
  const facts = obs.projects.filter((dir2) => {
32550
32753
  try {
32551
- return fs39.statSync(dir2).isDirectory();
32754
+ return fs40.statSync(dir2).isDirectory();
32552
32755
  } catch {
32553
32756
  return false;
32554
32757
  }
@@ -32583,7 +32786,7 @@ var init_detect = __esm({
32583
32786
  "pnpx",
32584
32787
  "bunx"
32585
32788
  ]);
32586
- READ_FLAGS = typeof fs39.constants.O_NONBLOCK === "number" ? fs39.constants.O_RDONLY | fs39.constants.O_NONBLOCK : fs39.constants.O_RDONLY;
32789
+ READ_FLAGS = typeof fs40.constants.O_NONBLOCK === "number" ? fs40.constants.O_RDONLY | fs40.constants.O_NONBLOCK : fs40.constants.O_RDONLY;
32587
32790
  }
32588
32791
  });
32589
32792
 
@@ -32643,9 +32846,9 @@ var init_daemon2 = __esm({
32643
32846
  });
32644
32847
 
32645
32848
  // src/config/migrate.ts
32646
- import fs40 from "fs";
32647
- import path41 from "path";
32648
- import os34 from "os";
32849
+ import fs41 from "fs";
32850
+ import path42 from "path";
32851
+ import os35 from "os";
32649
32852
  function backupPath(filePath) {
32650
32853
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
32651
32854
  return `${filePath}.bak-${stamp}`;
@@ -32655,7 +32858,7 @@ function migrateConfigFile(options = {}) {
32655
32858
  if (options.dryRun) {
32656
32859
  let raw;
32657
32860
  try {
32658
- raw = JSON.parse(fs40.readFileSync(filePath, "utf8"));
32861
+ raw = JSON.parse(fs41.readFileSync(filePath, "utf8"));
32659
32862
  } catch (err2) {
32660
32863
  if (err2.code === "ENOENT") return { status: "no-file" };
32661
32864
  return { status: "failed", error: `cannot read ${filePath}: ${err2.message}` };
@@ -32678,14 +32881,14 @@ function migrateConfigFile(options = {}) {
32678
32881
  }
32679
32882
  }
32680
32883
  function undoMigration(filePath = globalConfigPath()) {
32681
- const dir2 = path41.dirname(filePath);
32682
- const base2 = path41.basename(filePath);
32683
- const backups = fs40.readdirSync(dir2).filter((f) => f.startsWith(`${base2}.bak-`)).sort();
32884
+ const dir2 = path42.dirname(filePath);
32885
+ const base2 = path42.basename(filePath);
32886
+ const backups = fs41.readdirSync(dir2).filter((f) => f.startsWith(`${base2}.bak-`)).sort();
32684
32887
  const newest = backups.at(-1);
32685
32888
  if (!newest) return null;
32686
- const from = path41.join(dir2, newest);
32687
- atomicWriteSync(filePath, fs40.readFileSync(from, "utf8"), { mode: 384 });
32688
- fs40.unlinkSync(from);
32889
+ const from = path42.join(dir2, newest);
32890
+ atomicWriteSync(filePath, fs41.readFileSync(from, "utf8"), { mode: 384 });
32891
+ fs41.unlinkSync(from);
32689
32892
  return { restored: from };
32690
32893
  }
32691
32894
  function autoMigrateLocalConfig() {
@@ -32703,8 +32906,8 @@ function autoMigrateLocalConfig() {
32703
32906
  );
32704
32907
  } else if (outcome.status === "failed") {
32705
32908
  try {
32706
- fs40.appendFileSync(
32707
- path41.join(os34.homedir(), ".node9", "hook-debug.log"),
32909
+ fs41.appendFileSync(
32910
+ path42.join(os35.homedir(), ".node9", "hook-debug.log"),
32708
32911
  `[${(/* @__PURE__ */ new Date()).toISOString()}] config migrate failed: ${outcome.error}
32709
32912
  `
32710
32913
  );
@@ -32945,18 +33148,18 @@ var init_forecast = __esm({
32945
33148
  });
32946
33149
 
32947
33150
  // src/version.ts
32948
- import fs41 from "fs";
32949
- import path42 from "path";
33151
+ import fs42 from "fs";
33152
+ import path43 from "path";
32950
33153
  function readVersion() {
32951
33154
  let dir2 = __dirname;
32952
33155
  for (let i = 0; i < 5; i++) {
32953
- const p = path42.join(dir2, "package.json");
33156
+ const p = path43.join(dir2, "package.json");
32954
33157
  try {
32955
- const pkg = JSON.parse(fs41.readFileSync(p, "utf-8"));
33158
+ const pkg = JSON.parse(fs42.readFileSync(p, "utf-8"));
32956
33159
  if (pkg.name === "@node9/proxy" && typeof pkg.version === "string") return pkg.version;
32957
33160
  } catch {
32958
33161
  }
32959
- const parent = path42.dirname(dir2);
33162
+ const parent = path43.dirname(dir2);
32960
33163
  if (parent === dir2) break;
32961
33164
  dir2 = parent;
32962
33165
  }
@@ -32971,15 +33174,15 @@ var init_version = __esm({
32971
33174
  });
32972
33175
 
32973
33176
  // src/cli/commands/scan-cache.ts
32974
- import fs42 from "fs";
32975
- import os35 from "os";
32976
- import path43 from "path";
33177
+ import fs43 from "fs";
33178
+ import os36 from "os";
33179
+ import path44 from "path";
32977
33180
  import { createHash as createHash6 } from "crypto";
32978
33181
  function flushScanCaches() {
32979
33182
  for (const c of activeCaches) c.flush();
32980
33183
  }
32981
33184
  function scanCacheRoot() {
32982
- return path43.join(os35.homedir(), ".node9", "scan-cache");
33185
+ return path44.join(os36.homedir(), ".node9", "scan-cache");
32983
33186
  }
32984
33187
  function configHashOf(config) {
32985
33188
  const h = createHash6("sha256");
@@ -32987,14 +33190,14 @@ function configHashOf(config) {
32987
33190
  h.update(JSON.stringify(config.policySource));
32988
33191
  h.update(JSON.stringify(config.policy));
32989
33192
  for (const name of SIDECAR_FILES) {
32990
- const st = fileStamp(path43.join(os35.homedir(), ".node9", name));
33193
+ const st = fileStamp(path44.join(os36.homedir(), ".node9", name));
32991
33194
  h.update(`|${name}:${st ? `${st.size}:${st.mtimeMs}` : "absent"}`);
32992
33195
  }
32993
33196
  return h.digest("hex").slice(0, 16);
32994
33197
  }
32995
33198
  function fileStamp(filePath) {
32996
33199
  try {
32997
- const st = fs42.statSync(filePath);
33200
+ const st = fs43.statSync(filePath);
32998
33201
  return { size: st.size, mtimeMs: st.mtimeMs };
32999
33202
  } catch {
33000
33203
  return null;
@@ -33018,8 +33221,8 @@ var init_scan_cache = __esm({
33018
33221
  "trusted-hosts.json",
33019
33222
  "trusted-recipients.json",
33020
33223
  "rules-cache.json",
33021
- path43.join("osv", "npm", "meta.json"),
33022
- path43.join("osv", "PyPI", "meta.json")
33224
+ path44.join("osv", "npm", "meta.json"),
33225
+ path44.join("osv", "PyPI", "meta.json")
33023
33226
  ];
33024
33227
  ScanCache = class {
33025
33228
  root;
@@ -33048,7 +33251,7 @@ var init_scan_cache = __esm({
33048
33251
  if (!this.enabled) return fresh;
33049
33252
  try {
33050
33253
  const raw = JSON.parse(
33051
- fs42.readFileSync(path43.join(this.root, "index.json"), "utf8")
33254
+ fs43.readFileSync(path44.join(this.root, "index.json"), "utf8")
33052
33255
  );
33053
33256
  if (raw.cacheVersion !== SCAN_CACHE_VERSION || raw.engineVersion !== CANONICAL_EXTRACTOR_VERSION || raw.proxyVersion !== PROXY_VERSION || raw.configHash !== configHash || typeof raw.files !== "object") {
33054
33257
  this.dropAll();
@@ -33061,7 +33264,7 @@ var init_scan_cache = __esm({
33061
33264
  }
33062
33265
  dropAll() {
33063
33266
  try {
33064
- fs42.rmSync(this.root, { recursive: true, force: true });
33267
+ fs43.rmSync(this.root, { recursive: true, force: true });
33065
33268
  } catch {
33066
33269
  }
33067
33270
  }
@@ -33085,7 +33288,7 @@ var init_scan_cache = __esm({
33085
33288
  }
33086
33289
  }
33087
33290
  try {
33088
- const stored = JSON.parse(fs42.readFileSync(path43.join(this.root, e.result), "utf8"));
33291
+ const stored = JSON.parse(fs43.readFileSync(path44.join(this.root, e.result), "utf8"));
33089
33292
  if ((stored.judgedFrom ?? null) !== (e.judgedFrom ?? null) || !stored.result) {
33090
33293
  this.misses++;
33091
33294
  return null;
@@ -33100,14 +33303,14 @@ var init_scan_cache = __esm({
33100
33303
  put(filePath, stamp, agent, result, hasher, judgeFrom = null) {
33101
33304
  if (!this.enabled) return;
33102
33305
  const name = createHash6("sha256").update(`${agent}|${filePath}`).digest("hex").slice(0, 40);
33103
- const rel = path43.join("files", name.slice(0, 2), `${name}.json`);
33306
+ const rel = path44.join("files", name.slice(0, 2), `${name}.json`);
33104
33307
  const configHashes = {};
33105
33308
  if (hasher)
33106
33309
  for (const cwd of result.configCwds ?? []) configHashes[cwd] = hasher.configHashFor(cwd);
33107
33310
  try {
33108
- const abs = path43.join(this.root, rel);
33109
- fs42.mkdirSync(path43.dirname(abs), { recursive: true, mode: 448 });
33110
- fs42.writeFileSync(
33311
+ const abs = path44.join(this.root, rel);
33312
+ fs43.mkdirSync(path44.dirname(abs), { recursive: true, mode: 448 });
33313
+ fs43.writeFileSync(
33111
33314
  abs,
33112
33315
  JSON.stringify({ judgedFrom: judgeFrom ? judgeFrom.toISOString() : null, result }),
33113
33316
  { mode: 384 }
@@ -33130,9 +33333,9 @@ var init_scan_cache = __esm({
33130
33333
  /** Drop the entries of transcripts that no longer exist, and their files. */
33131
33334
  prune() {
33132
33335
  for (const [key, e] of Object.entries(this.index.files)) {
33133
- if (fs42.existsSync(key)) continue;
33336
+ if (fs43.existsSync(key)) continue;
33134
33337
  try {
33135
- fs42.rmSync(path43.join(this.root, e.result), { force: true });
33338
+ fs43.rmSync(path44.join(this.root, e.result), { force: true });
33136
33339
  } catch {
33137
33340
  }
33138
33341
  delete this.index.files[key];
@@ -33153,10 +33356,10 @@ var init_scan_cache = __esm({
33153
33356
  this.lastFlush = Date.now();
33154
33357
  if (!this.dirty) return;
33155
33358
  try {
33156
- fs42.mkdirSync(this.root, { recursive: true, mode: 448 });
33157
- const tmp = path43.join(this.root, `index.${process.pid}.tmp`);
33158
- fs42.writeFileSync(tmp, JSON.stringify(this.index), { mode: 384 });
33159
- fs42.renameSync(tmp, path43.join(this.root, "index.json"));
33359
+ fs43.mkdirSync(this.root, { recursive: true, mode: 448 });
33360
+ const tmp = path44.join(this.root, `index.${process.pid}.tmp`);
33361
+ fs43.writeFileSync(tmp, JSON.stringify(this.index), { mode: 384 });
33362
+ fs43.renameSync(tmp, path44.join(this.root, "index.json"));
33160
33363
  this.dirty = false;
33161
33364
  } catch {
33162
33365
  }
@@ -33166,9 +33369,9 @@ var init_scan_cache = __esm({
33166
33369
  });
33167
33370
 
33168
33371
  // src/cli/commands/scan-judge.ts
33169
- import fs43 from "fs";
33170
- import os36 from "os";
33171
- import path44 from "path";
33372
+ import fs44 from "fs";
33373
+ import os37 from "os";
33374
+ import path45 from "path";
33172
33375
  function findingDedupKey(f) {
33173
33376
  return `${f.source.rule.name ?? "<unnamed>"}|${preview(f.input)}|${f.project}|${f.repo ?? ""}`;
33174
33377
  }
@@ -33267,24 +33470,24 @@ var init_scan_judge = __esm({
33267
33470
  * file, so worktrees count), home-relative; undefined when none. Cached per
33268
33471
  * directory: history repeats the same few cwds thousands of times. */
33269
33472
  repoOf(cwd) {
33270
- if (!cwd || !path44.isAbsolute(cwd)) return void 0;
33473
+ if (!cwd || !path45.isAbsolute(cwd)) return void 0;
33271
33474
  if (this.repos.has(cwd)) return this.repos.get(cwd);
33272
33475
  let dir2 = cwd;
33273
33476
  let found;
33274
33477
  for (; ; ) {
33275
33478
  try {
33276
- if (fs43.existsSync(path44.join(dir2, ".git"))) {
33479
+ if (fs44.existsSync(path45.join(dir2, ".git"))) {
33277
33480
  found = dir2;
33278
33481
  break;
33279
33482
  }
33280
33483
  } catch {
33281
33484
  break;
33282
33485
  }
33283
- const parent = path44.dirname(dir2);
33486
+ const parent = path45.dirname(dir2);
33284
33487
  if (parent === dir2) break;
33285
33488
  dir2 = parent;
33286
33489
  }
33287
- const home = os36.homedir();
33490
+ const home = os37.homedir();
33288
33491
  const label3 = found && found.startsWith(home) ? "~" + found.slice(home.length) : found;
33289
33492
  this.repos.set(cwd, label3);
33290
33493
  return label3;
@@ -33429,15 +33632,15 @@ var init_scan_judge = __esm({
33429
33632
  });
33430
33633
 
33431
33634
  // src/pricing/litellm.ts
33432
- import fs44 from "fs";
33433
- import path45 from "path";
33434
- import os37 from "os";
33635
+ import fs45 from "fs";
33636
+ import path46 from "path";
33637
+ import os38 from "os";
33435
33638
  function normalizeModel(raw) {
33436
33639
  return raw.replace(/-\d{8}$/, "").toLowerCase();
33437
33640
  }
33438
33641
  function readCache(opts) {
33439
33642
  try {
33440
- const raw = JSON.parse(fs44.readFileSync(CACHE_FILE(), "utf-8"));
33643
+ const raw = JSON.parse(fs45.readFileSync(CACHE_FILE(), "utf-8"));
33441
33644
  if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
33442
33645
  return null;
33443
33646
  }
@@ -33452,18 +33655,18 @@ function readCache(opts) {
33452
33655
  function writeCache(prices) {
33453
33656
  try {
33454
33657
  const target = CACHE_FILE();
33455
- const dir2 = path45.dirname(target);
33456
- if (!fs44.existsSync(dir2)) fs44.mkdirSync(dir2, { recursive: true });
33658
+ const dir2 = path46.dirname(target);
33659
+ if (!fs45.existsSync(dir2)) fs45.mkdirSync(dir2, { recursive: true });
33457
33660
  const tmp = target + ".tmp";
33458
33661
  const body = {
33459
33662
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
33460
33663
  prices
33461
33664
  };
33462
- fs44.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
33463
- fs44.renameSync(tmp, target);
33665
+ fs45.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
33666
+ fs45.renameSync(tmp, target);
33464
33667
  } catch (err2) {
33465
33668
  try {
33466
- fs44.appendFileSync(
33669
+ fs45.appendFileSync(
33467
33670
  HOOK_DEBUG_LOG,
33468
33671
  `[pricing] cache write failed: ${err2.message}
33469
33672
  `
@@ -33621,7 +33824,7 @@ var init_litellm = __esm({
33621
33824
  "gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
33622
33825
  "gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
33623
33826
  };
33624
- CACHE_FILE = () => path45.join(os37.homedir(), ".node9", "model-pricing.json");
33827
+ CACHE_FILE = () => path46.join(os38.homedir(), ".node9", "model-pricing.json");
33625
33828
  TTL_MS2 = 24 * 60 * 60 * 1e3;
33626
33829
  memCache = null;
33627
33830
  memCacheAt = 0;
@@ -33631,11 +33834,11 @@ var init_litellm = __esm({
33631
33834
  });
33632
33835
 
33633
33836
  // src/cost-codex.ts
33634
- import fs45 from "fs";
33635
- import os38 from "os";
33636
- import path46 from "path";
33837
+ import fs46 from "fs";
33838
+ import os39 from "os";
33839
+ import path47 from "path";
33637
33840
  function codexSessionsDir() {
33638
- return path46.join(process.env.CODEX_HOME?.trim() || path46.join(os38.homedir(), ".codex"), "sessions");
33841
+ return path47.join(process.env.CODEX_HOME?.trim() || path47.join(os39.homedir(), ".codex"), "sessions");
33639
33842
  }
33640
33843
  function codexPriceFor(model) {
33641
33844
  return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
@@ -33667,14 +33870,14 @@ function codexSessionCost(model, tokens, request2) {
33667
33870
  function statAndFirstLine(file2) {
33668
33871
  const CAP = 4 * 1024 * 1024;
33669
33872
  const CHUNK = 64 * 1024;
33670
- const fd = fs45.openSync(file2, "r");
33873
+ const fd = fs46.openSync(file2, "r");
33671
33874
  try {
33672
- const stat = fs45.fstatSync(fd);
33875
+ const stat = fs46.fstatSync(fd);
33673
33876
  const limit = Math.min(stat.size, CAP);
33674
33877
  const parts = [];
33675
33878
  for (let pos = 0; pos < limit; pos += CHUNK) {
33676
33879
  const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
33677
- const read3 = fs45.readSync(fd, buf, 0, buf.length, pos);
33880
+ const read3 = fs46.readSync(fd, buf, 0, buf.length, pos);
33678
33881
  if (read3 <= 0) break;
33679
33882
  const slice = buf.subarray(0, read3);
33680
33883
  const nl = slice.indexOf(10);
@@ -33683,15 +33886,15 @@ function statAndFirstLine(file2) {
33683
33886
  }
33684
33887
  return { stat, first: Buffer.concat(parts).toString("utf8") };
33685
33888
  } finally {
33686
- fs45.closeSync(fd);
33889
+ fs46.closeSync(fd);
33687
33890
  }
33688
33891
  }
33689
33892
  function listCodexSessionFiles(base2 = codexSessionsDir()) {
33690
33893
  const files = [];
33691
33894
  const walk2 = (dir2) => {
33692
33895
  try {
33693
- for (const entry of fs45.readdirSync(dir2, { withFileTypes: true })) {
33694
- const file2 = path46.join(dir2, entry.name);
33896
+ for (const entry of fs46.readdirSync(dir2, { withFileTypes: true })) {
33897
+ const file2 = path47.join(dir2, entry.name);
33695
33898
  if (entry.isDirectory()) walk2(file2);
33696
33899
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file2);
33697
33900
  }
@@ -33699,7 +33902,7 @@ function listCodexSessionFiles(base2 = codexSessionsDir()) {
33699
33902
  }
33700
33903
  };
33701
33904
  walk2(base2);
33702
- if (path46.basename(base2) === "sessions") walk2(path46.join(path46.dirname(base2), "archived_sessions"));
33905
+ if (path47.basename(base2) === "sessions") walk2(path47.join(path47.dirname(base2), "archived_sessions"));
33703
33906
  const sessions = /* @__PURE__ */ new Map();
33704
33907
  for (const file2 of files.sort()) {
33705
33908
  try {
@@ -33897,13 +34100,13 @@ var init_cost_codex = __esm({
33897
34100
  CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
33898
34101
  codexSource = {
33899
34102
  id: "codex",
33900
- available: () => fs45.existsSync(codexSessionsDir()) || fs45.existsSync(path46.join(path46.dirname(codexSessionsDir()), "archived_sessions")),
34103
+ available: () => fs46.existsSync(codexSessionsDir()) || fs46.existsSync(path47.join(path47.dirname(codexSessionsDir()), "archived_sessions")),
33901
34104
  collect(sinceMs) {
33902
34105
  const entries = [];
33903
34106
  for (const file2 of listCodexSessionFiles()) {
33904
34107
  try {
33905
- if (sinceMs !== void 0 && fs45.statSync(file2).mtimeMs < sinceMs) continue;
33906
- entries.push(...parseCodexSession(fs45.readFileSync(file2, "utf8").split("\n")));
34108
+ if (sinceMs !== void 0 && fs46.statSync(file2).mtimeMs < sinceMs) continue;
34109
+ entries.push(...parseCodexSession(fs46.readFileSync(file2, "utf8").split("\n")));
33907
34110
  } catch {
33908
34111
  }
33909
34112
  }
@@ -33914,11 +34117,11 @@ var init_cost_codex = __esm({
33914
34117
  });
33915
34118
 
33916
34119
  // src/cost-gemini.ts
33917
- import fs46 from "fs";
33918
- import os39 from "os";
33919
- import path47 from "path";
34120
+ import fs47 from "fs";
34121
+ import os40 from "os";
34122
+ import path48 from "path";
33920
34123
  function geminiTmpDir() {
33921
- return path47.join(os39.homedir(), ".gemini", "tmp");
34124
+ return path48.join(os40.homedir(), ".gemini", "tmp");
33922
34125
  }
33923
34126
  function geminiPriceFor(model) {
33924
34127
  let tuple = pricingFor(model);
@@ -33933,14 +34136,14 @@ function geminiPriceFor(model) {
33933
34136
  }
33934
34137
  function safeReaddir(dir2) {
33935
34138
  try {
33936
- return fs46.readdirSync(dir2);
34139
+ return fs47.readdirSync(dir2);
33937
34140
  } catch {
33938
34141
  return [];
33939
34142
  }
33940
34143
  }
33941
34144
  function isDir(p) {
33942
34145
  try {
33943
- return fs46.statSync(p).isDirectory();
34146
+ return fs47.statSync(p).isDirectory();
33944
34147
  } catch {
33945
34148
  return false;
33946
34149
  }
@@ -33948,11 +34151,11 @@ function isDir(p) {
33948
34151
  function listGeminiSessionFiles(base2) {
33949
34152
  const out = [];
33950
34153
  for (const project of safeReaddir(base2)) {
33951
- const chats = path47.join(base2, project, "chats");
34154
+ const chats = path48.join(base2, project, "chats");
33952
34155
  if (!isDir(chats)) continue;
33953
34156
  for (const f of safeReaddir(chats)) {
33954
34157
  if (f.startsWith("session-") && f.endsWith(".jsonl")) {
33955
- out.push({ file: path47.join(chats, f), project });
34158
+ out.push({ file: path48.join(chats, f), project });
33956
34159
  }
33957
34160
  }
33958
34161
  }
@@ -34019,7 +34222,7 @@ var init_cost_gemini = __esm({
34019
34222
  id: "gemini",
34020
34223
  available() {
34021
34224
  try {
34022
- return fs46.existsSync(geminiTmpDir());
34225
+ return fs47.existsSync(geminiTmpDir());
34023
34226
  } catch {
34024
34227
  return false;
34025
34228
  }
@@ -34028,13 +34231,13 @@ var init_cost_gemini = __esm({
34028
34231
  const combined2 = /* @__PURE__ */ new Map();
34029
34232
  for (const { file: file2, project } of listGeminiSessionFiles(geminiTmpDir())) {
34030
34233
  try {
34031
- if (sinceMs !== void 0 && fs46.statSync(file2).mtimeMs < sinceMs) continue;
34234
+ if (sinceMs !== void 0 && fs47.statSync(file2).mtimeMs < sinceMs) continue;
34032
34235
  } catch {
34033
34236
  continue;
34034
34237
  }
34035
34238
  let content;
34036
34239
  try {
34037
- content = fs46.readFileSync(file2, "utf8");
34240
+ content = fs47.readFileSync(file2, "utf8");
34038
34241
  } catch {
34039
34242
  continue;
34040
34243
  }
@@ -34059,15 +34262,15 @@ var init_cost_gemini = __esm({
34059
34262
  });
34060
34263
 
34061
34264
  // src/cost-copilot.ts
34062
- import fs47 from "fs";
34063
- import os40 from "os";
34064
- import path48 from "path";
34265
+ import fs48 from "fs";
34266
+ import os41 from "os";
34267
+ import path49 from "path";
34065
34268
  function copilotSessionsDir() {
34066
- return path48.join(os40.homedir(), ".copilot", "session-state");
34269
+ return path49.join(os41.homedir(), ".copilot", "session-state");
34067
34270
  }
34068
34271
  function safeReaddir2(dir2) {
34069
34272
  try {
34070
- return fs47.readdirSync(dir2);
34273
+ return fs48.readdirSync(dir2);
34071
34274
  } catch {
34072
34275
  return [];
34073
34276
  }
@@ -34143,7 +34346,7 @@ var init_cost_copilot = __esm({
34143
34346
  id: "copilot",
34144
34347
  available() {
34145
34348
  try {
34146
- return fs47.existsSync(copilotSessionsDir());
34349
+ return fs48.existsSync(copilotSessionsDir());
34147
34350
  } catch {
34148
34351
  return false;
34149
34352
  }
@@ -34152,15 +34355,15 @@ var init_cost_copilot = __esm({
34152
34355
  const base2 = copilotSessionsDir();
34153
34356
  const combined2 = /* @__PURE__ */ new Map();
34154
34357
  for (const sid of safeReaddir2(base2)) {
34155
- const file2 = path48.join(base2, sid, "events.jsonl");
34358
+ const file2 = path49.join(base2, sid, "events.jsonl");
34156
34359
  try {
34157
- if (sinceMs !== void 0 && fs47.statSync(file2).mtimeMs < sinceMs) continue;
34360
+ if (sinceMs !== void 0 && fs48.statSync(file2).mtimeMs < sinceMs) continue;
34158
34361
  } catch {
34159
34362
  continue;
34160
34363
  }
34161
34364
  let content;
34162
34365
  try {
34163
- content = fs47.readFileSync(file2, "utf8");
34366
+ content = fs48.readFileSync(file2, "utf8");
34164
34367
  } catch {
34165
34368
  continue;
34166
34369
  }
@@ -34185,21 +34388,21 @@ var init_cost_copilot = __esm({
34185
34388
  });
34186
34389
 
34187
34390
  // src/session-files.ts
34188
- import * as fs48 from "fs";
34189
- import * as path49 from "path";
34391
+ import * as fs49 from "fs";
34392
+ import * as path50 from "path";
34190
34393
  function listSessionFiles(dir2, maxDepth = 6) {
34191
34394
  const out = [];
34192
34395
  const walk2 = (d, rel, depth) => {
34193
34396
  if (depth > maxDepth) return;
34194
34397
  let entries;
34195
34398
  try {
34196
- entries = fs48.readdirSync(d, { withFileTypes: true });
34399
+ entries = fs49.readdirSync(d, { withFileTypes: true });
34197
34400
  } catch {
34198
34401
  return;
34199
34402
  }
34200
34403
  for (const e of entries) {
34201
- const childRel = rel ? path49.join(rel, e.name) : e.name;
34202
- if (e.isDirectory()) walk2(path49.join(d, e.name), childRel, depth + 1);
34404
+ const childRel = rel ? path50.join(rel, e.name) : e.name;
34405
+ if (e.isDirectory()) walk2(path50.join(d, e.name), childRel, depth + 1);
34203
34406
  else if (e.name.endsWith(".jsonl")) out.push(childRel);
34204
34407
  }
34205
34408
  };
@@ -34207,7 +34410,7 @@ function listSessionFiles(dir2, maxDepth = 6) {
34207
34410
  return out;
34208
34411
  }
34209
34412
  function sessionIdOf(relPath) {
34210
- return path49.basename(relPath).replace(/\.jsonl$/, "");
34413
+ return path50.basename(relPath).replace(/\.jsonl$/, "");
34211
34414
  }
34212
34415
  var init_session_files = __esm({
34213
34416
  "src/session-files.ts"() {
@@ -34216,17 +34419,17 @@ var init_session_files = __esm({
34216
34419
  });
34217
34420
 
34218
34421
  // src/costSync.ts
34219
- import fs49 from "fs";
34220
- import path50 from "path";
34221
- import os41 from "os";
34422
+ import fs50 from "fs";
34423
+ import path51 from "path";
34424
+ import os42 from "os";
34222
34425
  function decodeProjectDirName(dirName) {
34223
34426
  return dirName.replace(/-/g, "/");
34224
34427
  }
34225
34428
  function parseJSONLFile(filePath, fallbackWorkingDir) {
34226
- const runId = path50.basename(filePath, ".jsonl");
34429
+ const runId = path51.basename(filePath, ".jsonl");
34227
34430
  let content;
34228
34431
  try {
34229
- content = fs49.readFileSync(filePath, "utf8");
34432
+ content = fs50.readFileSync(filePath, "utf8");
34230
34433
  } catch {
34231
34434
  return /* @__PURE__ */ new Map();
34232
34435
  }
@@ -34326,7 +34529,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
34326
34529
  signal: AbortSignal.timeout(15e3)
34327
34530
  });
34328
34531
  if (!res.ok) {
34329
- fs49.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
34532
+ fs50.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
34330
34533
  `);
34331
34534
  } else {
34332
34535
  let stored;
@@ -34348,8 +34551,8 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
34348
34551
  }
34349
34552
  function debugLog(line) {
34350
34553
  try {
34351
- fs49.mkdirSync(path50.dirname(HOOK_DEBUG_LOG), { recursive: true });
34352
- fs49.appendFileSync(HOOK_DEBUG_LOG, line);
34554
+ fs50.mkdirSync(path51.dirname(HOOK_DEBUG_LOG), { recursive: true });
34555
+ fs50.appendFileSync(HOOK_DEBUG_LOG, line);
34353
34556
  } catch (err2) {
34354
34557
  console.error(`[cost-sync] could not write ${HOOK_DEBUG_LOG}: ${safeMessage(err2)}`);
34355
34558
  }
@@ -34362,10 +34565,10 @@ async function syncCost() {
34362
34565
  if (entries.length === 0) return;
34363
34566
  let username = "unknown";
34364
34567
  try {
34365
- username = os41.userInfo().username;
34568
+ username = os42.userInfo().username;
34366
34569
  } catch {
34367
34570
  }
34368
- const machineId = `${os41.hostname()}:${username}`;
34571
+ const machineId = `${os42.hostname()}:${username}`;
34369
34572
  await postCostBatches(creds.apiUrl, creds.apiKey, machineId, entries);
34370
34573
  }
34371
34574
  function startCostSync() {
@@ -34393,22 +34596,22 @@ var init_costSync = __esm({
34393
34596
  claudeSource = {
34394
34597
  id: "claude",
34395
34598
  available() {
34396
- return fs49.existsSync(path50.join(os41.homedir(), ".claude", "projects"));
34599
+ return fs50.existsSync(path51.join(os42.homedir(), ".claude", "projects"));
34397
34600
  },
34398
34601
  collect(sinceMs) {
34399
- const projectsDir = path50.join(os41.homedir(), ".claude", "projects");
34400
- if (!fs49.existsSync(projectsDir)) return [];
34602
+ const projectsDir = path51.join(os42.homedir(), ".claude", "projects");
34603
+ if (!fs50.existsSync(projectsDir)) return [];
34401
34604
  const combined2 = /* @__PURE__ */ new Map();
34402
34605
  let dirs;
34403
34606
  try {
34404
- dirs = fs49.readdirSync(projectsDir);
34607
+ dirs = fs50.readdirSync(projectsDir);
34405
34608
  } catch {
34406
34609
  return [];
34407
34610
  }
34408
34611
  for (const dir2 of dirs) {
34409
- const dirPath = path50.join(projectsDir, dir2);
34612
+ const dirPath = path51.join(projectsDir, dir2);
34410
34613
  try {
34411
- if (!fs49.statSync(dirPath).isDirectory()) continue;
34614
+ if (!fs50.statSync(dirPath).isDirectory()) continue;
34412
34615
  } catch {
34413
34616
  continue;
34414
34617
  }
@@ -34420,10 +34623,10 @@ var init_costSync = __esm({
34420
34623
  }
34421
34624
  const fallbackWorkingDir = decodeProjectDirName(dir2);
34422
34625
  for (const file2 of files) {
34423
- const filePath = path50.join(dirPath, file2);
34626
+ const filePath = path51.join(dirPath, file2);
34424
34627
  if (sinceMs !== void 0) {
34425
34628
  try {
34426
- if (fs49.statSync(filePath).mtimeMs < sinceMs) continue;
34629
+ if (fs50.statSync(filePath).mtimeMs < sinceMs) continue;
34427
34630
  } catch {
34428
34631
  continue;
34429
34632
  }
@@ -34659,7 +34862,7 @@ function buildScanSummary(agents) {
34659
34862
  };
34660
34863
  }
34661
34864
  function wouldHaveVerb(summary, past = "would have blocked") {
34662
- return summary.policy?.recordOnly ? `logged only (${summary.policy.mode} mode, nothing enforced)` : past;
34865
+ return summary.policy?.recordOnly ? `logged only (${levelOf(summary.policy.mode)} mode, nothing enforced)` : past;
34663
34866
  }
34664
34867
  function buildSections(findings) {
34665
34868
  const sectionMap = /* @__PURE__ */ new Map();
@@ -34795,10 +34998,10 @@ var init_scan_summary = __esm({
34795
34998
  });
34796
34999
 
34797
35000
  // src/credential-files.ts
34798
- import path51 from "path";
35001
+ import path52 from "path";
34799
35002
  function homeCredentialFiles(home) {
34800
35003
  const f = (parts, description, score, kind = "credential") => ({
34801
- full: path51.join(home, ...parts),
35004
+ full: path52.join(home, ...parts),
34802
35005
  label: "~/" + parts.join("/"),
34803
35006
  description,
34804
35007
  score,
@@ -34819,8 +35022,7 @@ function homeCredentialFiles(home) {
34819
35022
  ),
34820
35023
  f([".docker", "config.json"], "Docker registry auth tokens", 10),
34821
35024
  f([".netrc"], "FTP/HTTP credentials in plain text", 15),
34822
- f([".npmrc"], "npm auth token: can publish packages as you", 10),
34823
- f([".node9", "credentials.json"], "Node9 cloud API key", 10)
35025
+ f([".npmrc"], "npm auth token: can publish packages as you", 10)
34824
35026
  ];
34825
35027
  }
34826
35028
  var init_credential_files = __esm({
@@ -34831,9 +35033,9 @@ var init_credential_files = __esm({
34831
35033
 
34832
35034
  // src/cli/commands/blast.ts
34833
35035
  import chalk3 from "chalk";
34834
- import fs50 from "fs";
34835
- import path52 from "path";
34836
- import os42 from "os";
35036
+ import fs51 from "fs";
35037
+ import path53 from "path";
35038
+ import os43 from "os";
34837
35039
  function buildSensitivePaths(home, cwd) {
34838
35040
  const homeFiles = homeCredentialFiles(home).map(({ full, label: label3, description, score }) => ({
34839
35041
  full,
@@ -34844,19 +35046,19 @@ function buildSensitivePaths(home, cwd) {
34844
35046
  return [
34845
35047
  ...homeFiles,
34846
35048
  {
34847
- full: path52.join(cwd, ".env"),
35049
+ full: path53.join(cwd, ".env"),
34848
35050
  label: ".env (current folder)",
34849
35051
  description: "App secrets: database passwords, API keys",
34850
35052
  score: 20
34851
35053
  },
34852
35054
  {
34853
- full: path52.join(cwd, ".env.local"),
35055
+ full: path53.join(cwd, ".env.local"),
34854
35056
  label: ".env.local (current folder)",
34855
35057
  description: "Local overrides: often contains real credentials",
34856
35058
  score: 15
34857
35059
  },
34858
35060
  {
34859
- full: path52.join(cwd, ".env.production"),
35061
+ full: path53.join(cwd, ".env.production"),
34860
35062
  label: ".env.production (current folder)",
34861
35063
  description: "Production secrets",
34862
35064
  score: 20
@@ -34865,7 +35067,7 @@ function buildSensitivePaths(home, cwd) {
34865
35067
  }
34866
35068
  function isReadable(filePath) {
34867
35069
  try {
34868
- fs50.accessSync(filePath, fs50.constants.R_OK);
35070
+ fs51.accessSync(filePath, fs51.constants.R_OK);
34869
35071
  return true;
34870
35072
  } catch {
34871
35073
  return false;
@@ -34878,13 +35080,13 @@ function scoreLabel(score) {
34878
35080
  return chalk3.red.bold(`${score}/100 Critical`);
34879
35081
  }
34880
35082
  function runBlast() {
34881
- const home = os42.homedir();
35083
+ const home = os43.homedir();
34882
35084
  const cwd = process.cwd();
34883
35085
  const paths = buildSensitivePaths(home, cwd);
34884
35086
  let scoreDeduction = 0;
34885
35087
  const reachable = [];
34886
35088
  for (const p of paths) {
34887
- if (fs50.existsSync(p.full) && isReadable(p.full)) {
35089
+ if (fs51.existsSync(p.full) && isReadable(p.full)) {
34888
35090
  reachable.push(p);
34889
35091
  scoreDeduction += p.score;
34890
35092
  }
@@ -34900,11 +35102,18 @@ function runBlast() {
34900
35102
  }
34901
35103
  return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
34902
35104
  }
35105
+ function labelColumnWidth(labels) {
35106
+ return Math.max(38, ...labels.map((l) => l.length + 2));
35107
+ }
34903
35108
  function registerBlastCommand(program2) {
34904
35109
  program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
34905
- const home = os42.homedir();
35110
+ const home = os43.homedir();
34906
35111
  const cwd = process.cwd();
34907
35112
  const { reachable, envFindings, score } = runBlast();
35113
+ const width = labelColumnWidth([
35114
+ ...reachable.map((p) => p.label),
35115
+ ...envFindings.map((f) => f.key)
35116
+ ]);
34908
35117
  console.log("");
34909
35118
  console.log(
34910
35119
  chalk3.bold(" \u{1F52D} Node9 Blast Radius") + chalk3.dim(" \xB7 what an AI agent can reach from here")
@@ -34915,7 +35124,7 @@ function registerBlastCommand(program2) {
34915
35124
  console.log(" " + chalk3.red.bold("Sensitive files reachable:"));
34916
35125
  for (const p of reachable) {
34917
35126
  console.log(
34918
- " " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(38)) + chalk3.dim(p.description)
35127
+ " " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(width)) + chalk3.dim(p.description)
34919
35128
  );
34920
35129
  }
34921
35130
  console.log("");
@@ -34924,7 +35133,7 @@ function registerBlastCommand(program2) {
34924
35133
  console.log(" " + chalk3.red.bold("Secrets in active environment:"));
34925
35134
  for (const f of envFindings) {
34926
35135
  console.log(
34927
- " " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(38)) + chalk3.dim(f.patternName)
35136
+ " " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(width)) + chalk3.dim(f.patternName)
34928
35137
  );
34929
35138
  }
34930
35139
  console.log("");
@@ -35039,6 +35248,12 @@ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
35039
35248
  if (days > 90) return "90d+";
35040
35249
  return `${days}d`;
35041
35250
  }
35251
+ function relativeAgo(timestamp2, now = /* @__PURE__ */ new Date()) {
35252
+ const rel = relativeDate(timestamp2, now);
35253
+ if (rel === "?" || rel === "today") return rel;
35254
+ if (rel === "1d") return "yesterday";
35255
+ return `${rel} ago`;
35256
+ }
35042
35257
  var PANEL_WIDTH;
35043
35258
  var init_scan_derive = __esm({
35044
35259
  "src/cli/render/scan-derive.ts"() {
@@ -35092,17 +35307,17 @@ var init_scan_json = __esm({
35092
35307
  });
35093
35308
 
35094
35309
  // src/cli/render/scan-history.ts
35095
- import fs51 from "fs";
35096
- import path53 from "path";
35097
- import os43 from "os";
35310
+ import fs52 from "fs";
35311
+ import path54 from "path";
35312
+ import os44 from "os";
35098
35313
  function defaultHistoryPath() {
35099
- return path53.join(os43.homedir(), ".node9", "scan-history.json");
35314
+ return path54.join(os44.homedir(), ".node9", "scan-history.json");
35100
35315
  }
35101
35316
  function readPreviousScan(opts = {}) {
35102
35317
  const filePath = opts.path ?? defaultHistoryPath();
35103
35318
  try {
35104
- if (!fs51.existsSync(filePath)) return null;
35105
- const raw = fs51.readFileSync(filePath, "utf8");
35319
+ if (!fs52.existsSync(filePath)) return null;
35320
+ const raw = fs52.readFileSync(filePath, "utf8");
35106
35321
  const parsed = JSON.parse(raw);
35107
35322
  if (!Array.isArray(parsed) || parsed.length === 0) return null;
35108
35323
  const last = parsed[parsed.length - 1];
@@ -35116,11 +35331,11 @@ function appendScanHistory(record2, opts = {}) {
35116
35331
  const filePath = opts.path ?? defaultHistoryPath();
35117
35332
  const cap = opts.cap ?? SCAN_HISTORY_CAP;
35118
35333
  try {
35119
- fs51.mkdirSync(path53.dirname(filePath), { recursive: true });
35334
+ fs52.mkdirSync(path54.dirname(filePath), { recursive: true });
35120
35335
  let history = [];
35121
- if (fs51.existsSync(filePath)) {
35336
+ if (fs52.existsSync(filePath)) {
35122
35337
  try {
35123
- const parsed = JSON.parse(fs51.readFileSync(filePath, "utf8"));
35338
+ const parsed = JSON.parse(fs52.readFileSync(filePath, "utf8"));
35124
35339
  if (Array.isArray(parsed)) {
35125
35340
  history = parsed.filter(isValidRecord);
35126
35341
  }
@@ -35175,9 +35390,9 @@ __export(scan_watermark_exports, {
35175
35390
  tickForensicBroadcast: () => tickForensicBroadcast,
35176
35391
  tickScanWatcher: () => tickScanWatcher
35177
35392
  });
35178
- import fs52 from "fs";
35179
- import os44 from "os";
35180
- import path54 from "path";
35393
+ import fs53 from "fs";
35394
+ import os45 from "os";
35395
+ import path55 from "path";
35181
35396
  import readline from "readline";
35182
35397
  function freshWatermark() {
35183
35398
  return {
@@ -35190,7 +35405,7 @@ function freshWatermark() {
35190
35405
  function loadWatermark() {
35191
35406
  let raw;
35192
35407
  try {
35193
- raw = fs52.readFileSync(WATERMARK_FILE(), "utf-8");
35408
+ raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
35194
35409
  } catch {
35195
35410
  return { status: "fresh", wm: freshWatermark() };
35196
35411
  }
@@ -35242,28 +35457,28 @@ function loadWatermark() {
35242
35457
  function saveWatermark(wm) {
35243
35458
  if (wm.schemaVersion > WATERMARK_SCHEMA_VERSION) return;
35244
35459
  const target = WATERMARK_FILE();
35245
- const dir2 = path54.dirname(target);
35246
- if (!fs52.existsSync(dir2)) fs52.mkdirSync(dir2, { recursive: true });
35460
+ const dir2 = path55.dirname(target);
35461
+ if (!fs53.existsSync(dir2)) fs53.mkdirSync(dir2, { recursive: true });
35247
35462
  const tmp = target + ".tmp";
35248
- fs52.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
35249
- fs52.renameSync(tmp, target);
35463
+ fs53.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
35464
+ fs53.renameSync(tmp, target);
35250
35465
  }
35251
35466
  function listJsonlFiles() {
35252
35467
  const root = PROJECTS_DIR();
35253
- if (!fs52.existsSync(root)) return [];
35468
+ if (!fs53.existsSync(root)) return [];
35254
35469
  const out = [];
35255
- for (const entry of fs52.readdirSync(root, { withFileTypes: true })) {
35470
+ for (const entry of fs53.readdirSync(root, { withFileTypes: true })) {
35256
35471
  if (!entry.isDirectory()) continue;
35257
- const projectDir = path54.join(root, entry.name);
35472
+ const projectDir = path55.join(root, entry.name);
35258
35473
  let inner;
35259
35474
  try {
35260
- inner = fs52.readdirSync(projectDir, { withFileTypes: true });
35475
+ inner = fs53.readdirSync(projectDir, { withFileTypes: true });
35261
35476
  } catch {
35262
35477
  continue;
35263
35478
  }
35264
35479
  for (const file2 of inner) {
35265
35480
  if (file2.isFile() && file2.name.endsWith(".jsonl")) {
35266
- out.push(path54.join(projectDir, file2.name));
35481
+ out.push(path55.join(projectDir, file2.name));
35267
35482
  }
35268
35483
  }
35269
35484
  }
@@ -35271,7 +35486,7 @@ function listJsonlFiles() {
35271
35486
  }
35272
35487
  function fileSize(p) {
35273
35488
  try {
35274
- return fs52.statSync(p).size;
35489
+ return fs53.statSync(p).size;
35275
35490
  } catch {
35276
35491
  return 0;
35277
35492
  }
@@ -35281,7 +35496,7 @@ async function scanDelta(filePath, fromByte, onLine) {
35281
35496
  if (size <= fromByte) return fromByte;
35282
35497
  const lastNl = findLastNewline(filePath, fromByte, size);
35283
35498
  const endsWithNewline = lastNl === size - 1;
35284
- const stream = fs52.createReadStream(filePath, {
35499
+ const stream = fs53.createReadStream(filePath, {
35285
35500
  start: fromByte,
35286
35501
  end: size - 1,
35287
35502
  highWaterMark: 64 * 1024
@@ -35310,7 +35525,7 @@ function findLastNewline(filePath, from, size) {
35310
35525
  const CHUNK = 64 * 1024;
35311
35526
  let fd;
35312
35527
  try {
35313
- fd = fs52.openSync(filePath, "r");
35528
+ fd = fs53.openSync(filePath, "r");
35314
35529
  } catch {
35315
35530
  return -1;
35316
35531
  }
@@ -35319,7 +35534,7 @@ function findLastNewline(filePath, from, size) {
35319
35534
  let end = size;
35320
35535
  while (end > from) {
35321
35536
  const start = Math.max(from, end - CHUNK);
35322
- const n = fs52.readSync(fd, buf, 0, end - start, start);
35537
+ const n = fs53.readSync(fd, buf, 0, end - start, start);
35323
35538
  const idx = buf.subarray(0, n).lastIndexOf(10);
35324
35539
  if (idx !== -1) return start + idx;
35325
35540
  end = start;
@@ -35328,7 +35543,7 @@ function findLastNewline(filePath, from, size) {
35328
35543
  } catch {
35329
35544
  return -1;
35330
35545
  } finally {
35331
- fs52.closeSync(fd);
35546
+ fs53.closeSync(fd);
35332
35547
  }
35333
35548
  }
35334
35549
  function safeCanaryCtxValues() {
@@ -35339,7 +35554,7 @@ function safeCanaryCtxValues() {
35339
35554
  }
35340
35555
  }
35341
35556
  function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
35342
- const cwd = typeof obj.cwd === "string" && path54.isAbsolute(obj.cwd) ? obj.cwd : void 0;
35557
+ const cwd = typeof obj.cwd === "string" && path55.isAbsolute(obj.cwd) ? obj.cwd : void 0;
35343
35558
  return {
35344
35559
  sessionId,
35345
35560
  lineIndex,
@@ -35355,7 +35570,7 @@ function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
35355
35570
  // line-level DLP runs above already
35356
35571
  canaryValues: canaryVals,
35357
35572
  ...cwd && { cwd },
35358
- homeDirs: [os44.homedir()]
35573
+ homeDirs: [os45.homedir()]
35359
35574
  };
35360
35575
  }
35361
35576
  function extractFindingsFromLine(line, sessionId, lineIndex, canaryVals = []) {
@@ -35443,7 +35658,7 @@ async function tickForensicBroadcast(offsets) {
35443
35658
  continue;
35444
35659
  }
35445
35660
  if (size <= offset) continue;
35446
- const sessionId = path54.basename(file2, ".jsonl");
35661
+ const sessionId = path55.basename(file2, ".jsonl");
35447
35662
  const newOffset = await scanDelta(file2, offset, (obj, lineIndex) => {
35448
35663
  out.push(...extractFindingsFromLine(obj, sessionId, lineIndex, canaryVals));
35449
35664
  });
@@ -35499,7 +35714,7 @@ function emptyTick(uploadAs) {
35499
35714
  function readRawWatermarkPreservingOffsets() {
35500
35715
  let raw;
35501
35716
  try {
35502
- raw = fs52.readFileSync(WATERMARK_FILE(), "utf-8");
35717
+ raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
35503
35718
  } catch {
35504
35719
  return null;
35505
35720
  }
@@ -35534,13 +35749,13 @@ async function runActualTick(wm) {
35534
35749
  if (!known) {
35535
35750
  let mtimeMs = 0;
35536
35751
  try {
35537
- mtimeMs = fs52.statSync(filePath).mtime.getTime();
35752
+ mtimeMs = fs53.statSync(filePath).mtime.getTime();
35538
35753
  } catch {
35539
35754
  continue;
35540
35755
  }
35541
35756
  if (mtimeMs >= watermarkCreatedAt) {
35542
35757
  filesNew++;
35543
- const sessionId2 = path54.basename(filePath, ".jsonl");
35758
+ const sessionId2 = path55.basename(filePath, ".jsonl");
35544
35759
  const newScannedTo2 = await scanDelta(filePath, 0, (obj, lineIndex) => {
35545
35760
  totalToolCalls++;
35546
35761
  toolCallsBySession[sessionId2] = (toolCallsBySession[sessionId2] ?? 0) + 1;
@@ -35558,7 +35773,7 @@ async function runActualTick(wm) {
35558
35773
  filesSkipped++;
35559
35774
  continue;
35560
35775
  }
35561
- const sessionId = path54.basename(filePath, ".jsonl");
35776
+ const sessionId = path55.basename(filePath, ".jsonl");
35562
35777
  const newScannedTo = await scanDelta(filePath, known.scannedTo, (obj, lineIndex) => {
35563
35778
  totalToolCalls++;
35564
35779
  toolCallsBySession[sessionId] = (toolCallsBySession[sessionId] ?? 0) + 1;
@@ -35593,8 +35808,8 @@ var init_scan_watermark = __esm({
35593
35808
  init_registry2();
35594
35809
  init_config();
35595
35810
  init_dist();
35596
- PROJECTS_DIR = () => path54.join(os44.homedir(), ".claude", "projects");
35597
- WATERMARK_FILE = () => path54.join(os44.homedir(), ".node9", "scan-watermark.json");
35811
+ PROJECTS_DIR = () => path55.join(os45.homedir(), ".claude", "projects");
35812
+ WATERMARK_FILE = () => path55.join(os45.homedir(), ".node9", "scan-watermark.json");
35598
35813
  MAX_LINE_BYTES = 2 * 1024 * 1024;
35599
35814
  WATERMARK_SCHEMA_VERSION = 2;
35600
35815
  LONG_OUTPUT_THRESHOLD_BYTES2 = LONG_OUTPUT_THRESHOLD_BYTES;
@@ -35610,10 +35825,10 @@ __export(scan_upload_history_exports, {
35610
35825
  parseSinceCutoff: () => parseSinceCutoff,
35611
35826
  runUploadHistory: () => runUploadHistory
35612
35827
  });
35613
- import fs53 from "fs";
35828
+ import fs54 from "fs";
35614
35829
  import https from "https";
35615
- import os45 from "os";
35616
- import path55 from "path";
35830
+ import os46 from "os";
35831
+ import path56 from "path";
35617
35832
  import chalk5 from "chalk";
35618
35833
  function emptySignals2() {
35619
35834
  return {
@@ -35648,40 +35863,40 @@ function parseSinceCutoff(raw, now = /* @__PURE__ */ new Date()) {
35648
35863
  return now.getTime() - 90 * 864e5;
35649
35864
  }
35650
35865
  function* iterateJsonlFiles(cutoffMs) {
35651
- const projectsDir = path55.join(os45.homedir(), ".claude", "projects");
35866
+ const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
35652
35867
  let dirs;
35653
35868
  try {
35654
- dirs = fs53.readdirSync(projectsDir);
35869
+ dirs = fs54.readdirSync(projectsDir);
35655
35870
  } catch {
35656
35871
  return;
35657
35872
  }
35658
35873
  for (const dir2 of dirs) {
35659
- const dirPath = path55.join(projectsDir, dir2);
35874
+ const dirPath = path56.join(projectsDir, dir2);
35660
35875
  let stats;
35661
35876
  try {
35662
- stats = fs53.statSync(dirPath);
35877
+ stats = fs54.statSync(dirPath);
35663
35878
  } catch {
35664
35879
  continue;
35665
35880
  }
35666
35881
  if (!stats.isDirectory()) continue;
35667
35882
  let files;
35668
35883
  try {
35669
- files = fs53.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
35884
+ files = fs54.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
35670
35885
  } catch {
35671
35886
  continue;
35672
35887
  }
35673
35888
  for (const file2 of files) {
35674
- const filePath = path55.join(dirPath, file2);
35889
+ const filePath = path56.join(dirPath, file2);
35675
35890
  let mtime = 0;
35676
35891
  try {
35677
- mtime = fs53.statSync(filePath).mtimeMs;
35892
+ mtime = fs54.statSync(filePath).mtimeMs;
35678
35893
  } catch {
35679
35894
  continue;
35680
35895
  }
35681
35896
  if (mtime < cutoffMs) continue;
35682
35897
  yield {
35683
35898
  filePath,
35684
- sessionId: path55.basename(file2, ".jsonl"),
35899
+ sessionId: path56.basename(file2, ".jsonl"),
35685
35900
  projectDir: dir2
35686
35901
  };
35687
35902
  }
@@ -35749,7 +35964,7 @@ async function runUploadHistory(opts) {
35749
35964
  filesScanned++;
35750
35965
  let content;
35751
35966
  try {
35752
- content = fs53.readFileSync(filePath, "utf8");
35967
+ content = fs54.readFileSync(filePath, "utf8");
35753
35968
  } catch {
35754
35969
  continue;
35755
35970
  }
@@ -35823,10 +36038,10 @@ async function runUploadHistory(opts) {
35823
36038
  const costUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/cost-sync") : `${creds.apiUrl.replace(/\/$/, "")}/cost-sync`;
35824
36039
  let username = "unknown";
35825
36040
  try {
35826
- username = os45.userInfo().username;
36041
+ username = os46.userInfo().username;
35827
36042
  } catch {
35828
36043
  }
35829
- const machineId = `${os45.hostname()}:${username}`;
36044
+ const machineId = `${os46.hostname()}:${username}`;
35830
36045
  await postJson(costUrl, creds.apiKey, {
35831
36046
  machineId,
35832
36047
  entries: dailyEntries
@@ -35900,9 +36115,9 @@ var init_scan_upload_history = __esm({
35900
36115
 
35901
36116
  // src/cli/commands/scan.ts
35902
36117
  import chalk6 from "chalk";
35903
- import fs54 from "fs";
35904
- import path56 from "path";
35905
- import os46 from "os";
36118
+ import fs55 from "fs";
36119
+ import path57 from "path";
36120
+ import os47 from "os";
35906
36121
  import stringWidth2 from "string-width";
35907
36122
  function dayKey(ts) {
35908
36123
  if (!ts) return "unknown";
@@ -36045,7 +36260,7 @@ function emptyScanDedup() {
36045
36260
  return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
36046
36261
  }
36047
36262
  function displayHome(p) {
36048
- const home = os46.homedir();
36263
+ const home = os47.homedir();
36049
36264
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
36050
36265
  }
36051
36266
  function dlpKey(patternName, redactedSample, projLabel, timestamp2) {
@@ -36184,7 +36399,7 @@ function detectLoops(calls, project, sessionId, agent) {
36184
36399
  return findings.sort((a, b) => b.count - a.count);
36185
36400
  }
36186
36401
  function geminiChatFiles(chatsDir) {
36187
- const files = fs54.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
36402
+ const files = fs55.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
36188
36403
  const seen = /* @__PURE__ */ new Set();
36189
36404
  return files.filter((f) => {
36190
36405
  const id = f.replace(/\.jsonl?$/, "");
@@ -36196,14 +36411,14 @@ function geminiChatFiles(chatsDir) {
36196
36411
  function countScanFiles(startDate = null) {
36197
36412
  let total = 0;
36198
36413
  const counts = (p) => fileMayHoldWindow(fileStamp(p), startDate);
36199
- const claudeDir = path56.join(os46.homedir(), ".claude", "projects");
36200
- if (fs54.existsSync(claudeDir)) {
36414
+ const claudeDir = path57.join(os47.homedir(), ".claude", "projects");
36415
+ if (fs55.existsSync(claudeDir)) {
36201
36416
  try {
36202
- for (const proj of fs54.readdirSync(claudeDir)) {
36203
- const p = path56.join(claudeDir, proj);
36417
+ for (const proj of fs55.readdirSync(claudeDir)) {
36418
+ const p = path57.join(claudeDir, proj);
36204
36419
  try {
36205
- if (!fs54.statSync(p).isDirectory()) continue;
36206
- total += listSessionFiles(p).filter((f) => counts(path56.join(p, f))).length;
36420
+ if (!fs55.statSync(p).isDirectory()) continue;
36421
+ total += listSessionFiles(p).filter((f) => counts(path57.join(p, f))).length;
36207
36422
  } catch {
36208
36423
  continue;
36209
36424
  }
@@ -36211,18 +36426,18 @@ function countScanFiles(startDate = null) {
36211
36426
  } catch {
36212
36427
  }
36213
36428
  }
36214
- const geminiDir = path56.join(os46.homedir(), ".gemini", "tmp");
36215
- if (fs54.existsSync(geminiDir)) {
36429
+ const geminiDir = path57.join(os47.homedir(), ".gemini", "tmp");
36430
+ if (fs55.existsSync(geminiDir)) {
36216
36431
  try {
36217
- for (const slug2 of fs54.readdirSync(geminiDir)) {
36218
- const p = path56.join(geminiDir, slug2);
36432
+ for (const slug2 of fs55.readdirSync(geminiDir)) {
36433
+ const p = path57.join(geminiDir, slug2);
36219
36434
  try {
36220
- if (!fs54.statSync(p).isDirectory()) continue;
36221
- const chatsDir = path56.join(p, "chats");
36222
- if (fs54.existsSync(chatsDir)) {
36435
+ if (!fs55.statSync(p).isDirectory()) continue;
36436
+ const chatsDir = path57.join(p, "chats");
36437
+ if (fs55.existsSync(chatsDir)) {
36223
36438
  try {
36224
36439
  total += geminiChatFiles(chatsDir).filter(
36225
- (f) => counts(path56.join(chatsDir, f))
36440
+ (f) => counts(path57.join(chatsDir, f))
36226
36441
  ).length;
36227
36442
  } catch {
36228
36443
  }
@@ -36235,13 +36450,13 @@ function countScanFiles(startDate = null) {
36235
36450
  }
36236
36451
  }
36237
36452
  for (const surface of ["antigravity-cli", "antigravity-ide"]) {
36238
- const brainDir = path56.join(os46.homedir(), ".gemini", surface, "brain");
36239
- if (!fs54.existsSync(brainDir)) continue;
36453
+ const brainDir = path57.join(os47.homedir(), ".gemini", surface, "brain");
36454
+ if (!fs55.existsSync(brainDir)) continue;
36240
36455
  try {
36241
- for (const conv of fs54.readdirSync(brainDir)) {
36242
- const convPath = path56.join(brainDir, conv);
36456
+ for (const conv of fs55.readdirSync(brainDir)) {
36457
+ const convPath = path57.join(brainDir, conv);
36243
36458
  try {
36244
- if (!fs54.statSync(convPath).isDirectory()) continue;
36459
+ if (!fs55.statSync(convPath).isDirectory()) continue;
36245
36460
  const transcript = antigravityTranscriptPath(convPath);
36246
36461
  if (transcript && counts(transcript)) total += 1;
36247
36462
  } catch {
@@ -36251,12 +36466,12 @@ function countScanFiles(startDate = null) {
36251
36466
  } catch {
36252
36467
  }
36253
36468
  }
36254
- const copilotDir = path56.join(os46.homedir(), ".copilot", "session-state");
36255
- if (fs54.existsSync(copilotDir)) {
36469
+ const copilotDir = path57.join(os47.homedir(), ".copilot", "session-state");
36470
+ if (fs55.existsSync(copilotDir)) {
36256
36471
  try {
36257
- for (const sid of fs54.readdirSync(copilotDir)) {
36258
- const events = path56.join(copilotDir, sid, "events.jsonl");
36259
- if (fs54.existsSync(events) && counts(events)) total += 1;
36472
+ for (const sid of fs55.readdirSync(copilotDir)) {
36473
+ const events = path57.join(copilotDir, sid, "events.jsonl");
36474
+ if (fs55.existsSync(events) && counts(events)) total += 1;
36260
36475
  }
36261
36476
  } catch {
36262
36477
  }
@@ -36280,7 +36495,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
36280
36495
  const session = { sessionId, costUSD: 0, toolCalls: 0 };
36281
36496
  let raw;
36282
36497
  try {
36283
- raw = fs54.readFileSync(path56.join(projPath, file2), "utf-8");
36498
+ raw = fs55.readFileSync(path57.join(projPath, file2), "utf-8");
36284
36499
  } catch {
36285
36500
  return false;
36286
36501
  }
@@ -36349,7 +36564,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
36349
36564
  if (block.type !== "tool_result") continue;
36350
36565
  const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
36351
36566
  if (filePath) {
36352
- const ext = path56.extname(filePath).toLowerCase();
36567
+ const ext = path57.extname(filePath).toLowerCase();
36353
36568
  if (CODE_EXTENSIONS.has(ext)) continue;
36354
36569
  }
36355
36570
  const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
@@ -36469,9 +36684,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
36469
36684
  return true;
36470
36685
  }
36471
36686
  async function processClaudeProject(proj, projectsDir, judge, cache7, startDate, result, dedup, canaryVals, onProgress, onLine) {
36472
- const projPath = path56.join(projectsDir, proj);
36687
+ const projPath = path57.join(projectsDir, proj);
36473
36688
  try {
36474
- if (!fs54.statSync(projPath).isDirectory()) return;
36689
+ if (!fs55.statSync(projPath).isDirectory()) return;
36475
36690
  } catch {
36476
36691
  return;
36477
36692
  }
@@ -36483,7 +36698,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
36483
36698
  return;
36484
36699
  }
36485
36700
  for (const file2 of files) {
36486
- const filePath = path56.join(projPath, file2);
36701
+ const filePath = path57.join(projPath, file2);
36487
36702
  const stamp = fileStamp(filePath);
36488
36703
  if (!fileMayHoldWindow(stamp, startDate)) continue;
36489
36704
  result.filesScanned++;
@@ -36514,7 +36729,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
36514
36729
  }
36515
36730
  function claudeProjectLabel(dirName) {
36516
36731
  const name = decodeURIComponent(dirName);
36517
- const homeDashed = os46.homedir().replace(/\//g, "-");
36732
+ const homeDashed = os47.homedir().replace(/\//g, "-");
36518
36733
  const label3 = name.startsWith(homeDashed) ? "~" + decodeProjectDirName(name.slice(homeDashed.length)) : decodeProjectDirName(name);
36519
36734
  return stripTerminalEscapes(label3).slice(0, 40);
36520
36735
  }
@@ -36625,12 +36840,12 @@ function mergeFileScan(result, file2, dedup, startDate = null) {
36625
36840
  }
36626
36841
  }
36627
36842
  async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
36628
- const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
36843
+ const projectsDir = path57.join(os47.homedir(), ".claude", "projects");
36629
36844
  const result = emptyClaudeScan();
36630
- if (!fs54.existsSync(projectsDir)) return result;
36845
+ if (!fs55.existsSync(projectsDir)) return result;
36631
36846
  let projDirs;
36632
36847
  try {
36633
- projDirs = fs54.readdirSync(projectsDir);
36848
+ projDirs = fs55.readdirSync(projectsDir);
36634
36849
  } catch {
36635
36850
  return result;
36636
36851
  }
@@ -36658,7 +36873,7 @@ async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
36658
36873
  }
36659
36874
  async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36660
36875
  const canaryVals = safeCanaryScanValues();
36661
- const tmpDir = path56.join(os46.homedir(), ".gemini", "tmp");
36876
+ const tmpDir = path57.join(os47.homedir(), ".gemini", "tmp");
36662
36877
  const result = {
36663
36878
  filesScanned: 0,
36664
36879
  sessions: 0,
@@ -36675,31 +36890,31 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36675
36890
  perSession: []
36676
36891
  };
36677
36892
  const dedup = emptyScanDedup();
36678
- if (!fs54.existsSync(tmpDir)) return result;
36893
+ if (!fs55.existsSync(tmpDir)) return result;
36679
36894
  let slugDirs;
36680
36895
  try {
36681
- slugDirs = fs54.readdirSync(tmpDir);
36896
+ slugDirs = fs55.readdirSync(tmpDir);
36682
36897
  } catch {
36683
36898
  return result;
36684
36899
  }
36685
36900
  const judge = new HistoryJudge();
36686
36901
  const cache7 = openScanCache(judge, opts);
36687
36902
  for (const slug2 of slugDirs) {
36688
- const slugPath = path56.join(tmpDir, slug2);
36903
+ const slugPath = path57.join(tmpDir, slug2);
36689
36904
  try {
36690
- if (!fs54.statSync(slugPath).isDirectory()) continue;
36905
+ if (!fs55.statSync(slugPath).isDirectory()) continue;
36691
36906
  } catch {
36692
36907
  continue;
36693
36908
  }
36694
36909
  let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
36695
36910
  try {
36696
36911
  projLabel = stripTerminalEscapes(
36697
- fs54.readFileSync(path56.join(slugPath, ".project_root"), "utf-8").trim()
36698
- ).replace(os46.homedir(), "~").slice(0, 40);
36912
+ fs55.readFileSync(path57.join(slugPath, ".project_root"), "utf-8").trim()
36913
+ ).replace(os47.homedir(), "~").slice(0, 40);
36699
36914
  } catch {
36700
36915
  }
36701
- const chatsDir = path56.join(slugPath, "chats");
36702
- if (!fs54.existsSync(chatsDir)) continue;
36916
+ const chatsDir = path57.join(slugPath, "chats");
36917
+ if (!fs55.existsSync(chatsDir)) continue;
36703
36918
  let chatFiles;
36704
36919
  try {
36705
36920
  chatFiles = geminiChatFiles(chatsDir);
@@ -36708,7 +36923,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36708
36923
  }
36709
36924
  for (const chatFile of chatFiles) {
36710
36925
  const sessionId = chatFile.replace(/\.jsonl?$/, "");
36711
- const filePath = path56.join(chatsDir, chatFile);
36926
+ const filePath = path57.join(chatsDir, chatFile);
36712
36927
  const stamp = fileStamp(filePath);
36713
36928
  if (!fileMayHoldWindow(stamp, startDate)) continue;
36714
36929
  result.filesScanned++;
@@ -36722,7 +36937,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36722
36937
  const fileDedup = emptyScanDedup();
36723
36938
  let raw;
36724
36939
  try {
36725
- raw = fs54.readFileSync(path56.join(chatsDir, chatFile), "utf-8");
36940
+ raw = fs55.readFileSync(path57.join(chatsDir, chatFile), "utf-8");
36726
36941
  } catch {
36727
36942
  continue;
36728
36943
  }
@@ -36859,13 +37074,13 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36859
37074
  return result;
36860
37075
  }
36861
37076
  function antigravityBrainDirs() {
36862
- return ["antigravity-cli", "antigravity-ide"].map((surface) => path56.join(os46.homedir(), ".gemini", surface, "brain")).filter((p) => fs54.existsSync(p));
37077
+ return ["antigravity-cli", "antigravity-ide"].map((surface) => path57.join(os47.homedir(), ".gemini", surface, "brain")).filter((p) => fs55.existsSync(p));
36863
37078
  }
36864
37079
  function antigravityTranscriptPath(convPath) {
36865
- const logsDir = path56.join(convPath, ".system_generated", "logs");
37080
+ const logsDir = path57.join(convPath, ".system_generated", "logs");
36866
37081
  for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
36867
- const p = path56.join(logsDir, name);
36868
- if (fs54.existsSync(p)) return p;
37082
+ const p = path57.join(logsDir, name);
37083
+ if (fs55.existsSync(p)) return p;
36869
37084
  }
36870
37085
  return null;
36871
37086
  }
@@ -36895,14 +37110,14 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
36895
37110
  for (const brainDir of brainDirs) {
36896
37111
  let convDirs;
36897
37112
  try {
36898
- convDirs = fs54.readdirSync(brainDir);
37113
+ convDirs = fs55.readdirSync(brainDir);
36899
37114
  } catch {
36900
37115
  continue;
36901
37116
  }
36902
37117
  for (const conv of convDirs) {
36903
- const convPath = path56.join(brainDir, conv);
37118
+ const convPath = path57.join(brainDir, conv);
36904
37119
  try {
36905
- if (!fs54.statSync(convPath).isDirectory()) continue;
37120
+ if (!fs55.statSync(convPath).isDirectory()) continue;
36906
37121
  } catch {
36907
37122
  continue;
36908
37123
  }
@@ -36921,7 +37136,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
36921
37136
  const fileDedup = emptyScanDedup();
36922
37137
  let raw;
36923
37138
  try {
36924
- raw = fs54.readFileSync(transcriptFile, "utf-8");
37139
+ raw = fs55.readFileSync(transcriptFile, "utf-8");
36925
37140
  } catch {
36926
37141
  continue;
36927
37142
  }
@@ -36996,7 +37211,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
36996
37211
  day.bashCalls++;
36997
37212
  const cwd = String(input.cwd ?? "");
36998
37213
  if (cwd && projLabel === conv.slice(0, 8)) {
36999
- projLabel = stripTerminalEscapes(cwd).replace(os46.homedir(), "~").slice(0, 40);
37214
+ projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
37000
37215
  }
37001
37216
  }
37002
37217
  const rawCmd = String(input.command ?? "").trimStart();
@@ -37022,7 +37237,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
37022
37237
  timestamp: timestamp2,
37023
37238
  sessionId,
37024
37239
  project: projLabel,
37025
- cwd: typeof input.cwd === "string" && path56.isAbsolute(input.cwd) ? input.cwd : void 0
37240
+ cwd: typeof input.cwd === "string" && path57.isAbsolute(input.cwd) ? input.cwd : void 0
37026
37241
  },
37027
37242
  fileResult,
37028
37243
  fileDedup
@@ -37045,7 +37260,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
37045
37260
  }
37046
37261
  async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37047
37262
  const canaryVals = safeCanaryScanValues();
37048
- const sessionDir = path56.join(os46.homedir(), ".copilot", "session-state");
37263
+ const sessionDir = path57.join(os47.homedir(), ".copilot", "session-state");
37049
37264
  const result = {
37050
37265
  filesScanned: 0,
37051
37266
  sessions: 0,
@@ -37063,18 +37278,18 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37063
37278
  perSession: []
37064
37279
  };
37065
37280
  const dedup = emptyScanDedup();
37066
- if (!fs54.existsSync(sessionDir)) return result;
37281
+ if (!fs55.existsSync(sessionDir)) return result;
37067
37282
  let sessionIds;
37068
37283
  try {
37069
- sessionIds = fs54.readdirSync(sessionDir);
37284
+ sessionIds = fs55.readdirSync(sessionDir);
37070
37285
  } catch {
37071
37286
  return result;
37072
37287
  }
37073
37288
  const judge = new HistoryJudge();
37074
37289
  const cache7 = openScanCache(judge, opts);
37075
37290
  for (const sessionId of sessionIds) {
37076
- const eventsPath = path56.join(sessionDir, sessionId, "events.jsonl");
37077
- if (!fs54.existsSync(eventsPath)) continue;
37291
+ const eventsPath = path57.join(sessionDir, sessionId, "events.jsonl");
37292
+ if (!fs55.existsSync(eventsPath)) continue;
37078
37293
  const stamp = fileStamp(eventsPath);
37079
37294
  if (!fileMayHoldWindow(stamp, startDate)) continue;
37080
37295
  result.filesScanned++;
@@ -37088,7 +37303,7 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37088
37303
  const fileDedup = emptyScanDedup();
37089
37304
  let raw;
37090
37305
  try {
37091
- raw = fs54.readFileSync(eventsPath, "utf-8");
37306
+ raw = fs55.readFileSync(eventsPath, "utf-8");
37092
37307
  } catch {
37093
37308
  continue;
37094
37309
  }
@@ -37113,8 +37328,8 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37113
37328
  if (ev.type === "session.start") {
37114
37329
  const cwd = ev.data?.context?.cwd;
37115
37330
  if (typeof cwd === "string" && cwd) {
37116
- projLabel = stripTerminalEscapes(cwd).replace(os46.homedir(), "~").slice(0, 40);
37117
- if (path56.isAbsolute(cwd)) sessionCwd = cwd;
37331
+ projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
37332
+ if (path57.isAbsolute(cwd)) sessionCwd = cwd;
37118
37333
  }
37119
37334
  continue;
37120
37335
  }
@@ -37247,7 +37462,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
37247
37462
  const fileDedup = emptyScanDedup();
37248
37463
  let lines;
37249
37464
  try {
37250
- lines = fs54.readFileSync(filePath, "utf-8").split("\n");
37465
+ lines = fs55.readFileSync(filePath, "utf-8").split("\n");
37251
37466
  } catch {
37252
37467
  continue;
37253
37468
  }
@@ -37270,8 +37485,8 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
37270
37485
  sessionId = String(payload["id"] ?? filePath);
37271
37486
  startTime = String(payload["timestamp"] ?? "");
37272
37487
  const cwd = String(payload["cwd"] ?? "");
37273
- projLabel = stripTerminalEscapes(cwd.replace(os46.homedir(), "~")).slice(0, 40);
37274
- if (path56.isAbsolute(cwd)) sessionCwd = cwd;
37488
+ projLabel = stripTerminalEscapes(cwd.replace(os47.homedir(), "~")).slice(0, 40);
37489
+ if (path57.isAbsolute(cwd)) sessionCwd = cwd;
37275
37490
  continue;
37276
37491
  }
37277
37492
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
@@ -37388,17 +37603,17 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
37388
37603
  return result;
37389
37604
  }
37390
37605
  function scanShellConfig() {
37391
- const home = os46.homedir();
37606
+ const home = os47.homedir();
37392
37607
  const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
37393
- (f) => path56.join(home, f)
37608
+ (f) => path57.join(home, f)
37394
37609
  );
37395
37610
  const findings = [];
37396
37611
  const seen = /* @__PURE__ */ new Set();
37397
37612
  for (const filePath of configFiles) {
37398
- if (!fs54.existsSync(filePath)) continue;
37613
+ if (!fs55.existsSync(filePath)) continue;
37399
37614
  let lines;
37400
37615
  try {
37401
- lines = fs54.readFileSync(filePath, "utf-8").split("\n");
37616
+ lines = fs55.readFileSync(filePath, "utf-8").split("\n");
37402
37617
  } catch {
37403
37618
  continue;
37404
37619
  }
@@ -37728,25 +37943,25 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
37728
37943
  const topLines = [];
37729
37944
  if (scan.canaryFindings.length > 0) {
37730
37945
  const latest = scan.canaryFindings[0];
37731
- const rel = relativeDate(latest.timestamp, now);
37946
+ const rel = relativeAgo(latest.timestamp, now);
37732
37947
  const n = scan.canaryFindings.length;
37733
37948
  topLines.push(
37734
37949
  mkLine(
37735
37950
  ["\u{1FAA4} ", chalk6.red],
37736
37951
  [`${n} decoy credential${n !== 1 ? "s" : ""} tripped `, chalk6.bold],
37737
- [`(latest: ${rel} ago, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
37952
+ [`(latest: ${rel}, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
37738
37953
  )
37739
37954
  );
37740
37955
  }
37741
37956
  if (scan.dlpFindings.length > 0) {
37742
37957
  const latest = scan.dlpFindings[0];
37743
- const rel = relativeDate(latest.timestamp, now);
37958
+ const rel = relativeAgo(latest.timestamp, now);
37744
37959
  const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
37745
37960
  topLines.push(
37746
37961
  mkLine(
37747
37962
  ["\u{1F6A8} ", chalk6.red],
37748
37963
  [`${scan.dlpFindings.length} ${noun} in tool input `, chalk6.bold],
37749
- [`(latest: ${rel} ago, ${latest.patternName})`, chalk6.dim]
37964
+ [`(latest: ${rel}, ${latest.patternName})`, chalk6.dim]
37750
37965
  )
37751
37966
  );
37752
37967
  }
@@ -38315,7 +38530,7 @@ function registerScanCommand(program2) {
38315
38530
  if (!drillDown) {
38316
38531
  const useInk2 = !options.classic;
38317
38532
  if (useInk2) {
38318
- const scanInkPath = path56.join(__dirname, "scan-ink.mjs");
38533
+ const scanInkPath = path57.join(__dirname, "scan-ink.mjs");
38319
38534
  const dynamicImport = new Function("id", "return import(id)");
38320
38535
  const mod = await dynamicImport(`file://${scanInkPath}`);
38321
38536
  const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
@@ -38672,12 +38887,12 @@ var init_scan = __esm({
38672
38887
  });
38673
38888
 
38674
38889
  // src/daemon/build-id.ts
38675
- import fs55 from "fs";
38676
- import path57 from "path";
38890
+ import fs56 from "fs";
38891
+ import path58 from "path";
38677
38892
  function readOwnVersion() {
38678
38893
  for (const rel of ["../package.json", "../../package.json"]) {
38679
38894
  try {
38680
- const raw = fs55.readFileSync(path57.join(__dirname, rel), "utf-8");
38895
+ const raw = fs56.readFileSync(path58.join(__dirname, rel), "utf-8");
38681
38896
  const v = JSON.parse(raw).version;
38682
38897
  if (typeof v === "string" && v.length > 0) return v;
38683
38898
  } catch {
@@ -38688,7 +38903,7 @@ function readOwnVersion() {
38688
38903
  function computeBuildId(entry = process.argv[1] ?? "") {
38689
38904
  let mtimeMs = 0;
38690
38905
  try {
38691
- if (entry) mtimeMs = fs55.statSync(entry).mtimeMs;
38906
+ if (entry) mtimeMs = fs56.statSync(entry).mtimeMs;
38692
38907
  } catch {
38693
38908
  }
38694
38909
  return { version: readOwnVersion(), mtimeMs };
@@ -38829,8 +39044,8 @@ var init_suggestion_tracker = __esm({
38829
39044
  });
38830
39045
 
38831
39046
  // src/daemon/taint-store.ts
38832
- import fs56 from "fs";
38833
- import path58 from "path";
39047
+ import fs57 from "fs";
39048
+ import path59 from "path";
38834
39049
  var DEFAULT_TTL_MS, TaintStore, SESSION_TAINT_TTL_MS2, SessionTaintStore;
38835
39050
  var init_taint_store = __esm({
38836
39051
  "src/daemon/taint-store.ts"() {
@@ -38900,9 +39115,9 @@ var init_taint_store = __esm({
38900
39115
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
38901
39116
  _resolve(filePath) {
38902
39117
  try {
38903
- return fs56.realpathSync.native(path58.resolve(filePath));
39118
+ return fs57.realpathSync.native(path59.resolve(filePath));
38904
39119
  } catch {
38905
- return path58.resolve(filePath);
39120
+ return path59.resolve(filePath);
38906
39121
  }
38907
39122
  }
38908
39123
  };
@@ -38958,7 +39173,7 @@ var init_taint_store = __esm({
38958
39173
  });
38959
39174
 
38960
39175
  // src/daemon/written-store.ts
38961
- import path59 from "path";
39176
+ import path60 from "path";
38962
39177
  var SESSION_WRITE_TTL_MS, MAX_WRITTEN, MAX_PATH, SessionWriteStore;
38963
39178
  var init_written_store = __esm({
38964
39179
  "src/daemon/written-store.ts"() {
@@ -38970,7 +39185,7 @@ var init_written_store = __esm({
38970
39185
  records = /* @__PURE__ */ new Map();
38971
39186
  key(p) {
38972
39187
  if (typeof p !== "string" || !p || p.length > MAX_PATH) return null;
38973
- return path59.resolve(p);
39188
+ return path60.resolve(p);
38974
39189
  }
38975
39190
  /** Record that these absolute paths were (or are about to be) written. */
38976
39191
  record(paths, ttlMs = SESSION_WRITE_TTL_MS) {
@@ -39301,9 +39516,9 @@ var init_cards = __esm({
39301
39516
 
39302
39517
  // src/daemon/state.ts
39303
39518
  import net2 from "net";
39304
- import fs57 from "fs";
39305
- import path60 from "path";
39306
- import os47 from "os";
39519
+ import fs58 from "fs";
39520
+ import path61 from "path";
39521
+ import os48 from "os";
39307
39522
  import { randomUUID as randomUUID6 } from "crypto";
39308
39523
  function onCardDecided(card) {
39309
39524
  const r = card.record;
@@ -39329,8 +39544,8 @@ function onCardDecided(card) {
39329
39544
  }
39330
39545
  function loadInsightCounts() {
39331
39546
  try {
39332
- if (!fs57.existsSync(INSIGHT_COUNTS_FILE)) return;
39333
- const data = JSON.parse(fs57.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
39547
+ if (!fs58.existsSync(INSIGHT_COUNTS_FILE)) return;
39548
+ const data = JSON.parse(fs58.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
39334
39549
  for (const [tool, count] of Object.entries(data)) {
39335
39550
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
39336
39551
  }
@@ -39398,16 +39613,16 @@ function appendAuditLog(data) {
39398
39613
  ...data.channel && { channel: data.channel },
39399
39614
  ...data.cardId && { cardId: data.cardId }
39400
39615
  };
39401
- const dir2 = path60.dirname(AUDIT_LOG_FILE);
39402
- if (!fs57.existsSync(dir2)) fs57.mkdirSync(dir2, { recursive: true });
39403
- fs57.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
39616
+ const dir2 = path61.dirname(AUDIT_LOG_FILE);
39617
+ if (!fs58.existsSync(dir2)) fs58.mkdirSync(dir2, { recursive: true });
39618
+ fs58.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
39404
39619
  } catch {
39405
39620
  }
39406
39621
  }
39407
39622
  function getAuditHistory(limit = 20) {
39408
39623
  try {
39409
- if (!fs57.existsSync(AUDIT_LOG_FILE)) return [];
39410
- const lines = fs57.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
39624
+ if (!fs58.existsSync(AUDIT_LOG_FILE)) return [];
39625
+ const lines = fs58.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
39411
39626
  if (lines.length === 1 && lines[0] === "") return [];
39412
39627
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
39413
39628
  } catch {
@@ -39416,7 +39631,7 @@ function getAuditHistory(limit = 20) {
39416
39631
  }
39417
39632
  function getOrgName() {
39418
39633
  try {
39419
- if (fs57.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
39634
+ if (fs58.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
39420
39635
  } catch {
39421
39636
  }
39422
39637
  return null;
@@ -39428,8 +39643,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
39428
39643
  try {
39429
39644
  let trust = { entries: [] };
39430
39645
  try {
39431
- if (fs57.existsSync(TRUST_FILE2))
39432
- trust = JSON.parse(fs57.readFileSync(TRUST_FILE2, "utf-8"));
39646
+ if (fs58.existsSync(TRUST_FILE2))
39647
+ trust = JSON.parse(fs58.readFileSync(TRUST_FILE2, "utf-8"));
39433
39648
  } catch {
39434
39649
  }
39435
39650
  trust.entries = trust.entries.filter(
@@ -39446,8 +39661,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
39446
39661
  }
39447
39662
  function readPersistentDecisions() {
39448
39663
  try {
39449
- if (fs57.existsSync(DECISIONS_FILE)) {
39450
- return JSON.parse(fs57.readFileSync(DECISIONS_FILE, "utf-8"));
39664
+ if (fs58.existsSync(DECISIONS_FILE)) {
39665
+ return JSON.parse(fs58.readFileSync(DECISIONS_FILE, "utf-8"));
39451
39666
  }
39452
39667
  } catch {
39453
39668
  }
@@ -39475,7 +39690,7 @@ function estimateToolCost(tool, args) {
39475
39690
  const filePath = a.file_path ?? a.path;
39476
39691
  if (filePath) {
39477
39692
  try {
39478
- const bytes = fs57.statSync(filePath).size;
39693
+ const bytes = fs58.statSync(filePath).size;
39479
39694
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
39480
39695
  } catch {
39481
39696
  }
@@ -39543,7 +39758,7 @@ function abandonPending() {
39543
39758
  cards.abandonLocalOnly();
39544
39759
  if (autoStarted && cards.open().length === 0) {
39545
39760
  try {
39546
- fs57.unlinkSync(DAEMON_PID_FILE);
39761
+ fs58.unlinkSync(DAEMON_PID_FILE);
39547
39762
  } catch {
39548
39763
  }
39549
39764
  setTimeout(() => {
@@ -39554,8 +39769,8 @@ function abandonPending() {
39554
39769
  }
39555
39770
  function logActivitySocket(msg) {
39556
39771
  try {
39557
- fs57.appendFileSync(
39558
- path60.join(homeDir, ".node9", "hook-debug.log"),
39772
+ fs58.appendFileSync(
39773
+ path61.join(homeDir, ".node9", "hook-debug.log"),
39559
39774
  `[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
39560
39775
  `
39561
39776
  );
@@ -39577,13 +39792,13 @@ function shouldRebind(now = Date.now()) {
39577
39792
  function startActivitySocket() {
39578
39793
  bindActivitySocket();
39579
39794
  activityHealthInterval = setInterval(() => {
39580
- if (!fs57.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
39795
+ if (!fs58.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
39581
39796
  }, ACTIVITY_HEALTH_PROBE_MS);
39582
39797
  activityHealthInterval.unref();
39583
39798
  process.on("exit", () => {
39584
39799
  if (activityHealthInterval) clearInterval(activityHealthInterval);
39585
39800
  try {
39586
- fs57.unlinkSync(ACTIVITY_SOCKET_PATH2);
39801
+ fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
39587
39802
  } catch {
39588
39803
  }
39589
39804
  });
@@ -39611,7 +39826,7 @@ function attemptRebind(reason) {
39611
39826
  }
39612
39827
  function bindActivitySocket() {
39613
39828
  try {
39614
- fs57.unlinkSync(ACTIVITY_SOCKET_PATH2);
39829
+ fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
39615
39830
  } catch {
39616
39831
  }
39617
39832
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -39720,14 +39935,14 @@ var init_state3 = __esm({
39720
39935
  init_session_history();
39721
39936
  init_atomic_write();
39722
39937
  init_cards();
39723
- homeDir = os47.homedir();
39724
- DAEMON_PID_FILE = path60.join(homeDir, ".node9", "daemon.pid");
39725
- DECISIONS_FILE = path60.join(homeDir, ".node9", "decisions.json");
39726
- AUDIT_LOG_FILE = path60.join(homeDir, ".node9", "audit.log");
39727
- TRUST_FILE2 = path60.join(homeDir, ".node9", "trust.json");
39728
- GLOBAL_CONFIG_FILE = path60.join(homeDir, ".node9", "config.json");
39729
- CREDENTIALS_FILE = path60.join(homeDir, ".node9", "credentials.json");
39730
- INSIGHT_COUNTS_FILE = path60.join(homeDir, ".node9", "insight-counts.json");
39938
+ homeDir = os48.homedir();
39939
+ DAEMON_PID_FILE = path61.join(homeDir, ".node9", "daemon.pid");
39940
+ DECISIONS_FILE = path61.join(homeDir, ".node9", "decisions.json");
39941
+ AUDIT_LOG_FILE = path61.join(homeDir, ".node9", "audit.log");
39942
+ TRUST_FILE2 = path61.join(homeDir, ".node9", "trust.json");
39943
+ GLOBAL_CONFIG_FILE = path61.join(homeDir, ".node9", "config.json");
39944
+ CREDENTIALS_FILE = path61.join(homeDir, ".node9", "credentials.json");
39945
+ INSIGHT_COUNTS_FILE = path61.join(homeDir, ".node9", "insight-counts.json");
39731
39946
  sseClients = /* @__PURE__ */ new Set();
39732
39947
  cards = new CardStore({
39733
39948
  onDecided: onCardDecided,
@@ -39750,7 +39965,7 @@ var init_state3 = __esm({
39750
39965
  "2h": 2 * 60 * 6e4
39751
39966
  };
39752
39967
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
39753
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path60.join(os47.tmpdir(), "node9-activity.sock");
39968
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path61.join(os48.tmpdir(), "node9-activity.sock");
39754
39969
  ACTIVITY_RING_SIZE = 100;
39755
39970
  activityRing = [];
39756
39971
  LARGE_RESPONSE_RING_SIZE = 20;
@@ -39789,13 +40004,13 @@ var init_state3 = __esm({
39789
40004
  });
39790
40005
 
39791
40006
  // src/posture/secrets.ts
39792
- import fs58 from "fs";
39793
- import path61 from "path";
39794
- import os48 from "os";
40007
+ import fs59 from "fs";
40008
+ import path62 from "path";
40009
+ import os49 from "os";
39795
40010
  function displayPath(p, home) {
39796
40011
  if (p === home) return "~";
39797
- const prefix = home.endsWith(path61.sep) ? home : home + path61.sep;
39798
- if (p.startsWith(prefix)) return "~" + path61.sep + p.slice(prefix.length);
40012
+ const prefix = home.endsWith(path62.sep) ? home : home + path62.sep;
40013
+ if (p.startsWith(prefix)) return "~" + path62.sep + p.slice(prefix.length);
39799
40014
  return p;
39800
40015
  }
39801
40016
  function safeRead(file2) {
@@ -39806,8 +40021,8 @@ function safeRead(file2) {
39806
40021
  function candidateFiles(home, cwd) {
39807
40022
  const files = /* @__PURE__ */ new Set();
39808
40023
  try {
39809
- for (const name of fs58.readdirSync(cwd)) {
39810
- if (name === ".env" || name.startsWith(".env.")) files.add(path61.join(cwd, name));
40024
+ for (const name of fs59.readdirSync(cwd)) {
40025
+ if (name === ".env" || name.startsWith(".env.")) files.add(path62.join(cwd, name));
39811
40026
  }
39812
40027
  } catch {
39813
40028
  }
@@ -39815,7 +40030,7 @@ function candidateFiles(home, cwd) {
39815
40030
  if (spec.hookFile) files.add(spec.hookFile(home));
39816
40031
  if (spec.mcpFile) files.add(spec.mcpFile(home));
39817
40032
  }
39818
- files.add(path61.join(home, ".env"));
40033
+ files.add(path62.join(home, ".env"));
39819
40034
  return [...files];
39820
40035
  }
39821
40036
  function credentialMaterial(home) {
@@ -39829,7 +40044,7 @@ function plantedDecoyPaths() {
39829
40044
  }
39830
40045
  }
39831
40046
  function checkSecrets(ctx) {
39832
- const home = ctx.home || os48.homedir();
40047
+ const home = ctx.home || os49.homedir();
39833
40048
  const findings = [];
39834
40049
  const planted = plantedDecoyPaths();
39835
40050
  const plaintext = [];
@@ -39865,7 +40080,7 @@ function checkSecrets(ctx) {
39865
40080
  for (const file2 of credentialMaterial(home)) {
39866
40081
  if (planted.has(file2)) continue;
39867
40082
  try {
39868
- if (fs58.statSync(file2).isFile()) {
40083
+ if (fs59.statSync(file2).isFile()) {
39869
40084
  creds.push(displayPath(file2, home));
39870
40085
  credPaths.push(file2);
39871
40086
  }
@@ -40035,10 +40250,10 @@ var init_templates = __esm({
40035
40250
  });
40036
40251
 
40037
40252
  // src/posture/egress.ts
40038
- import fs59 from "fs";
40253
+ import fs60 from "fs";
40039
40254
  function sandboxEgressWallActive() {
40040
40255
  try {
40041
- return fs59.existsSync(ALLOWED_DOMAINS_PATH);
40256
+ return fs60.existsSync(ALLOWED_DOMAINS_PATH);
40042
40257
  } catch {
40043
40258
  return false;
40044
40259
  }
@@ -40050,7 +40265,7 @@ function evaluateEgressConfig(egress) {
40050
40265
  severity: "high",
40051
40266
  title: "Egress is locked, but node9 is not enforcing it",
40052
40267
  what: "Egress is set to block, but node9 is not applying the policy.",
40053
- why: "node9 isn't wired in (or is in observe mode), so the lock has no effect.",
40268
+ why: "node9 isn't wired in (or is in audit mode), so the lock has no effect.",
40054
40269
  who: "The lock protects nothing until node9 is enforcing in-path.",
40055
40270
  owner: "node9",
40056
40271
  detail: [],
@@ -40067,7 +40282,7 @@ function evaluateEgressConfig(egress) {
40067
40282
  severity: "medium",
40068
40283
  title: "Egress is in review, but node9 is not enforcing it",
40069
40284
  what: "Egress is set to review (approval-gate), but node9 is not applying the policy.",
40070
- why: "node9 isn't wired in (or is in observe mode), so the gate has no effect.",
40285
+ why: "node9 isn't wired in (or is in audit mode), so the gate has no effect.",
40071
40286
  who: "Nothing gates outbound until node9 is enforcing in-path.",
40072
40287
  owner: "node9",
40073
40288
  detail: [],
@@ -40208,14 +40423,14 @@ var init_gate = __esm({
40208
40423
  });
40209
40424
 
40210
40425
  // src/posture/supply-chain.ts
40211
- import os49 from "os";
40212
- import path62 from "path";
40426
+ import os50 from "os";
40427
+ import path63 from "path";
40213
40428
  import { parse as parseToml6 } from "smol-toml";
40214
40429
  function isNode9Managed(command, args = []) {
40215
40430
  if (!command) return false;
40216
- if (path62.basename(command).toLowerCase() === "node9") return true;
40217
- if (PACKAGE_RUNNERS.has(path62.basename(command).toLowerCase())) {
40218
- return args.some((a) => a === "node9" || path62.basename(a).toLowerCase() === "node9");
40431
+ if (path63.basename(command).toLowerCase() === "node9") return true;
40432
+ if (PACKAGE_RUNNERS.has(path63.basename(command).toLowerCase())) {
40433
+ return args.some((a) => a === "node9" || path63.basename(a).toLowerCase() === "node9");
40219
40434
  }
40220
40435
  return false;
40221
40436
  }
@@ -40237,7 +40452,7 @@ function readServers(file2, format, agent) {
40237
40452
  }
40238
40453
  }
40239
40454
  function checkSupplyChain(ctx) {
40240
- const home = ctx.home || os49.homedir();
40455
+ const home = ctx.home || os50.homedir();
40241
40456
  const servers = [];
40242
40457
  for (const spec of AGENT_SPECS) {
40243
40458
  if (!spec.mcpFile) continue;
@@ -40336,11 +40551,11 @@ var init_privilege = __esm({
40336
40551
  });
40337
40552
 
40338
40553
  // src/posture/containment.ts
40339
- import fs60 from "fs";
40554
+ import fs61 from "fs";
40340
40555
  function inContainer() {
40341
- if (fs60.existsSync("/.dockerenv") || fs60.existsSync("/run/.containerenv")) return true;
40556
+ if (fs61.existsSync("/.dockerenv") || fs61.existsSync("/run/.containerenv")) return true;
40342
40557
  try {
40343
- const cgroup = fs60.readFileSync("/proc/1/cgroup", "utf8");
40558
+ const cgroup = fs61.readFileSync("/proc/1/cgroup", "utf8");
40344
40559
  if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
40345
40560
  } catch {
40346
40561
  }
@@ -40386,7 +40601,7 @@ var init_containment = __esm({
40386
40601
  });
40387
40602
 
40388
40603
  // src/posture/inbound.ts
40389
- import fs61 from "fs";
40604
+ import fs62 from "fs";
40390
40605
  function buildNetworkFix(labels) {
40391
40606
  const shielded = [
40392
40607
  ...new Map(
@@ -40444,7 +40659,7 @@ function collectListeners() {
40444
40659
  const byPort = /* @__PURE__ */ new Map();
40445
40660
  for (const file2 of ["/proc/net/tcp", "/proc/net/tcp6"]) {
40446
40661
  try {
40447
- for (const l of parseListeners(fs61.readFileSync(file2, "utf8"))) {
40662
+ for (const l of parseListeners(fs62.readFileSync(file2, "utf8"))) {
40448
40663
  if (!byPort.has(l.port)) byPort.set(l.port, l);
40449
40664
  }
40450
40665
  } catch {
@@ -40456,11 +40671,11 @@ function readProc(pid) {
40456
40671
  let comm = "unknown";
40457
40672
  let cmdline = "";
40458
40673
  try {
40459
- comm = fs61.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
40674
+ comm = fs62.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
40460
40675
  } catch {
40461
40676
  }
40462
40677
  try {
40463
- cmdline = fs61.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
40678
+ cmdline = fs62.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
40464
40679
  } catch {
40465
40680
  }
40466
40681
  return { comm, cmdline };
@@ -40470,21 +40685,21 @@ function resolveProcesses(inodes) {
40470
40685
  if (inodes.size === 0) return map;
40471
40686
  let pids;
40472
40687
  try {
40473
- pids = fs61.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
40688
+ pids = fs62.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
40474
40689
  } catch {
40475
40690
  return map;
40476
40691
  }
40477
40692
  for (const pid of pids) {
40478
40693
  let fds;
40479
40694
  try {
40480
- fds = fs61.readdirSync(`/proc/${pid}/fd`);
40695
+ fds = fs62.readdirSync(`/proc/${pid}/fd`);
40481
40696
  } catch {
40482
40697
  continue;
40483
40698
  }
40484
40699
  for (const fd of fds) {
40485
40700
  let link;
40486
40701
  try {
40487
- link = fs61.readlinkSync(`/proc/${pid}/fd/${fd}`);
40702
+ link = fs62.readlinkSync(`/proc/${pid}/fd/${fd}`);
40488
40703
  } catch {
40489
40704
  continue;
40490
40705
  }
@@ -40589,9 +40804,9 @@ var init_inbound = __esm({
40589
40804
  });
40590
40805
 
40591
40806
  // src/posture/coverage.ts
40592
- import os50 from "os";
40807
+ import os51 from "os";
40593
40808
  function checkCoverage(ctx) {
40594
- const home = ctx.home || os50.homedir();
40809
+ const home = ctx.home || os51.homedir();
40595
40810
  const findings = [];
40596
40811
  const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
40597
40812
  if (protectedAgents.length === 0) {
@@ -40609,13 +40824,14 @@ function checkCoverage(ctx) {
40609
40824
  return findings;
40610
40825
  }
40611
40826
  const mode = getConfig(ctx.cwd).settings.mode;
40612
- if (mode === "observe" || mode === "audit") {
40827
+ if (isRecordOnly(mode)) {
40828
+ const level = levelOf(mode);
40613
40829
  findings.push({
40614
40830
  category: "Coverage",
40615
40831
  severity: "high",
40616
- title: `node9 is in ${mode} mode \u2014 watching, not blocking`,
40832
+ title: `node9 is in ${level} mode \u2014 watching, not blocking`,
40617
40833
  what: "node9 is watching but not actually blocking anything.",
40618
- why: `It's in ${mode} mode, which logs risky actions but lets them through.`,
40834
+ why: `It's in ${level} mode, which logs risky actions but lets them through.`,
40619
40835
  who: "The guardrails above are observed, not enforced.",
40620
40836
  detail: [],
40621
40837
  owner: "node9",
@@ -40628,12 +40844,13 @@ var init_coverage = __esm({
40628
40844
  "src/posture/coverage.ts"() {
40629
40845
  "use strict";
40630
40846
  init_config();
40847
+ init_dist();
40631
40848
  init_agent_wiring();
40632
40849
  }
40633
40850
  });
40634
40851
 
40635
40852
  // src/posture/governance.ts
40636
- import path63 from "path";
40853
+ import path64 from "path";
40637
40854
  function checkData(ctx) {
40638
40855
  const dlp = getConfig(ctx.cwd).policy.dlp;
40639
40856
  if (!dlp?.enabled) {
@@ -40777,7 +40994,7 @@ var init_governance = __esm({
40777
40994
  "src/posture/governance.ts"() {
40778
40995
  "use strict";
40779
40996
  init_config();
40780
- jailProbePath = (home) => path63.join(home, ".aws", "credentials");
40997
+ jailProbePath = (home) => path64.join(home, ".aws", "credentials");
40781
40998
  }
40782
40999
  });
40783
41000
 
@@ -40931,7 +41148,7 @@ async function annotateCoverage(findings, ctx) {
40931
41148
  const worst = verdicts.some((v) => v === "review") ? "review" : "block";
40932
41149
  f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
40933
41150
  if (isRecordOnly(mode) && f.fix?.includes("shield enable project-jail")) {
40934
- f.fix = `node9 already recognises reads of these files, but it is in ${mode ?? "audit"} mode, which logs them and lets them through. Fix it now: set mode to \`standard\` (or \`strict\`) to stop them.`;
41151
+ f.fix = `node9 already recognises reads of these files, but it is in ${levelOf(mode)} mode, which logs them and lets them through. Fix it now: set mode to \`standard\` (or \`strict\`) to stop them.`;
40935
41152
  }
40936
41153
  }
40937
41154
  continue;
@@ -40956,7 +41173,7 @@ var init_enforcement = __esm({
40956
41173
  });
40957
41174
 
40958
41175
  // src/posture/index.ts
40959
- import os51 from "os";
41176
+ import os52 from "os";
40960
41177
  function dropEnforcementRedundant(findings) {
40961
41178
  const coveragePresent = findings.some((f) => f.category === "Coverage");
40962
41179
  if (!coveragePresent) return findings;
@@ -40982,7 +41199,7 @@ async function runChecks(checks, ctx) {
40982
41199
  }
40983
41200
  async function runPosture(opts = {}) {
40984
41201
  const ctx = {
40985
- home: opts.home ?? os51.homedir(),
41202
+ home: opts.home ?? os52.homedir(),
40986
41203
  cwd: opts.cwd ?? process.cwd(),
40987
41204
  agent: opts.agent
40988
41205
  };
@@ -41359,20 +41576,20 @@ var init_ship2 = __esm({
41359
41576
 
41360
41577
  // src/machine-id.ts
41361
41578
  import * as crypto7 from "crypto";
41362
- import * as fs62 from "fs";
41363
- import * as os52 from "os";
41364
- import * as path64 from "path";
41365
- function getMachineId(homeDir2 = os52.homedir()) {
41366
- const file2 = path64.join(homeDir2, ".node9", "machine-id");
41579
+ import * as fs63 from "fs";
41580
+ import * as os53 from "os";
41581
+ import * as path65 from "path";
41582
+ function getMachineId(homeDir2 = os53.homedir()) {
41583
+ const file2 = path65.join(homeDir2, ".node9", "machine-id");
41367
41584
  try {
41368
- const existing = fs62.readFileSync(file2, "utf-8").trim();
41585
+ const existing = fs63.readFileSync(file2, "utf-8").trim();
41369
41586
  if (UUID_RE.test(existing)) return existing;
41370
41587
  } catch {
41371
41588
  }
41372
41589
  const id = crypto7.randomUUID();
41373
41590
  try {
41374
- fs62.mkdirSync(path64.dirname(file2), { recursive: true });
41375
- fs62.writeFileSync(file2, id + "\n", { mode: 384 });
41591
+ fs63.mkdirSync(path65.dirname(file2), { recursive: true });
41592
+ fs63.writeFileSync(file2, id + "\n", { mode: 384 });
41376
41593
  } catch {
41377
41594
  }
41378
41595
  return id;
@@ -41412,10 +41629,10 @@ var init_sync_trigger = __esm({
41412
41629
  });
41413
41630
 
41414
41631
  // src/daemon/sync.ts
41415
- import fs63 from "fs";
41632
+ import fs64 from "fs";
41416
41633
  import https4 from "https";
41417
- import os53 from "os";
41418
- import path65 from "path";
41634
+ import os54 from "os";
41635
+ import path66 from "path";
41419
41636
  function emptySignals3() {
41420
41637
  return {
41421
41638
  dlpFindings: 0,
@@ -41463,7 +41680,7 @@ function readCredentials() {
41463
41680
  }
41464
41681
  function readCachedEtag() {
41465
41682
  try {
41466
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
41683
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
41467
41684
  return typeof raw.etag === "string" ? raw.etag : void 0;
41468
41685
  } catch {
41469
41686
  return void 0;
@@ -41471,7 +41688,7 @@ function readCachedEtag() {
41471
41688
  }
41472
41689
  function readCachedSyncIntervalHours() {
41473
41690
  try {
41474
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
41691
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
41475
41692
  return typeof raw.syncIntervalHours === "number" ? raw.syncIntervalHours : void 0;
41476
41693
  } catch {
41477
41694
  return void 0;
@@ -41488,7 +41705,7 @@ function effectiveSyncIntervalMs() {
41488
41705
  }
41489
41706
  function readSyncHealth() {
41490
41707
  try {
41491
- const raw = JSON.parse(fs63.readFileSync(syncHealthFile(), "utf-8"));
41708
+ const raw = JSON.parse(fs64.readFileSync(syncHealthFile(), "utf-8"));
41492
41709
  return {
41493
41710
  lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : void 0,
41494
41711
  lastChangedAt: typeof raw.lastChangedAt === "string" ? raw.lastChangedAt : void 0,
@@ -41503,17 +41720,17 @@ function readSyncHealth() {
41503
41720
  function writeSyncHealth(h) {
41504
41721
  try {
41505
41722
  const file2 = syncHealthFile();
41506
- const dir2 = path65.dirname(file2);
41507
- if (!fs63.existsSync(dir2)) fs63.mkdirSync(dir2, { recursive: true });
41723
+ const dir2 = path66.dirname(file2);
41724
+ if (!fs64.existsSync(dir2)) fs64.mkdirSync(dir2, { recursive: true });
41508
41725
  const tmp = `${file2}.${process.pid}.tmp`;
41509
- fs63.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
41510
- fs63.renameSync(tmp, file2);
41726
+ fs64.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
41727
+ fs64.renameSync(tmp, file2);
41511
41728
  } catch {
41512
41729
  }
41513
41730
  }
41514
41731
  function readCacheFetchedAt() {
41515
41732
  try {
41516
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
41733
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
41517
41734
  return typeof raw.fetchedAt === "string" ? raw.fetchedAt : void 0;
41518
41735
  } catch {
41519
41736
  return void 0;
@@ -41550,13 +41767,13 @@ function safeNode9Version() {
41550
41767
  let dir2 = __dirname;
41551
41768
  for (let up = 0; up < 5; up++) {
41552
41769
  try {
41553
- const pkg = JSON.parse(fs63.readFileSync(path65.join(dir2, "package.json"), "utf-8"));
41770
+ const pkg = JSON.parse(fs64.readFileSync(path66.join(dir2, "package.json"), "utf-8"));
41554
41771
  if (pkg.name === "@node9/proxy" || pkg.name === "node9-ai") {
41555
41772
  return pkg.version;
41556
41773
  }
41557
41774
  } catch {
41558
41775
  }
41559
- const parent = path65.dirname(dir2);
41776
+ const parent = path66.dirname(dir2);
41560
41777
  if (parent === dir2) break;
41561
41778
  dir2 = parent;
41562
41779
  }
@@ -41809,13 +42026,13 @@ function extractManagedConfig(body) {
41809
42026
  }
41810
42027
  function sweepStaleTmp(target) {
41811
42028
  try {
41812
- const dir2 = path65.dirname(target);
41813
- const prefix = `${path65.basename(target)}.`;
41814
- for (const name of fs63.readdirSync(dir2)) {
42029
+ const dir2 = path66.dirname(target);
42030
+ const prefix = `${path66.basename(target)}.`;
42031
+ for (const name of fs64.readdirSync(dir2)) {
41815
42032
  if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
41816
- const full = path65.join(dir2, name);
42033
+ const full = path66.join(dir2, name);
41817
42034
  try {
41818
- if (Date.now() - fs63.statSync(full).mtimeMs > 5 * 60 * 1e3) fs63.unlinkSync(full);
42035
+ if (Date.now() - fs64.statSync(full).mtimeMs > 5 * 60 * 1e3) fs64.unlinkSync(full);
41819
42036
  } catch {
41820
42037
  }
41821
42038
  }
@@ -41918,7 +42135,7 @@ async function pushBlastSnapshot(creds) {
41918
42135
  }
41919
42136
  async function pushPostureSnapshot(creds) {
41920
42137
  try {
41921
- const home = os53.homedir();
42138
+ const home = os54.homedir();
41922
42139
  const result = await runPosture({ home, cwd: home });
41923
42140
  await shipPosture(result, creds);
41924
42141
  } catch {
@@ -42092,7 +42309,7 @@ async function runCloudSync() {
42092
42309
  }
42093
42310
  function getCloudSyncStatus() {
42094
42311
  try {
42095
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
42312
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
42096
42313
  if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
42097
42314
  return {
42098
42315
  cached: true,
@@ -42109,7 +42326,7 @@ function getCloudSyncStatus() {
42109
42326
  }
42110
42327
  function getCloudRules() {
42111
42328
  try {
42112
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
42329
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
42113
42330
  return Array.isArray(raw.rules) ? raw.rules : null;
42114
42331
  } catch {
42115
42332
  return null;
@@ -42179,12 +42396,12 @@ var init_sync = __esm({
42179
42396
  loop: "loops",
42180
42397
  "long-output-redacted": "longOutputRedactions"
42181
42398
  };
42182
- rulesCacheFile = () => path65.join(os53.homedir(), ".node9", "rules-cache.json");
42183
- rulesCacheBackupFile = () => path65.join(os53.homedir(), ".node9", "rules-cache.last-good.json");
42399
+ rulesCacheFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.json");
42400
+ rulesCacheBackupFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.last-good.json");
42184
42401
  DEFAULT_INTERVAL_HOURS = 5;
42185
42402
  MIN_INTERVAL_SECONDS = 15;
42186
42403
  MAX_INTERVAL_SECONDS = 24 * 60 * 60;
42187
- syncHealthFile = () => path65.join(os53.homedir(), ".node9", "sync-health.json");
42404
+ syncHealthFile = () => path66.join(os54.homedir(), ".node9", "sync-health.json");
42188
42405
  STALE_MIN_MS = 3 * 60 * 60 * 1e3;
42189
42406
  STALE_MAX_MS = 24 * 60 * 60 * 1e3;
42190
42407
  STALE_FACTOR = 3;
@@ -42209,9 +42426,9 @@ __export(audit_shipper_exports, {
42209
42426
  startAuditShipper: () => startAuditShipper,
42210
42427
  writeWatermark: () => writeWatermark
42211
42428
  });
42212
- import fs64 from "fs";
42213
- import path66 from "path";
42214
- import os54 from "os";
42429
+ import fs65 from "fs";
42430
+ import path67 from "path";
42431
+ import os55 from "os";
42215
42432
  import crypto8 from "crypto";
42216
42433
  function exposureOf(v) {
42217
42434
  if (!v || typeof v !== "object") return null;
@@ -42221,21 +42438,21 @@ function exposureOf(v) {
42221
42438
  return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
42222
42439
  }
42223
42440
  function fileSignature(filePath) {
42224
- const fd = fs64.openSync(filePath, "r");
42441
+ const fd = fs65.openSync(filePath, "r");
42225
42442
  try {
42226
42443
  const buf = Buffer.alloc(512);
42227
- const read3 = fs64.readSync(fd, buf, 0, 512, 0);
42444
+ const read3 = fs65.readSync(fd, buf, 0, 512, 0);
42228
42445
  const slice = buf.subarray(0, read3);
42229
42446
  const nl = slice.indexOf(10);
42230
42447
  const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
42231
42448
  return crypto8.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
42232
42449
  } finally {
42233
- fs64.closeSync(fd);
42450
+ fs65.closeSync(fd);
42234
42451
  }
42235
42452
  }
42236
42453
  function readWatermark(watermarkPath) {
42237
42454
  try {
42238
- const raw = JSON.parse(fs64.readFileSync(watermarkPath, "utf-8"));
42455
+ const raw = JSON.parse(fs65.readFileSync(watermarkPath, "utf-8"));
42239
42456
  if (typeof raw.fileSig === "string" && typeof raw.offset === "number" && raw.offset >= 0)
42240
42457
  return raw;
42241
42458
  } catch {
@@ -42244,8 +42461,8 @@ function readWatermark(watermarkPath) {
42244
42461
  }
42245
42462
  function writeWatermark(watermarkPath, wm) {
42246
42463
  const tmp = `${watermarkPath}.tmp`;
42247
- fs64.writeFileSync(tmp, JSON.stringify(wm));
42248
- fs64.renameSync(tmp, watermarkPath);
42464
+ fs65.writeFileSync(tmp, JSON.stringify(wm));
42465
+ fs65.renameSync(tmp, watermarkPath);
42249
42466
  }
42250
42467
  function buildWireRows(chunk2) {
42251
42468
  const lastNl = chunk2.lastIndexOf(10);
@@ -42332,11 +42549,11 @@ async function shipOnce(deps = {}) {
42332
42549
  if (!creds?.apiKey) return { status: "no-creds", shipped: 0 };
42333
42550
  const endpoint = buildBatchEndpoint(creds.apiUrl);
42334
42551
  if (!endpoint) return { status: "no-creds", shipped: 0 };
42335
- if (!fs64.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
42552
+ if (!fs65.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
42336
42553
  let shipped = 0;
42337
42554
  try {
42338
42555
  for (let chunkN = 0; chunkN < MAX_CHUNKS_PER_TICK; chunkN++) {
42339
- const size = fs64.statSync(auditLogPath).size;
42556
+ const size = fs65.statSync(auditLogPath).size;
42340
42557
  if (size === 0) break;
42341
42558
  const sig = fileSignature(auditLogPath);
42342
42559
  const wm = readWatermark(watermarkPath);
@@ -42344,12 +42561,12 @@ async function shipOnce(deps = {}) {
42344
42561
  if (offset >= size) break;
42345
42562
  const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
42346
42563
  const buf = Buffer.alloc(toRead);
42347
- const fd = fs64.openSync(auditLogPath, "r");
42564
+ const fd = fs65.openSync(auditLogPath, "r");
42348
42565
  let read3;
42349
42566
  try {
42350
- read3 = fs64.readSync(fd, buf, 0, toRead, offset);
42567
+ read3 = fs65.readSync(fd, buf, 0, toRead, offset);
42351
42568
  } finally {
42352
- fs64.closeSync(fd);
42569
+ fs65.closeSync(fd);
42353
42570
  }
42354
42571
  const { rows, consumed } = buildWireRows(buf.subarray(0, read3));
42355
42572
  if (consumed === 0) break;
@@ -42396,8 +42613,8 @@ async function shipOnce(deps = {}) {
42396
42613
  }
42397
42614
  function shipLagBytes(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK) {
42398
42615
  try {
42399
- if (!fs64.existsSync(auditLogPath)) return 0;
42400
- const size = fs64.statSync(auditLogPath).size;
42616
+ if (!fs65.existsSync(auditLogPath)) return 0;
42617
+ const size = fs65.statSync(auditLogPath).size;
42401
42618
  const wm = readWatermark(watermarkPath);
42402
42619
  if (!wm) return size;
42403
42620
  if (wm.fileSig !== fileSignature(auditLogPath)) return size;
@@ -42422,20 +42639,20 @@ function startAuditShipper() {
42422
42639
  }
42423
42640
  function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK, maxBytes = MAX_CHUNK_BYTES) {
42424
42641
  try {
42425
- const fd = fs64.openSync(auditLogPath, "r");
42642
+ const fd = fs65.openSync(auditLogPath, "r");
42426
42643
  let chunk2;
42427
42644
  let truncated;
42428
42645
  try {
42429
- const size = fs64.fstatSync(fd).size;
42646
+ const size = fs65.fstatSync(fd).size;
42430
42647
  const wm = readWatermark(watermarkPath);
42431
42648
  const offset = wm && wm.fileSig === fileSignature(auditLogPath) && wm.offset <= size ? wm.offset : 0;
42432
42649
  const length = Math.min(size - offset, maxBytes);
42433
42650
  const buffer = Buffer.alloc(length);
42434
- const read3 = fs64.readSync(fd, buffer, 0, length, offset);
42651
+ const read3 = fs65.readSync(fd, buffer, 0, length, offset);
42435
42652
  chunk2 = buffer.subarray(0, read3);
42436
42653
  truncated = size - offset > length;
42437
42654
  } finally {
42438
- fs64.closeSync(fd);
42655
+ fs65.closeSync(fd);
42439
42656
  }
42440
42657
  const { rows } = buildWireRows(chunk2);
42441
42658
  const oldest = rows.reduce((old, row) => {
@@ -42472,7 +42689,7 @@ var init_audit_shipper = __esm({
42472
42689
  "policy",
42473
42690
  "abandoned"
42474
42691
  ]);
42475
- AUDIT_SHIP_WATERMARK = path66.join(os54.homedir(), ".node9", "audit-ship.json");
42692
+ AUDIT_SHIP_WATERMARK = path67.join(os55.homedir(), ".node9", "audit-ship.json");
42476
42693
  DEFAULT_INTERVAL_MS = 2e4;
42477
42694
  MAX_BATCH = 500;
42478
42695
  MAX_CHUNK_BYTES = 4 * 1024 * 1024;
@@ -42554,12 +42771,12 @@ var init_decision = __esm({
42554
42771
  });
42555
42772
 
42556
42773
  // src/daemon/dlp-scanner.ts
42557
- import fs65 from "fs";
42558
- import path67 from "path";
42559
- import os55 from "os";
42774
+ import fs66 from "fs";
42775
+ import path68 from "path";
42776
+ import os56 from "os";
42560
42777
  function loadIndex() {
42561
42778
  try {
42562
- const raw = JSON.parse(fs65.readFileSync(INDEX_FILE, "utf-8"));
42779
+ const raw = JSON.parse(fs66.readFileSync(INDEX_FILE, "utf-8"));
42563
42780
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
42564
42781
  const r = raw;
42565
42782
  if (r.offsets && typeof r.offsets === "object") return r.offsets;
@@ -42571,13 +42788,13 @@ function loadIndex() {
42571
42788
  }
42572
42789
  function saveIndex(index) {
42573
42790
  try {
42574
- fs65.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
42791
+ fs66.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
42575
42792
  } catch {
42576
42793
  }
42577
42794
  }
42578
42795
  function appendAuditEntry(entry) {
42579
42796
  try {
42580
- fs65.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
42797
+ fs66.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
42581
42798
  } catch {
42582
42799
  }
42583
42800
  }
@@ -42591,52 +42808,52 @@ function runDlpScan() {
42591
42808
  canaryVals = [];
42592
42809
  }
42593
42810
  const canaryFindings = [];
42594
- if (!fs65.existsSync(PROJECTS_DIR2)) return;
42811
+ if (!fs66.existsSync(PROJECTS_DIR2)) return;
42595
42812
  const index = loadIndex();
42596
42813
  const seenThisPass = /* @__PURE__ */ new Set();
42597
42814
  const newFindings = [];
42598
42815
  let updated = false;
42599
42816
  let projDirs;
42600
42817
  try {
42601
- projDirs = fs65.readdirSync(PROJECTS_DIR2);
42818
+ projDirs = fs66.readdirSync(PROJECTS_DIR2);
42602
42819
  } catch {
42603
42820
  return;
42604
42821
  }
42605
42822
  for (const proj of projDirs) {
42606
- const projPath = path67.join(PROJECTS_DIR2, proj);
42823
+ const projPath = path68.join(PROJECTS_DIR2, proj);
42607
42824
  try {
42608
- if (!fs65.lstatSync(projPath).isDirectory()) continue;
42609
- const real = fs65.realpathSync(projPath);
42610
- if (!real.startsWith(PROJECTS_DIR2 + path67.sep) && real !== PROJECTS_DIR2) continue;
42825
+ if (!fs66.lstatSync(projPath).isDirectory()) continue;
42826
+ const real = fs66.realpathSync(projPath);
42827
+ if (!real.startsWith(PROJECTS_DIR2 + path68.sep) && real !== PROJECTS_DIR2) continue;
42611
42828
  } catch {
42612
42829
  continue;
42613
42830
  }
42614
42831
  let files;
42615
42832
  try {
42616
- files = fs65.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
42833
+ files = fs66.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
42617
42834
  } catch {
42618
42835
  continue;
42619
42836
  }
42620
42837
  for (const file2 of files) {
42621
- const filePath = path67.join(projPath, file2);
42838
+ const filePath = path68.join(projPath, file2);
42622
42839
  const lastOffset = index[filePath] ?? 0;
42623
42840
  let size;
42624
42841
  try {
42625
- size = fs65.statSync(filePath).size;
42842
+ size = fs66.statSync(filePath).size;
42626
42843
  } catch {
42627
42844
  continue;
42628
42845
  }
42629
42846
  if (size <= lastOffset) continue;
42630
42847
  let fd;
42631
42848
  try {
42632
- fd = fs65.openSync(filePath, "r");
42849
+ fd = fs66.openSync(filePath, "r");
42633
42850
  } catch {
42634
42851
  continue;
42635
42852
  }
42636
42853
  try {
42637
42854
  const chunkSize = size - lastOffset;
42638
42855
  const buf = Buffer.alloc(chunkSize);
42639
- fs65.readSync(fd, buf, 0, chunkSize, lastOffset);
42856
+ fs66.readSync(fd, buf, 0, chunkSize, lastOffset);
42640
42857
  const chunk2 = buf.toString("utf-8");
42641
42858
  for (const line of chunk2.split("\n")) {
42642
42859
  if (!line.trim()) continue;
@@ -42657,7 +42874,7 @@ function runDlpScan() {
42657
42874
  const match = scanText(text);
42658
42875
  const canary = canaryVals.length > 0 ? matchCanary(text, canaryVals) : null;
42659
42876
  if (!match && !canary) continue;
42660
- const projLabel = decodeURIComponent(proj).replace(os55.homedir(), "~").slice(0, 40);
42877
+ const projLabel = decodeURIComponent(proj).replace(os56.homedir(), "~").slice(0, 40);
42661
42878
  const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
42662
42879
  if (canary) {
42663
42880
  const rec = canaryById.get(canary.id);
@@ -42707,7 +42924,7 @@ function runDlpScan() {
42707
42924
  updated = true;
42708
42925
  } finally {
42709
42926
  try {
42710
- fs65.closeSync(fd);
42927
+ fs66.closeSync(fd);
42711
42928
  } catch {
42712
42929
  }
42713
42930
  }
@@ -42766,15 +42983,15 @@ var init_dlp_scanner = __esm({
42766
42983
  init_registry2();
42767
42984
  init_native();
42768
42985
  init_state3();
42769
- INDEX_FILE = path67.join(os55.homedir(), ".node9", "dlp-index.json");
42770
- PROJECTS_DIR2 = path67.join(os55.homedir(), ".claude", "projects");
42986
+ INDEX_FILE = path68.join(os56.homedir(), ".node9", "dlp-index.json");
42987
+ PROJECTS_DIR2 = path68.join(os56.homedir(), ".claude", "projects");
42771
42988
  }
42772
42989
  });
42773
42990
 
42774
42991
  // src/daemon/mcp-reconciler.ts
42775
- import fs66 from "fs";
42776
- import path68 from "path";
42777
- import os56 from "os";
42992
+ import fs67 from "fs";
42993
+ import path69 from "path";
42994
+ import os57 from "os";
42778
42995
  import crypto9 from "crypto";
42779
42996
  function idKey(e) {
42780
42997
  const cmdHash = crypto9.createHash("sha256").update(`${e.command} ${e.args.join(" ")}`).digest("hex").slice(0, 16);
@@ -42782,7 +42999,7 @@ function idKey(e) {
42782
42999
  }
42783
43000
  function loadBaseline() {
42784
43001
  try {
42785
- const raw = JSON.parse(fs66.readFileSync(BASELINE_FILE2, "utf-8"));
43002
+ const raw = JSON.parse(fs67.readFileSync(BASELINE_FILE2, "utf-8"));
42786
43003
  return new Set(Array.isArray(raw) ? raw : []);
42787
43004
  } catch {
42788
43005
  return /* @__PURE__ */ new Set();
@@ -42790,7 +43007,7 @@ function loadBaseline() {
42790
43007
  }
42791
43008
  function saveBaseline(keys) {
42792
43009
  try {
42793
- fs66.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
43010
+ fs67.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
42794
43011
  mode: 384
42795
43012
  });
42796
43013
  } catch {
@@ -42978,7 +43195,7 @@ var init_mcp_reconciler = __esm({
42978
43195
  init_cloud();
42979
43196
  init_audit();
42980
43197
  init_mcp_pin();
42981
- BASELINE_FILE2 = path68.join(os56.homedir(), ".node9", "mcp-baseline.json");
43198
+ BASELINE_FILE2 = path69.join(os57.homedir(), ".node9", "mcp-baseline.json");
42982
43199
  BASELINE_CAP = 500;
42983
43200
  DEFAULT_INTERVAL_MIN = 60;
42984
43201
  DEFAULT_STALE_DAYS = 7;
@@ -43052,114 +43269,6 @@ var init_hook_heal = __esm({
43052
43269
  }
43053
43270
  });
43054
43271
 
43055
- // src/daemon/startup-log.ts
43056
- import fs67 from "fs";
43057
- import path69 from "path";
43058
- import os57 from "os";
43059
- function capStartupLog(file2) {
43060
- try {
43061
- if (fs67.statSync(file2).size > MAX_STARTUP_LOG_BYTES) fs67.truncateSync(file2);
43062
- } catch {
43063
- }
43064
- }
43065
- function openStartupLogFd() {
43066
- try {
43067
- const file2 = DAEMON_STARTUP_LOG();
43068
- const dir2 = path69.dirname(file2);
43069
- if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
43070
- capStartupLog(file2);
43071
- return fs67.openSync(file2, "a");
43072
- } catch {
43073
- return void 0;
43074
- }
43075
- }
43076
- function recordStartupState(outcome, kind, detail) {
43077
- try {
43078
- if (outcome === "starting") {
43079
- const prev = readStartupState();
43080
- if (prev?.outcome === "starting") {
43081
- const prevAt = new Date(prev.at).getTime();
43082
- const age = Math.abs(Date.now() - prevAt);
43083
- if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
43084
- }
43085
- }
43086
- const file2 = DAEMON_STARTUP_STATE();
43087
- const dir2 = path69.dirname(file2);
43088
- if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
43089
- const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
43090
- if (kind) state.kind = kind;
43091
- if (detail) state.detail = detail.slice(0, MAX_DETAIL);
43092
- const tmp = `${file2}.${process.pid}.tmp`;
43093
- try {
43094
- fs67.writeFileSync(tmp, JSON.stringify(state), "utf-8");
43095
- fs67.renameSync(tmp, file2);
43096
- } catch (err2) {
43097
- try {
43098
- fs67.unlinkSync(tmp);
43099
- } catch {
43100
- }
43101
- throw err2;
43102
- }
43103
- } catch {
43104
- }
43105
- }
43106
- function readStartupState() {
43107
- try {
43108
- const raw = fs67.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
43109
- const s = JSON.parse(raw);
43110
- if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
43111
- return s;
43112
- } catch {
43113
- return null;
43114
- }
43115
- }
43116
- function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
43117
- const s = readStartupState();
43118
- if (!s) return null;
43119
- const at = new Date(s.at);
43120
- if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
43121
- switch (s.outcome) {
43122
- case "ok":
43123
- case "ok-elsewhere":
43124
- return null;
43125
- case "starting":
43126
- if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
43127
- return {
43128
- kind: "did-not-start",
43129
- detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
43130
- at,
43131
- // `at` is the FIRST attempt of the streak, not the most recent one.
43132
- label: "start attempts failing since"
43133
- };
43134
- case "failed":
43135
- return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
43136
- default:
43137
- return null;
43138
- }
43139
- }
43140
- function logDaemonStartup(kind, detail) {
43141
- try {
43142
- const file2 = DAEMON_STARTUP_LOG();
43143
- const dir2 = path69.dirname(file2);
43144
- if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
43145
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-startup:${kind}${detail ? ` ${detail}` : ""}
43146
- `;
43147
- fs67.appendFileSync(file2, line, "utf-8");
43148
- } catch {
43149
- }
43150
- }
43151
- var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
43152
- var init_startup_log = __esm({
43153
- "src/daemon/startup-log.ts"() {
43154
- "use strict";
43155
- DAEMON_STARTUP_LOG = () => path69.join(os57.homedir(), ".node9", "daemon-startup.log");
43156
- MAX_STARTUP_LOG_BYTES = 256 * 1024;
43157
- DAEMON_STARTUP_STATE = () => path69.join(os57.homedir(), ".node9", "daemon-startup-state.json");
43158
- MAX_DETAIL = 200;
43159
- STARTING_GRACE_MS = 90 * 1e3;
43160
- }
43161
- });
43162
-
43163
43272
  // src/supply-chain/zip.ts
43164
43273
  import fs68 from "fs";
43165
43274
  import zlib from "zlib";
@@ -77491,7 +77600,7 @@ function buildReportJson(input) {
77491
77600
  var BLOCK_REASON_LABELS = {
77492
77601
  timeout: "Approval timeout",
77493
77602
  "smart-rule-block": "Smart rule",
77494
- "observe-mode-dlp-would-block": "DLP (observe)",
77603
+ "observe-mode-dlp-would-block": "DLP (audit)",
77495
77604
  "persistent-deny": "Persistent deny",
77496
77605
  "local-decision": "User denied",
77497
77606
  "dlp-block": "DLP block",
@@ -77531,10 +77640,10 @@ function fmtCost2(usd) {
77531
77640
  return "$" + usd.toFixed(2);
77532
77641
  }
77533
77642
  function registerReportCommand(program2) {
77534
- program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
77643
+ program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | 90d | month", "30d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").option("--json", "Emit machine-readable JSON to stdout (suppresses renderer)").action((options) => {
77535
77644
  const period = ["today", "7d", "30d", "90d", "month"].includes(
77536
77645
  options.period
77537
- ) ? options.period : "7d";
77646
+ ) ? options.period : "30d";
77538
77647
  const excludeTests = options.tests === false;
77539
77648
  const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
77540
77649
  excludeTests
@@ -77732,7 +77841,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
77732
77841
  );
77733
77842
  summaryRow(
77734
77843
  observeDlp > 0 ? chalk20.blue("\u{1F441}") : chalk20.dim("\u{1F441}"),
77735
- "DLP (observe)",
77844
+ "DLP (audit)",
77736
77845
  observeDlp,
77737
77846
  observeDlp > 0 ? "would-block in strict mode" : void 0,
77738
77847
  observeDlp > 0 ? (s) => chalk20.blue(s) : (s) => chalk20.dim(s)
@@ -79240,6 +79349,235 @@ import fs92 from "fs";
79240
79349
  import os78 from "os";
79241
79350
  import path91 from "path";
79242
79351
  import { spawnSync as spawnSync5 } from "child_process";
79352
+
79353
+ // src/cli/commands/egress.ts
79354
+ init_config();
79355
+ init_keyed_guard();
79356
+ init_dist();
79357
+ init_egress_config();
79358
+ init_egress_config();
79359
+ import chalk27 from "chalk";
79360
+ function guard(fn) {
79361
+ try {
79362
+ fn();
79363
+ return true;
79364
+ } catch (err2) {
79365
+ console.error(chalk27.red(`
79366
+ \u2717 ${err2.message}
79367
+ `));
79368
+ process.exitCode = 1;
79369
+ return false;
79370
+ }
79371
+ }
79372
+ function mutate(action, change) {
79373
+ if (!cliGuardPolicyWrite(action)) return false;
79374
+ return guard(() => setEgress(change));
79375
+ }
79376
+ function addHost(list2, host2) {
79377
+ if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
79378
+ return guard(() => addEgressHost(list2, host2));
79379
+ }
79380
+ function exempt2(address) {
79381
+ if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
79382
+ return guard(() => addSsrfExemption(address));
79383
+ }
79384
+ var INTERNAL_FIELDS = {
79385
+ allowed: { ssrfStrict: false, allowPrivate: true },
79386
+ listed: { ssrfStrict: false, allowPrivate: false },
79387
+ blocked: { ssrfStrict: true, allowPrivate: false }
79388
+ };
79389
+ var INTERNAL_SAID = {
79390
+ allowed: "reachable without listing them",
79391
+ listed: "reachable only if they are on your allowlist",
79392
+ blocked: "blocked"
79393
+ };
79394
+ function describeInternalState(state) {
79395
+ return INTERNAL_SAID[state];
79396
+ }
79397
+ function readInternalState(e) {
79398
+ if (e.ssrfStrict === true) return "blocked";
79399
+ return e.allowPrivate === false ? "listed" : "allowed";
79400
+ }
79401
+ function setInternal(value) {
79402
+ const state = value.trim().toLowerCase();
79403
+ if (!(state in INTERNAL_FIELDS)) {
79404
+ console.error(
79405
+ chalk27.red(`
79406
+ \u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk27.gray(
79407
+ "\n allowed loopback, 10/172.16/192.168 and CGNAT are reachable (default)\n listed they are reachable only if you allowlist them\n blocked they are blocked at the floor\n"
79408
+ )
79409
+ );
79410
+ process.exitCode = 1;
79411
+ return;
79412
+ }
79413
+ if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
79414
+ _resetConfigCache();
79415
+ const effective = readInternalState(getConfig().policy.egress);
79416
+ if (effective !== state) {
79417
+ console.log(
79418
+ chalk27.yellow(
79419
+ `
79420
+ \u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
79421
+ Change it in the dashboard, Enforcement \u2192 Network.
79422
+ `
79423
+ )
79424
+ );
79425
+ return;
79426
+ }
79427
+ const line = `
79428
+ \u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
79429
+ `;
79430
+ console.log(state === "allowed" ? chalk27.yellow(line) : chalk27.green(line));
79431
+ }
79432
+ function showFloor(e, ssrfStrictSource) {
79433
+ console.log(
79434
+ chalk27.gray("\n Protected addresses") + chalk27.gray(" \u2014 in shell commands and declared URLs")
79435
+ );
79436
+ console.log(
79437
+ chalk27.gray(
79438
+ " always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
79439
+ )
79440
+ );
79441
+ const state = readInternalState(e);
79442
+ const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
79443
+ const STATE_LABEL = {
79444
+ allowed: "allowed",
79445
+ listed: "allowlist only",
79446
+ blocked: "blocked"
79447
+ };
79448
+ console.log(
79449
+ ` Internal addresses: ${state === "blocked" ? chalk27.green(STATE_LABEL[state]) : chalk27.yellow(STATE_LABEL[state])}` + chalk27.gray(` loopback, 10/172.16/192.168 and CGNAT are ${INTERNAL_SAID[state]}`)
79450
+ );
79451
+ console.log(chalk27.gray(` set by: ${by}`));
79452
+ const exemptions = e.ssrfAllow ?? [];
79453
+ console.log(chalk27.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
79454
+ console.log(
79455
+ chalk27.gray(
79456
+ " Covered: shell commands, and tools that declare a URL (WebFetch, an MCP\n fetch tool, browser navigate).\n Not covered: an interpreter one-liner (node -e, python3 -c) hides its\n destination inside a program and does not reach this gate.\n CGNAT (100.64/10) is NOT in the always-blocked set: it is reachable\n until Internal addresses is on, and an exemption can release it."
79457
+ )
79458
+ );
79459
+ }
79460
+ function showStatus() {
79461
+ const cfg = getConfig();
79462
+ const e = cfg.policy.egress;
79463
+ const state = !e.enabled ? chalk27.red("OFF \u2014 your agent can reach any host, except the protected ones below") : e.mode === "block" ? chalk27.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk27.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
79464
+ console.log(chalk27.cyan.bold("\n\u{1F310} Egress control"));
79465
+ console.log(" State: " + state);
79466
+ if (cfg.policySource === "workspace") {
79467
+ console.log(
79468
+ chalk27.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
79469
+ );
79470
+ }
79471
+ console.log(
79472
+ chalk27.gray(
79473
+ ` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
79474
+ )
79475
+ );
79476
+ showFloor(e, cfg.ssrfStrictSource);
79477
+ if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
79478
+ if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
79479
+ if (!e.enabled) {
79480
+ console.log(chalk27.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
79481
+ console.log(chalk27.gray(" node9 egress lock (hard-block unknown hosts)"));
79482
+ }
79483
+ console.log("");
79484
+ }
79485
+ function registerEgressCommand(program2) {
79486
+ const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
79487
+ egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
79488
+ if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
79489
+ console.log(chalk27.green("\n\u2713 Egress is now watched (review mode)."));
79490
+ console.log(
79491
+ chalk27.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
79492
+ );
79493
+ console.log(
79494
+ chalk27.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
79495
+ );
79496
+ });
79497
+ egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
79498
+ if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
79499
+ console.log(chalk27.green("\n\u2713 Egress is now locked (block mode)."));
79500
+ console.log(chalk27.gray(" Routine hosts are still allowed; unknown hosts are denied."));
79501
+ console.log(chalk27.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
79502
+ });
79503
+ egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
79504
+ if (!addHost("allow", host2)) return;
79505
+ console.log(chalk27.green(`
79506
+ \u2713 Allowed egress to ${host2}.
79507
+ `));
79508
+ });
79509
+ egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
79510
+ if (!addHost("deny", host2)) return;
79511
+ console.log(chalk27.green(`
79512
+ \u2713 Denied egress to ${host2}.
79513
+ `));
79514
+ });
79515
+ egress.command("off").description("Turn egress control off").action(() => {
79516
+ if (!mutate("egress off", { enabled: false })) return;
79517
+ console.log(
79518
+ chalk27.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
79519
+ );
79520
+ });
79521
+ egress.command("internal <allowed|listed|blocked>").description(
79522
+ "How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
79523
+ ).action((value) => setInternal(value));
79524
+ egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
79525
+ const v = value.trim().toLowerCase();
79526
+ if (v !== "on" && v !== "off") {
79527
+ console.error(chalk27.red(`
79528
+ \u2717 Expected "on" or "off", got "${value}".
79529
+ `));
79530
+ process.exitCode = 1;
79531
+ return;
79532
+ }
79533
+ const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
79534
+ console.log(chalk27.gray(`
79535
+ (\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
79536
+ setInternal(state);
79537
+ });
79538
+ egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
79539
+ const a = normalizeEgressHost(address);
79540
+ const slash = a.indexOf("/");
79541
+ const base2 = slash === -1 ? a : a.slice(0, slash);
79542
+ const prefixText = slash === -1 ? null : a.slice(slash + 1);
79543
+ if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
79544
+ console.error(
79545
+ chalk27.red(`
79546
+ \u2717 "${address}" is not an address or a range.`) + chalk27.gray(
79547
+ "\n Exemptions are matched as an address (10.0.0.5) or a CIDR\n range (100.64.0.0/10), never a name.\n"
79548
+ )
79549
+ );
79550
+ process.exitCode = 1;
79551
+ return;
79552
+ }
79553
+ const m = classifySsrf(base2);
79554
+ if (m && !m.overridable) {
79555
+ console.error(
79556
+ chalk27.red(`
79557
+ \u2717 ${a} cannot be exempted.`) + chalk27.gray(
79558
+ `
79559
+ ${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
79560
+ `
79561
+ )
79562
+ );
79563
+ process.exitCode = 1;
79564
+ return;
79565
+ }
79566
+ if (!exempt2(a)) return;
79567
+ console.log(chalk27.green(`
79568
+ \u2713 ${a} is exempt from the floor.`));
79569
+ if (!m)
79570
+ console.log(
79571
+ chalk27.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
79572
+ );
79573
+ else console.log("");
79574
+ });
79575
+ egress.command("status").description("Show the current egress state").action(showStatus);
79576
+ egress.action(showStatus);
79577
+ }
79578
+
79579
+ // src/mcp-server/index.ts
79580
+ init_agent_wiring();
79243
79581
  function ok(id, result) {
79244
79582
  return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
79245
79583
  }
@@ -79373,7 +79711,7 @@ var TOOLS = [
79373
79711
  },
79374
79712
  {
79375
79713
  name: "node9_scan",
79376
- description: "Scan all AI agent history (Claude + Gemini) and report what node9 would have blocked or flagged. Shows blocked operations, reviewed commands, credential leaks, and agent spend. Use this to audit past activity and find security gaps before they become incidents.",
79714
+ description: "Scan the history of all supported coding agents (Claude Code, Gemini, Codex, Copilot, Antigravity) and report what node9 would have blocked or flagged. Shows blocked operations, reviewed commands, credential leaks, and agent spend. Use this to audit past activity and find security gaps before they become incidents.",
79377
79715
  inputSchema: {
79378
79716
  type: "object",
79379
79717
  properties: {
@@ -79393,8 +79731,8 @@ var TOOLS = [
79393
79731
  properties: {
79394
79732
  period: {
79395
79733
  type: "string",
79396
- enum: ["today", "7d", "30d", "month"],
79397
- description: "Time period for the report (default: 7d)."
79734
+ enum: ["today", "7d", "30d", "90d", "month"],
79735
+ description: "Time period for the report (default: 30d)."
79398
79736
  },
79399
79737
  no_tests: {
79400
79738
  type: "boolean",
@@ -79528,7 +79866,7 @@ function handleStatus() {
79528
79866
  const wsGoverned = config.policySource === "workspace";
79529
79867
  const activeShields = wsGoverned ? config.policy.appliedShields ?? [] : readActiveShields();
79530
79868
  const lines = [];
79531
- lines.push(`Mode: ${settings.mode}`);
79869
+ lines.push(`Mode: ${levelOf(settings.mode)}`);
79532
79870
  if (wsGoverned) {
79533
79871
  lines.push(
79534
79872
  "Policy source: workspace config (app.node9.ai) \u2014 local config files are ignored for policy"
@@ -79544,6 +79882,12 @@ function handleStatus() {
79544
79882
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
79545
79883
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
79546
79884
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
79885
+ lines.push("Agent hooks:");
79886
+ for (const a of getAgentWiring()) {
79887
+ const hooked = a.hooks.some((h) => h.wired);
79888
+ const state = !a.installed ? "not installed" : hooked ? "wired" : a.isProtected ? "protected through MCP" : "not wired";
79889
+ lines.push(` ${a.label}: ${state}`);
79890
+ }
79547
79891
  const projectConfig = path91.join(process.cwd(), "node9.config.json");
79548
79892
  const globalConfig = path91.join(os78.homedir(), ".node9", "config.json");
79549
79893
  const presentLabel = wsGoverned ? "present \u2014 ignored (workspace config governs)" : "present";
@@ -79636,6 +79980,11 @@ function handleShieldDisable(args) {
79636
79980
  if (disabledApp) recordUserDecision(disabledApp.slug, "off");
79637
79981
  return `Shield "${name}" disabled.`;
79638
79982
  }
79983
+ function internalAddressLine(e) {
79984
+ const configured = readInternalState(e);
79985
+ const state = !e.enabled && configured === "listed" ? "allowed" : configured;
79986
+ return `Internal addresses: ${state} \u2014 loopback and the private ranges are ${describeInternalState(state)}.`;
79987
+ }
79639
79988
  function handleEgressStatus() {
79640
79989
  const cfgEgress = getConfig();
79641
79990
  const e = cfgEgress.policy.egress;
@@ -79657,7 +80006,7 @@ function handleEgressStatus() {
79657
80006
  // actually covers is measured in egress.integration.test.ts.
79658
80007
  "Protected addresses: cloud metadata, link-local and multicast are blocked before any of the above is consulted, in shell commands AND in tools that declare a URL (WebFetch, an MCP fetch tool, browser navigate). No setting releases them, though `node9 pause` suspends all enforcement.",
79659
80008
  "NOT covered by that: an interpreter one-liner (node -e, python3 -c) carries its destination inside a program and does not reach this gate. CGNAT (100.64/10) is NOT in the always-blocked set \u2014 it is reachable until the strict tier is on, and an exemption can release it.",
79660
- `Internal addresses: ${e.ssrfStrict ? "on" : "off"} \u2014 loopback and the private ranges are ${e.ssrfStrict ? "blocked too" : "reachable"}.`,
80009
+ internalAddressLine(e),
79661
80010
  `Floor exemptions: ${e.ssrfAllow?.length ? e.ssrfAllow.join(", ") : "(none)"}`
79662
80011
  ];
79663
80012
  return lines.join("\n");
@@ -79980,7 +80329,7 @@ function registerMcpServerCommand(program2) {
79980
80329
  init_trusted_hosts();
79981
80330
  init_keyed_guard();
79982
80331
  init_trusted_recipients();
79983
- import chalk27 from "chalk";
80332
+ import chalk28 from "chalk";
79984
80333
  function isValidHost(host2) {
79985
80334
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host2);
79986
80335
  }
@@ -79991,17 +80340,17 @@ function registerTrustCommand(program2) {
79991
80340
  const normalized = normalizeHost2(host2.trim());
79992
80341
  if (!isValidHost(normalized)) {
79993
80342
  console.error(
79994
- chalk27.red(`
80343
+ chalk28.red(`
79995
80344
  \u274C Invalid host: "${host2}"
79996
- `) + chalk27.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
80345
+ `) + chalk28.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
79997
80346
  );
79998
80347
  process.exit(1);
79999
80348
  }
80000
80349
  addTrustedHost(normalized);
80001
- console.log(chalk27.green(`
80350
+ console.log(chalk28.green(`
80002
80351
  \u2705 ${normalized} added to trusted hosts.`));
80003
80352
  console.log(
80004
- chalk27.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
80353
+ chalk28.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
80005
80354
  );
80006
80355
  });
80007
80356
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host2) => {
@@ -80009,27 +80358,27 @@ function registerTrustCommand(program2) {
80009
80358
  const normalized = normalizeHost2(host2.trim());
80010
80359
  const removed = removeTrustedHost(normalized);
80011
80360
  if (!removed) {
80012
- console.error(chalk27.yellow(`
80361
+ console.error(chalk28.yellow(`
80013
80362
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
80014
80363
  `));
80015
80364
  process.exit(1);
80016
80365
  }
80017
- console.log(chalk27.green(`
80366
+ console.log(chalk28.green(`
80018
80367
  \u2705 ${normalized} removed from trusted hosts.
80019
80368
  `));
80020
80369
  });
80021
80370
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
80022
80371
  const hosts = readTrustedHosts();
80023
80372
  if (hosts.length === 0) {
80024
- console.log(chalk27.gray("\n No trusted hosts configured.\n"));
80025
- console.log(` Add one: ${chalk27.cyan("node9 trust add api.mycompany.com")}
80373
+ console.log(chalk28.gray("\n No trusted hosts configured.\n"));
80374
+ console.log(` Add one: ${chalk28.cyan("node9 trust add api.mycompany.com")}
80026
80375
  `);
80027
80376
  return;
80028
80377
  }
80029
- console.log(chalk27.bold("\n\u{1F513} Trusted Hosts\n"));
80378
+ console.log(chalk28.bold("\n\u{1F513} Trusted Hosts\n"));
80030
80379
  for (const entry of hosts) {
80031
80380
  const date = new Date(entry.addedAt).toLocaleDateString();
80032
- console.log(` ${chalk27.cyan(entry.host.padEnd(40))} ${chalk27.gray(`added ${date}`)}`);
80381
+ console.log(` ${chalk28.cyan(entry.host.padEnd(40))} ${chalk28.gray(`added ${date}`)}`);
80033
80382
  }
80034
80383
  console.log("");
80035
80384
  });
@@ -80041,41 +80390,41 @@ function registerTrustCommand(program2) {
80041
80390
  const stored = addTrustedRecipient(value, "user");
80042
80391
  if (!stored) {
80043
80392
  console.error(
80044
- chalk27.red(`
80393
+ chalk28.red(`
80045
80394
  \u274C Not a destination: "${value}"
80046
- `) + chalk27.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
80395
+ `) + chalk28.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
80047
80396
  );
80048
80397
  process.exit(1);
80049
80398
  }
80050
- console.log(chalk27.green(`
80399
+ console.log(chalk28.green(`
80051
80400
  \u2705 ${stored} added to trusted recipients.
80052
80401
  `));
80053
80402
  });
80054
80403
  rec.command("remove <value>").description("Remove a trusted recipient").action((value) => {
80055
80404
  if (!cliGuardPolicyWrite(`trust recipient remove ${value}`)) return;
80056
80405
  if (!removeTrustedRecipient(value)) {
80057
- console.error(chalk27.yellow(`
80406
+ console.error(chalk28.yellow(`
80058
80407
  \u26A0\uFE0F "${value}" is not in the trusted recipients list.
80059
80408
  `));
80060
80409
  process.exit(1);
80061
80410
  }
80062
- console.log(chalk27.green(`
80411
+ console.log(chalk28.green(`
80063
80412
  \u2705 ${value} removed from trusted recipients.
80064
80413
  `));
80065
80414
  });
80066
80415
  rec.command("list").description("Show all trusted recipients").action(() => {
80067
80416
  const entries = readTrustedRecipients();
80068
80417
  if (entries.length === 0) {
80069
- console.log(chalk27.gray("\n No trusted recipients.\n"));
80070
- console.log(` Add one: ${chalk27.cyan("node9 trust recipient add partner@example.com")}
80418
+ console.log(chalk28.gray("\n No trusted recipients.\n"));
80419
+ console.log(` Add one: ${chalk28.cyan("node9 trust recipient add partner@example.com")}
80071
80420
  `);
80072
80421
  return;
80073
80422
  }
80074
- console.log(chalk27.bold("\n\u{1F513} Trusted Recipients\n"));
80423
+ console.log(chalk28.bold("\n\u{1F513} Trusted Recipients\n"));
80075
80424
  for (const e of entries) {
80076
80425
  const date = new Date(e.addedAt).toLocaleDateString();
80077
80426
  console.log(
80078
- ` ${chalk27.cyan(e.value.padEnd(44))} ${chalk27.gray(`added ${date} (${e.addedBy})`)}`
80427
+ ` ${chalk28.cyan(e.value.padEnd(44))} ${chalk28.gray(`added ${date} (${e.addedBy})`)}`
80079
80428
  );
80080
80429
  }
80081
80430
  console.log("");
@@ -80084,13 +80433,13 @@ function registerTrustCommand(program2) {
80084
80433
 
80085
80434
  // src/cli/commands/mcp-pin.ts
80086
80435
  init_mcp_pin();
80087
- import chalk29 from "chalk";
80436
+ import chalk30 from "chalk";
80088
80437
  import fs93 from "fs";
80089
80438
 
80090
80439
  // src/cli/commands/mcp-gateway-cmd.ts
80091
80440
  init_mcp_wrap();
80092
80441
  init_mcp_status();
80093
- import chalk28 from "chalk";
80442
+ import chalk29 from "chalk";
80094
80443
  var STATE_WORD = {
80095
80444
  gatewayed: "governed",
80096
80445
  "node9-self": "node9",
@@ -80099,10 +80448,10 @@ var STATE_WORD = {
80099
80448
  };
80100
80449
  function stateChip(state) {
80101
80450
  const w = STATE_WORD[state].padEnd(12);
80102
- if (state === "gatewayed") return chalk28.green(w);
80103
- if (state === "node9-self") return chalk28.gray(w);
80104
- if (state === "remote") return chalk28.gray(w);
80105
- return chalk28.yellow(w);
80451
+ if (state === "gatewayed") return chalk29.green(w);
80452
+ if (state === "node9-self") return chalk29.gray(w);
80453
+ if (state === "remote") return chalk29.gray(w);
80454
+ return chalk29.yellow(w);
80106
80455
  }
80107
80456
  function relativeAge(ms, now) {
80108
80457
  const s = Math.max(0, Math.round((now - ms) / 1e3));
@@ -80116,15 +80465,15 @@ function relativeAge(ms, now) {
80116
80465
  function connectionChip(e, now) {
80117
80466
  switch (e.connection) {
80118
80467
  case "connected":
80119
- return chalk28.green(
80468
+ return chalk29.green(
80120
80469
  `\u2713 connected${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`
80121
80470
  );
80122
80471
  case "stale":
80123
- return chalk28.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
80472
+ return chalk29.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
80124
80473
  case "pending-launch":
80125
- return chalk28.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
80474
+ return chalk29.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
80126
80475
  case "unlaunchable":
80127
- return chalk28.red(
80476
+ return chalk29.red(
80128
80477
  `\u2717 can't launch \u2014 ${(e.missingEnv ?? []).map((v) => `$${v}`).join(", ")} not set`
80129
80478
  );
80130
80479
  default:
@@ -80149,7 +80498,7 @@ function registerMcpGatewayCommand2(mcp) {
80149
80498
  const now = Date.now();
80150
80499
  const rows = resolveMcpStatus(void 0, process.env, now);
80151
80500
  if (rows.length === 0) {
80152
- console.error(chalk28.gray("No MCP servers found in any agent config."));
80501
+ console.error(chalk29.gray("No MCP servers found in any agent config."));
80153
80502
  return;
80154
80503
  }
80155
80504
  for (const e of rows) {
@@ -80163,20 +80512,20 @@ function registerMcpGatewayCommand2(mcp) {
80163
80512
  const pending = rows.filter((e) => e.connection === "pending-launch").length;
80164
80513
  if (ungoverned > 0) {
80165
80514
  console.error(
80166
- chalk28.yellow(
80515
+ chalk29.yellow(
80167
80516
  `
80168
- ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all") + " to govern."
80517
+ ${ungoverned} ungoverned \u2014 run ` + chalk29.bold("node9 mcp gateway --all") + " to govern."
80169
80518
  )
80170
80519
  );
80171
80520
  }
80172
80521
  if (pending > 0) {
80173
80522
  console.error(
80174
- chalk28.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
80523
+ chalk29.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
80175
80524
  );
80176
80525
  }
80177
80526
  if (unlaunchable > 0) {
80178
80527
  console.error(
80179
- chalk28.red(
80528
+ chalk29.red(
80180
80529
  `${unlaunchable} can't launch \u2014 set the missing env var(s) where the agent starts (a restart alone won't fix it).`
80181
80530
  )
80182
80531
  );
@@ -80195,27 +80544,27 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
80195
80544
  } else if (name) {
80196
80545
  const matches2 = resolve(name, opts.agent);
80197
80546
  if (matches2.length === 0) {
80198
- console.error(chalk28.red(`No MCP server named "${name}" found.`));
80547
+ console.error(chalk29.red(`No MCP server named "${name}" found.`));
80199
80548
  process.exitCode = 1;
80200
80549
  return;
80201
80550
  }
80202
80551
  if (matches2.length > 1) {
80203
80552
  console.error(
80204
- chalk28.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
80553
+ chalk29.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
80205
80554
  );
80206
80555
  process.exitCode = 1;
80207
80556
  return;
80208
80557
  }
80209
80558
  targets = matches2;
80210
80559
  } else {
80211
- console.error(chalk28.red("Pass a server name or --all. See `node9 mcp status`."));
80560
+ console.error(chalk29.red("Pass a server name or --all. See `node9 mcp status`."));
80212
80561
  process.exitCode = 1;
80213
80562
  return;
80214
80563
  }
80215
80564
  const doable = targets.filter((e) => e.state === targetState);
80216
80565
  if (doable.length === 0) {
80217
80566
  console.error(
80218
- chalk28.gray(
80567
+ chalk29.gray(
80219
80568
  opts.rewrap ? "Nothing to do \u2014 no governed servers to refresh." : "Nothing to do \u2014 already governed (or node9-self)."
80220
80569
  )
80221
80570
  );
@@ -80229,20 +80578,20 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
80229
80578
  else wrapEntry(e);
80230
80579
  agents.add(e.agentLabel);
80231
80580
  const verb = opts.rewrap ? "refreshed" : "governed";
80232
- console.error(chalk28.green(`\u2713 ${verb} ${e.name}`) + chalk28.gray(` (${e.agentLabel})`));
80581
+ console.error(chalk29.green(`\u2713 ${verb} ${e.name}`) + chalk29.gray(` (${e.agentLabel})`));
80233
80582
  } catch (err2) {
80234
80583
  failed++;
80235
80584
  const msg = err2 instanceof Error ? err2.message : String(err2);
80236
80585
  console.error(
80237
- chalk28.red(`\u2717 failed ${e.name}`) + chalk28.gray(` (${e.agentLabel}): ${msg}`)
80586
+ chalk29.red(`\u2717 failed ${e.name}`) + chalk29.gray(` (${e.agentLabel}): ${msg}`)
80238
80587
  );
80239
80588
  }
80240
80589
  }
80241
80590
  if (failed > 0) process.exitCode = 1;
80242
80591
  if (agents.size > 0) {
80243
80592
  console.error(
80244
- chalk28.bold(`
80245
- Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any server with ") + chalk28.cyan("node9 mcp ungateway <name>") + chalk28.gray(".")
80593
+ chalk29.bold(`
80594
+ Restart ${[...agents].join(", ")}`) + chalk29.gray(" to activate. Undo any server with ") + chalk29.cyan("node9 mcp ungateway <name>") + chalk29.gray(".")
80246
80595
  );
80247
80596
  }
80248
80597
  }
@@ -80250,19 +80599,19 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
80250
80599
  mcp.command("ungateway <name>").description("Restore an MCP server to its original (un-route it from node9)").option("--agent <id>", "disambiguate a name in more than one agent").action((name, opts) => {
80251
80600
  const matches2 = resolve(name, opts.agent).filter((e2) => e2.state === "gatewayed");
80252
80601
  if (matches2.length === 0) {
80253
- console.error(chalk28.red(`No governed MCP server named "${name}" found.`));
80602
+ console.error(chalk29.red(`No governed MCP server named "${name}" found.`));
80254
80603
  process.exitCode = 1;
80255
80604
  return;
80256
80605
  }
80257
80606
  if (matches2.length > 1) {
80258
- console.error(chalk28.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
80607
+ console.error(chalk29.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
80259
80608
  process.exitCode = 1;
80260
80609
  return;
80261
80610
  }
80262
80611
  const e = matches2[0];
80263
80612
  const orig = fromGateway(e.raw);
80264
80613
  if (!orig) {
80265
- console.error(chalk28.red(`Couldn't parse the original command for "${name}".`));
80614
+ console.error(chalk29.red(`Couldn't parse the original command for "${name}".`));
80266
80615
  process.exitCode = 1;
80267
80616
  return;
80268
80617
  }
@@ -80270,12 +80619,12 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
80270
80619
  writeMcpEntry(e.mcpFile, e.format, e.name, orig);
80271
80620
  } catch (err2) {
80272
80621
  const msg = err2 instanceof Error ? err2.message : String(err2);
80273
- console.error(chalk28.red(`Failed to restore "${name}": ${msg}`));
80622
+ console.error(chalk29.red(`Failed to restore "${name}": ${msg}`));
80274
80623
  process.exitCode = 1;
80275
80624
  return;
80276
80625
  }
80277
80626
  console.error(
80278
- chalk28.green(`\u2713 restored ${e.name}`) + chalk28.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
80627
+ chalk29.green(`\u2713 restored ${e.name}`) + chalk29.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
80279
80628
  );
80280
80629
  });
80281
80630
  }
@@ -80301,14 +80650,14 @@ function registerMcpPinCommand(program2) {
80301
80650
  }
80302
80651
  }
80303
80652
  if (repoCorrupt) {
80304
- console.error(chalk29.red(`
80653
+ console.error(chalk30.red(`
80305
80654
  \u274C Repo pin file at ${found.path} is corrupt or unreadable.`));
80306
80655
  process.exit(1);
80307
80656
  }
80308
80657
  if (!homeResult.ok && homeResult.reason === "corrupt") {
80309
- console.error(chalk29.red(`
80658
+ console.error(chalk30.red(`
80310
80659
  \u274C Home pin file is corrupt: ${homeResult.detail}`));
80311
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80660
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80312
80661
  process.exit(1);
80313
80662
  }
80314
80663
  const homeEntries = homeResult.ok ? homeResult.pins.servers : {};
@@ -80320,25 +80669,25 @@ function registerMcpPinCommand(program2) {
80320
80669
  merged.set(key, { entry, source: "repo" });
80321
80670
  }
80322
80671
  if (merged.size === 0) {
80323
- console.log(chalk29.gray("\nNo MCP servers are pinned yet."));
80672
+ console.log(chalk30.gray("\nNo MCP servers are pinned yet."));
80324
80673
  console.log(
80325
- chalk29.gray("Pins are created automatically when the MCP gateway first connects.\n")
80674
+ chalk30.gray("Pins are created automatically when the MCP gateway first connects.\n")
80326
80675
  );
80327
80676
  return;
80328
80677
  }
80329
- console.log(chalk29.bold("\n\u{1F512} Pinned MCP Servers\n"));
80678
+ console.log(chalk30.bold("\n\u{1F512} Pinned MCP Servers\n"));
80330
80679
  const showSource = found.source === "repo";
80331
80680
  for (const [key, { entry, source }] of merged) {
80332
- const tag = showSource ? ` ${chalk29.yellow(`[${source}]`)}` : "";
80333
- console.log(` ${chalk29.cyan(key)}${tag} ${chalk29.gray(entry.label)}`);
80334
- console.log(` Tools (${entry.toolCount}): ${chalk29.white(entry.toolNames.join(", "))}`);
80335
- console.log(` Hash: ${chalk29.gray(entry.toolsHash.slice(0, 16))}...`);
80336
- console.log(` Pinned: ${chalk29.gray(entry.pinnedAt)}`);
80681
+ const tag = showSource ? ` ${chalk30.yellow(`[${source}]`)}` : "";
80682
+ console.log(` ${chalk30.cyan(key)}${tag} ${chalk30.gray(entry.label)}`);
80683
+ console.log(` Tools (${entry.toolCount}): ${chalk30.white(entry.toolNames.join(", "))}`);
80684
+ console.log(` Hash: ${chalk30.gray(entry.toolsHash.slice(0, 16))}...`);
80685
+ console.log(` Pinned: ${chalk30.gray(entry.pinnedAt)}`);
80337
80686
  console.log("");
80338
80687
  }
80339
80688
  if (showSource) {
80340
- console.log(chalk29.gray(` [repo] entries come from ${found.path}`));
80341
- console.log(chalk29.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
80689
+ console.log(chalk30.gray(` [repo] entries come from ${found.path}`));
80690
+ console.log(chalk30.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
80342
80691
  }
80343
80692
  });
80344
80693
  pinSubCmd.command("promote <serverKey>").description(
@@ -80348,22 +80697,22 @@ function registerMcpPinCommand(program2) {
80348
80697
  const { repoPath, created } = promotePin(serverKey, process.cwd());
80349
80698
  if (created) {
80350
80699
  console.log(
80351
- chalk29.green(
80700
+ chalk30.green(
80352
80701
  `
80353
- \u2705 Created ${repoPath} with the promoted pin for ${chalk29.cyan(serverKey)}.`
80702
+ \u2705 Created ${repoPath} with the promoted pin for ${chalk30.cyan(serverKey)}.`
80354
80703
  )
80355
80704
  );
80356
80705
  } else {
80357
- console.log(chalk29.green(`
80358
- \u2705 Promoted ${chalk29.cyan(serverKey)} into ${repoPath}.`));
80706
+ console.log(chalk30.green(`
80707
+ \u2705 Promoted ${chalk30.cyan(serverKey)} into ${repoPath}.`));
80359
80708
  }
80360
- console.log(chalk29.gray(" Review the change and commit it:"));
80361
- console.log(chalk29.cyan(` git add ${repoPath}`));
80362
- console.log(chalk29.cyan(` git commit -m "pin ${serverKey} (node9)"`));
80709
+ console.log(chalk30.gray(" Review the change and commit it:"));
80710
+ console.log(chalk30.cyan(` git add ${repoPath}`));
80711
+ console.log(chalk30.cyan(` git commit -m "pin ${serverKey} (node9)"`));
80363
80712
  console.log("");
80364
80713
  } catch (err2) {
80365
80714
  const msg = err2 instanceof Error ? err2.message : String(err2);
80366
- console.error(chalk29.red(`
80715
+ console.error(chalk30.red(`
80367
80716
  \u274C ${msg}
80368
80717
  `));
80369
80718
  process.exit(1);
@@ -80376,36 +80725,36 @@ function registerMcpPinCommand(program2) {
80376
80725
  try {
80377
80726
  pins = readMcpPins();
80378
80727
  } catch {
80379
- console.error(chalk29.red("\n\u274C Pin file is corrupt."));
80380
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80728
+ console.error(chalk30.red("\n\u274C Pin file is corrupt."));
80729
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80381
80730
  process.exit(1);
80382
80731
  }
80383
80732
  if (!pins.servers[serverKey]) {
80384
- console.error(chalk29.red(`
80733
+ console.error(chalk30.red(`
80385
80734
  \u274C No pin found for server key "${serverKey}"
80386
80735
  `));
80387
- console.error(`Run ${chalk29.cyan("node9 mcp pin list")} to see pinned servers.
80736
+ console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
80388
80737
  `);
80389
80738
  process.exit(1);
80390
80739
  }
80391
80740
  const label3 = pins.servers[serverKey].label;
80392
80741
  removePin(serverKey);
80393
- console.log(chalk29.green(`
80394
- \u{1F513} Pin removed for ${chalk29.cyan(serverKey)}`));
80395
- console.log(chalk29.gray(` Server: ${label3}`));
80396
- console.log(chalk29.gray(" Next connection will re-pin with current tool definitions.\n"));
80742
+ console.log(chalk30.green(`
80743
+ \u{1F513} Pin removed for ${chalk30.cyan(serverKey)}`));
80744
+ console.log(chalk30.gray(` Server: ${label3}`));
80745
+ console.log(chalk30.gray(" Next connection will re-pin with current tool definitions.\n"));
80397
80746
  });
80398
80747
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
80399
80748
  const result = readMcpPinsSafe();
80400
80749
  if (!result.ok && result.reason === "missing") {
80401
- console.log(chalk29.gray("\nNo pins to clear.\n"));
80750
+ console.log(chalk30.gray("\nNo pins to clear.\n"));
80402
80751
  return;
80403
80752
  }
80404
80753
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
80405
80754
  clearAllPins();
80406
- console.log(chalk29.green(`
80755
+ console.log(chalk30.green(`
80407
80756
  \u{1F513} Cleared ${count} MCP pin(s).`));
80408
- console.log(chalk29.gray(" Next connection to each server will re-pin.\n"));
80757
+ console.log(chalk30.gray(" Next connection to each server will re-pin.\n"));
80409
80758
  });
80410
80759
  pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
80411
80760
  if (opts.stale) {
@@ -80414,7 +80763,7 @@ function registerMcpPinCommand(program2) {
80414
80763
  }
80415
80764
  if (!serverKey) {
80416
80765
  console.error(
80417
- chalk29.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
80766
+ chalk30.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
80418
80767
  );
80419
80768
  process.exit(1);
80420
80769
  }
@@ -80422,15 +80771,15 @@ function registerMcpPinCommand(program2) {
80422
80771
  try {
80423
80772
  pins = readMcpPins();
80424
80773
  } catch {
80425
- console.error(chalk29.red("\n\u274C Pin file is corrupt."));
80426
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80774
+ console.error(chalk30.red("\n\u274C Pin file is corrupt."));
80775
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80427
80776
  process.exit(1);
80428
80777
  }
80429
80778
  if (!pins.servers[serverKey]) {
80430
- console.error(chalk29.red(`
80779
+ console.error(chalk30.red(`
80431
80780
  \u274C No pin found for server key "${serverKey}"
80432
80781
  `));
80433
- console.error(`Run ${chalk29.cyan("node9 mcp pin list")} to see pinned servers.
80782
+ console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
80434
80783
  `);
80435
80784
  process.exit(1);
80436
80785
  }
@@ -80438,10 +80787,10 @@ function registerMcpPinCommand(program2) {
80438
80787
  const liveKeys = inventoryServerKeys(inv);
80439
80788
  if (liveKeys.has(serverKey)) {
80440
80789
  const agent = "an agent config";
80441
- console.error(chalk29.red(`
80790
+ console.error(chalk30.red(`
80442
80791
  \u274C Server "${serverKey}" is still configured in ${agent}.`));
80443
80792
  console.error(
80444
- chalk29.yellow(
80793
+ chalk30.yellow(
80445
80794
  ` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
80446
80795
  `
80447
80796
  )
@@ -80450,10 +80799,10 @@ function registerMcpPinCommand(program2) {
80450
80799
  }
80451
80800
  const label3 = pins.servers[serverKey].label;
80452
80801
  removePin(serverKey);
80453
- console.log(chalk29.green(`
80454
- \u2713 Forgot server ${chalk29.cyan(serverKey)}`));
80455
- console.log(chalk29.gray(` Was: ${label3}`));
80456
- console.log(chalk29.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
80802
+ console.log(chalk30.green(`
80803
+ \u2713 Forgot server ${chalk30.cyan(serverKey)}`));
80804
+ console.log(chalk30.gray(` Was: ${label3}`));
80805
+ console.log(chalk30.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
80457
80806
  });
80458
80807
  }
80459
80808
  function forgetAllStale() {
@@ -80461,16 +80810,16 @@ function forgetAllStale() {
80461
80810
  try {
80462
80811
  pins = readMcpPins();
80463
80812
  } catch {
80464
- console.error(chalk29.red("\n\u274C Pin file is corrupt."));
80465
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80813
+ console.error(chalk30.red("\n\u274C Pin file is corrupt."));
80814
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80466
80815
  process.exit(1);
80467
80816
  }
80468
80817
  const inv = inventoryMcp();
80469
80818
  const liveKeys = inventoryServerKeys(inv);
80470
80819
  if (liveKeys.size === 0) {
80471
- console.error(chalk29.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
80820
+ console.error(chalk30.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
80472
80821
  console.error(
80473
- chalk29.yellow(
80822
+ chalk30.yellow(
80474
80823
  " This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
80475
80824
  )
80476
80825
  );
@@ -80478,118 +80827,118 @@ function forgetAllStale() {
80478
80827
  }
80479
80828
  const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
80480
80829
  if (stale.length === 0) {
80481
- console.log(chalk29.gray("\nNo stale servers to remove.\n"));
80830
+ console.log(chalk30.gray("\nNo stale servers to remove.\n"));
80482
80831
  return;
80483
80832
  }
80484
80833
  for (const [sk, pin] of stale) {
80485
80834
  delete pins.servers[sk];
80486
- console.log(chalk29.green(` \u2713 ${chalk29.cyan(sk)} ${chalk29.gray(pin.label)}`));
80835
+ console.log(chalk30.green(` \u2713 ${chalk30.cyan(sk)} ${chalk30.gray(pin.label)}`));
80487
80836
  }
80488
80837
  writeMcpPins(pins);
80489
- console.log(chalk29.green(`
80838
+ console.log(chalk30.green(`
80490
80839
  Removed ${stale.length} stale server(s).
80491
80840
  `));
80492
80841
  }
80493
80842
 
80494
80843
  // src/cli/commands/sync.ts
80495
80844
  init_sync();
80496
- import chalk30 from "chalk";
80845
+ import chalk31 from "chalk";
80497
80846
  function registerSyncCommand(program2) {
80498
80847
  const policy = program2.command("policy").description("Manage cloud policy rules");
80499
80848
  policy.command("push").description("Push this machine's effective policy to the node9 dashboard").action(async () => {
80500
- process.stdout.write(chalk30.cyan("Pushing policy to the dashboard\u2026"));
80849
+ process.stdout.write(chalk31.cyan("Pushing policy to the dashboard\u2026"));
80501
80850
  const result = await runPolicyPush();
80502
80851
  process.stdout.write("\n");
80503
80852
  if (!result.ok) {
80504
- console.error(chalk30.red(`\u2717 ${result.reason}`));
80853
+ console.error(chalk31.red(`\u2717 ${result.reason}`));
80505
80854
  process.exit(1);
80506
80855
  }
80507
- console.log(chalk30.green("\u2713 Policy mirrored to the dashboard"));
80508
- console.log(chalk30.gray(" See it under Security Policy \u2192 Machines"));
80856
+ console.log(chalk31.green("\u2713 Policy mirrored to the dashboard"));
80857
+ console.log(chalk31.gray(" See it under Security Policy \u2192 Machines"));
80509
80858
  });
80510
80859
  policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
80511
- process.stdout.write(chalk30.cyan("Syncing cloud policy rules\u2026"));
80860
+ process.stdout.write(chalk31.cyan("Syncing cloud policy rules\u2026"));
80512
80861
  const result = await runCloudSync();
80513
80862
  process.stdout.write("\n");
80514
80863
  if (!result.ok) {
80515
- console.error(chalk30.red(`\u2717 ${result.reason}`));
80864
+ console.error(chalk31.red(`\u2717 ${result.reason}`));
80516
80865
  process.exit(1);
80517
80866
  }
80518
80867
  if (result.unchanged) {
80519
80868
  console.log(
80520
- chalk30.green(
80869
+ chalk31.green(
80521
80870
  `\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
80522
80871
  )
80523
80872
  );
80524
- console.log(chalk30.gray(` Cached at: ${result.fetchedAt}`));
80525
- console.log(chalk30.gray(` Server returned 304 (no changes since last sync)`));
80873
+ console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
80874
+ console.log(chalk31.gray(` Server returned 304 (no changes since last sync)`));
80526
80875
  } else {
80527
80876
  console.log(
80528
- chalk30.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
80877
+ chalk31.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
80529
80878
  );
80530
- console.log(chalk30.gray(` Cached at: ${result.fetchedAt}`));
80531
- console.log(chalk30.gray(` File: ~/.node9/rules-cache.json`));
80879
+ console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
80880
+ console.log(chalk31.gray(` File: ~/.node9/rules-cache.json`));
80532
80881
  }
80533
80882
  });
80534
80883
  policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
80535
80884
  const status = getCloudSyncStatus();
80536
80885
  if (!status.cached) {
80537
- console.log(chalk30.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
80886
+ console.log(chalk31.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
80538
80887
  return;
80539
80888
  }
80540
80889
  const rules = getCloudRules() ?? [];
80541
80890
  const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
80542
80891
  console.log(
80543
- chalk30.bold(`
80544
- Cloud policy rules`) + chalk30.gray(
80892
+ chalk31.bold(`
80893
+ Cloud policy rules`) + chalk31.gray(
80545
80894
  ` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
80546
80895
  `
80547
80896
  )
80548
80897
  );
80549
80898
  if (rules.length === 0) {
80550
- console.log(chalk30.gray(" No rules defined in cloud policy.\n"));
80899
+ console.log(chalk31.gray(" No rules defined in cloud policy.\n"));
80551
80900
  return;
80552
80901
  }
80553
80902
  for (const rule of rules) {
80554
80903
  const r = rule;
80555
- const verdictColor = r.verdict === "block" ? chalk30.red : r.verdict === "allow" ? chalk30.green : chalk30.yellow;
80904
+ const verdictColor = r.verdict === "block" ? chalk31.red : r.verdict === "allow" ? chalk31.green : chalk31.yellow;
80556
80905
  console.log(
80557
80906
  ` ${verdictColor(
80558
80907
  String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
80559
- )} ${chalk30.white(String(r.name ?? "(unnamed)"))}`
80908
+ )} ${chalk31.white(String(r.name ?? "(unnamed)"))}`
80560
80909
  );
80561
- if (r.reason) console.log(chalk30.gray(` ${String(r.reason)}`));
80910
+ if (r.reason) console.log(chalk31.gray(` ${String(r.reason)}`));
80562
80911
  }
80563
80912
  console.log("");
80564
80913
  });
80565
80914
  policy.command("status").description("Show current cloud policy cache status").action(() => {
80566
80915
  const s = getCloudSyncStatus();
80567
80916
  if (!s.cached) {
80568
- console.log(chalk30.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
80917
+ console.log(chalk31.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
80569
80918
  return;
80570
80919
  }
80571
80920
  const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
80572
80921
  console.log(`
80573
- Rules : ${chalk30.green(String(s.rules))} cloud rules loaded`);
80922
+ Rules : ${chalk31.green(String(s.rules))} cloud rules loaded`);
80574
80923
  console.log(
80575
- ` Synced : ${chalk30.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
80924
+ ` Synced : ${chalk31.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
80576
80925
  );
80577
80926
  if (s.workspaceId) {
80578
- console.log(` Workspace: ${chalk30.gray(s.workspaceId)}`);
80927
+ console.log(` Workspace: ${chalk31.gray(s.workspaceId)}`);
80579
80928
  }
80580
80929
  if (s.panicMode) {
80581
80930
  console.log(
80582
- ` ${chalk30.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk30.dim("(every review-verdict becomes block)")
80931
+ ` ${chalk31.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk31.dim("(every review-verdict becomes block)")
80583
80932
  );
80584
80933
  }
80585
80934
  if (s.shadowMode) {
80586
80935
  console.log(
80587
- ` ${chalk30.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk30.dim("(blocks become would-block log entries)")
80936
+ ` ${chalk31.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk31.dim("(blocks become would-block log entries)")
80588
80937
  );
80589
80938
  }
80590
80939
  if (s.syncIntervalHours) {
80591
80940
  console.log(
80592
- chalk30.gray(
80941
+ chalk31.gray(
80593
80942
  ` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
80594
80943
  )
80595
80944
  );
@@ -80600,7 +80949,7 @@ function registerSyncCommand(program2) {
80600
80949
 
80601
80950
  // src/cli/commands/agents.ts
80602
80951
  init_setup();
80603
- import chalk31 from "chalk";
80952
+ import chalk32 from "chalk";
80604
80953
  var SETUP_FN = {
80605
80954
  claude: setupClaude,
80606
80955
  gemini: setupGemini,
@@ -80643,30 +80992,30 @@ function registerAgentsCommand(program2) {
80643
80992
  console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
80644
80993
  console.log(" " + "\u2500".repeat(44));
80645
80994
  for (const s of statuses) {
80646
- const installed = s.installed ? chalk31.green("\u2713") : chalk31.gray("\u2717");
80647
- const wired = !s.installed ? chalk31.gray("\u2014") : s.wired ? chalk31.green("\u2713") : chalk31.yellow("\u2717");
80648
- const mode = s.mode ? chalk31.gray(s.mode) : chalk31.gray("\u2014");
80649
- const hint = s.installed && !s.wired ? chalk31.gray(` \u2190 node9 agents add ${s.name}`) : "";
80995
+ const installed = s.installed ? chalk32.green("\u2713") : chalk32.gray("\u2717");
80996
+ const wired = !s.installed ? chalk32.gray("\u2014") : s.wired ? chalk32.green("\u2713") : chalk32.yellow("\u2717");
80997
+ const mode = s.mode ? chalk32.gray(s.mode) : chalk32.gray("\u2014");
80998
+ const hint = s.installed && !s.wired ? chalk32.gray(` \u2190 node9 agents add ${s.name}`) : "";
80650
80999
  console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
80651
81000
  }
80652
81001
  console.log("");
80653
81002
  if (!anyInstalled) {
80654
81003
  console.log(
80655
- chalk31.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + chalk31.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
81004
+ chalk32.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + chalk32.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
80656
81005
  );
80657
81006
  return;
80658
81007
  }
80659
81008
  const unwired = statuses.filter((s) => s.installed && !s.wired);
80660
81009
  if (unwired.length > 0) {
80661
81010
  console.log(
80662
- chalk31.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk31.white(`node9 agents add ${unwired[0].name}`) + "\n"
81011
+ chalk32.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk32.white(`node9 agents add ${unwired[0].name}`) + "\n"
80663
81012
  );
80664
81013
  }
80665
81014
  if (statuses.some((s) => s.name === "gemini" && s.installed)) {
80666
81015
  console.log(
80667
- chalk31.yellow(
81016
+ chalk32.yellow(
80668
81017
  " \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
80669
- ) + chalk31.gray(" Migrate to Antigravity: ") + chalk31.white("node9 agents add antigravity") + "\n"
81018
+ ) + chalk32.gray(" Migrate to Antigravity: ") + chalk32.white("node9 agents add antigravity") + "\n"
80670
81019
  );
80671
81020
  }
80672
81021
  });
@@ -80674,7 +81023,7 @@ function registerAgentsCommand(program2) {
80674
81023
  const name = resolveAgentName(agent);
80675
81024
  const fn = SETUP_FN[name];
80676
81025
  if (!fn) {
80677
- console.error(chalk31.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
81026
+ console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
80678
81027
  process.exit(1);
80679
81028
  }
80680
81029
  await fn();
@@ -80683,14 +81032,14 @@ function registerAgentsCommand(program2) {
80683
81032
  const name = resolveAgentName(agent);
80684
81033
  const fn = TEARDOWN_FN[name];
80685
81034
  if (!fn) {
80686
- console.error(chalk31.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
81035
+ console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
80687
81036
  process.exit(1);
80688
81037
  }
80689
- console.log(chalk31.cyan(`
81038
+ console.log(chalk32.cyan(`
80690
81039
  \u{1F6E1}\uFE0F Node9: removing from ${name}...
80691
81040
  `));
80692
81041
  fn();
80693
- console.log(chalk31.gray("\n Restart the agent for changes to take effect."));
81042
+ console.log(chalk32.gray("\n Restart the agent for changes to take effect."));
80694
81043
  });
80695
81044
  }
80696
81045
 
@@ -80699,21 +81048,21 @@ init_scan();
80699
81048
 
80700
81049
  // src/cli/commands/posture.ts
80701
81050
  init_posture();
80702
- import chalk33 from "chalk";
81051
+ import chalk34 from "chalk";
80703
81052
 
80704
81053
  // src/posture/render.ts
80705
81054
  init_score();
80706
- import chalk32 from "chalk";
81055
+ import chalk33 from "chalk";
80707
81056
  var ICON = {
80708
- critical: chalk32.red("\u274C"),
80709
- high: chalk32.red("\u274C"),
80710
- medium: chalk32.yellow("\u26A0\uFE0F "),
80711
- advisory: chalk32.gray("\u26A0\uFE0F ")
81057
+ critical: chalk33.red("\u274C"),
81058
+ high: chalk33.red("\u274C"),
81059
+ medium: chalk33.yellow("\u26A0\uFE0F "),
81060
+ advisory: chalk33.gray("\u26A0\uFE0F ")
80712
81061
  };
80713
81062
  var TIER_LABEL = {
80714
- good: chalk32.green("Good"),
80715
- "at-risk": chalk32.yellow("At risk"),
80716
- critical: chalk32.red("Critical")
81063
+ good: chalk33.green("Good"),
81064
+ "at-risk": chalk33.yellow("At risk"),
81065
+ critical: chalk33.red("Critical")
80717
81066
  };
80718
81067
  function wrap(text, width) {
80719
81068
  const out = [];
@@ -80731,7 +81080,7 @@ function wrap(text, width) {
80731
81080
  }
80732
81081
  var LABEL_WIDTH = 14;
80733
81082
  function label2(category) {
80734
- return chalk32.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
81083
+ return chalk33.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
80735
81084
  }
80736
81085
  function guardedWhat(f) {
80737
81086
  if (f.detail.length === 0) return "this";
@@ -80741,31 +81090,31 @@ function guardedWhat(f) {
80741
81090
  }
80742
81091
  function renderFinding(f, showWeight = false, displayLabel = f.category) {
80743
81092
  const lines = [];
80744
- const wt = showWeight && f.scoreWeight ? chalk32.cyan.bold(`+${f.scoreWeight} `) : "";
81093
+ const wt = showWeight && f.scoreWeight ? chalk33.cyan.bold(`+${f.scoreWeight} `) : "";
80745
81094
  lines.push(` ${ICON[f.severity]} ${label2(displayLabel)}${wt}${f.title}`);
80746
81095
  const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
80747
81096
  const width = 80 - indent.length;
80748
81097
  for (const s of [f.what, f.why, f.who]) {
80749
- if (s) for (const l of wrap(s, width)) lines.push(indent + chalk32.gray(l));
81098
+ if (s) for (const l of wrap(s, width)) lines.push(indent + chalk33.gray(l));
80750
81099
  }
80751
- for (const d of f.detail) lines.push(indent + chalk32.gray(d));
81100
+ for (const d of f.detail) lines.push(indent + chalk33.gray(d));
80752
81101
  if (f.fix) {
80753
81102
  let first = true;
80754
81103
  for (const seg of f.fix.split("\n")) {
80755
81104
  for (const l of wrap(seg, width - 2)) {
80756
- lines.push(indent + chalk32.cyan(first ? "\u2192 " + l : " " + l));
81105
+ lines.push(indent + chalk33.cyan(first ? "\u2192 " + l : " " + l));
80757
81106
  first = false;
80758
81107
  }
80759
81108
  }
80760
81109
  }
80761
81110
  const tradeoff = [
80762
- [f.gain, "gain: ", chalk32.green],
80763
- [f.cost, "cost: ", chalk32.yellow]
81111
+ [f.gain, "gain: ", chalk33.green],
81112
+ [f.cost, "cost: ", chalk33.yellow]
80764
81113
  ];
80765
81114
  for (const [text, lbl, color2] of tradeoff) {
80766
81115
  if (!text) continue;
80767
81116
  wrap(text, width - 6).forEach((l, i) => {
80768
- lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk32.gray(l));
81117
+ lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk33.gray(l));
80769
81118
  });
80770
81119
  }
80771
81120
  return lines;
@@ -80775,7 +81124,7 @@ function renderPosture(result) {
80775
81124
  const tier = TIER_LABEL[result.tier];
80776
81125
  lines.push("");
80777
81126
  lines.push(
80778
- chalk32.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk32.gray(` \u2014 ${result.agent}`) + ` ${chalk32.bold(`Score: ${result.score}/100`)} (${tier})`
81127
+ chalk33.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk33.gray(` \u2014 ${result.agent}`) + ` ${chalk33.bold(`Score: ${result.score}/100`)} (${tier})`
80779
81128
  );
80780
81129
  const headroom = openHeadroom(result.findings);
80781
81130
  if (headroom > 0) {
@@ -80783,15 +81132,15 @@ function renderPosture(result) {
80783
81132
  (f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
80784
81133
  ).length;
80785
81134
  const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
80786
- for (const l of wrap(note, 76)) lines.push(" " + chalk32.gray(l));
81135
+ for (const l of wrap(note, 76)) lines.push(" " + chalk33.gray(l));
80787
81136
  }
80788
81137
  lines.push("");
80789
81138
  if (result.headline) {
80790
81139
  const indent = " ";
80791
- lines.push(` ${chalk32.red.bold("\u{1F525} Biggest risk")}`);
80792
- for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk32.white(l));
81140
+ lines.push(` ${chalk33.red.bold("\u{1F525} Biggest risk")}`);
81141
+ for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk33.white(l));
80793
81142
  const action = wrap(`Do this first: ${result.headline.action}`, 72);
80794
- action.forEach((l, i) => lines.push(indent + chalk32.cyan(i === 0 ? "\u2192 " + l : " " + l)));
81143
+ action.forEach((l, i) => lines.push(indent + chalk33.cyan(i === 0 ? "\u2192 " + l : " " + l)));
80795
81144
  lines.push("");
80796
81145
  }
80797
81146
  const covered = result.findings.filter((f) => f.coverage?.state === "covered");
@@ -80801,13 +81150,13 @@ function renderPosture(result) {
80801
81150
  );
80802
81151
  const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
80803
81152
  if (covered.length > 0) {
80804
- lines.push(" " + chalk32.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
81153
+ lines.push(" " + chalk33.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
80805
81154
  for (const f of covered) {
80806
81155
  const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
80807
81156
  const via = f.coverage?.via ?? "node9";
80808
81157
  const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
80809
81158
  lines.push(
80810
- ` ${chalk32.green("\u2705")} ${label2(lbl)}${chalk32.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
81159
+ ` ${chalk33.green("\u2705")} ${label2(lbl)}${chalk33.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
80811
81160
  );
80812
81161
  }
80813
81162
  lines.push("");
@@ -80816,24 +81165,24 @@ function renderPosture(result) {
80816
81165
  const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
80817
81166
  const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
80818
81167
  if (node9Open.length > 0) {
80819
- lines.push(" " + chalk32.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
81168
+ lines.push(" " + chalk33.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
80820
81169
  for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
80821
81170
  }
80822
81171
  if (reduceOpen.length > 0) {
80823
81172
  if (node9Open.length > 0) lines.push("");
80824
- lines.push(" " + chalk32.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
81173
+ lines.push(" " + chalk33.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
80825
81174
  for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
80826
81175
  }
80827
81176
  if (osOpen.length > 0) {
80828
81177
  if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
80829
- lines.push(" " + chalk32.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
81178
+ lines.push(" " + chalk33.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
80830
81179
  for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
80831
81180
  }
80832
81181
  for (const cat of result.passedCategories) {
80833
- lines.push(` ${chalk32.green("\u2705")} ${label2(cat)}${chalk32.gray("no issues found")}`);
81182
+ lines.push(` ${chalk33.green("\u2705")} ${label2(cat)}${chalk33.gray("no issues found")}`);
80834
81183
  }
80835
81184
  for (const cat of result.erroredCategories) {
80836
- lines.push(` ${chalk32.gray("\u2022")} ${label2(cat)}${chalk32.gray("could not be checked")}`);
81185
+ lines.push(` ${chalk33.gray("\u2022")} ${label2(cat)}${chalk33.gray("could not be checked")}`);
80837
81186
  }
80838
81187
  lines.push("");
80839
81188
  const crit = open.filter((f) => f.severity === "critical").length;
@@ -80841,16 +81190,16 @@ function renderPosture(result) {
80841
81190
  const med = open.filter((f) => f.severity === "medium").length;
80842
81191
  const adv = open.filter((f) => f.severity === "advisory").length;
80843
81192
  const parts = [];
80844
- if (crit) parts.push(chalk32.red(`${crit} critical`));
80845
- if (high) parts.push(chalk32.red(`${high} high`));
80846
- if (med) parts.push(chalk32.yellow(`${med} medium`));
80847
- if (adv) parts.push(chalk32.gray(`${adv} advisory`));
80848
- const summary = parts.length ? parts.join(" \xB7 ") : chalk32.green("no findings");
81193
+ if (crit) parts.push(chalk33.red(`${crit} critical`));
81194
+ if (high) parts.push(chalk33.red(`${high} high`));
81195
+ if (med) parts.push(chalk33.yellow(`${med} medium`));
81196
+ if (adv) parts.push(chalk33.gray(`${adv} advisory`));
81197
+ const summary = parts.length ? parts.join(" \xB7 ") : chalk33.green("no findings");
80849
81198
  lines.push(` ${summary}`);
80850
81199
  lines.push("");
80851
- lines.push(" " + chalk32.bold("Track this across your fleet & keep it green:"));
81200
+ lines.push(" " + chalk33.bold("Track this across your fleet & keep it green:"));
80852
81201
  lines.push(
80853
- " " + chalk32.dim("\u2192 ") + chalk32.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
81202
+ " " + chalk33.dim("\u2192 ") + chalk33.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
80854
81203
  );
80855
81204
  lines.push("");
80856
81205
  return lines.join("\n");
@@ -80870,11 +81219,11 @@ function registerPostureCommand(program2) {
80870
81219
  if (opts.ship) {
80871
81220
  const creds = readCredentials();
80872
81221
  if (!creds) {
80873
- console.error(chalk33.gray(" Run `node9 login` to ship this to your dashboard."));
81222
+ console.error(chalk34.gray(" Run `node9 login` to ship this to your dashboard."));
80874
81223
  } else {
80875
81224
  const ok2 = await shipPosture(result, creds);
80876
81225
  console.error(
80877
- ok2 ? chalk33.gray(" \u2713 Shipped to your node9 dashboard.") : chalk33.gray(" Could not reach the dashboard \u2014 saved locally only.")
81226
+ ok2 ? chalk34.gray(" \u2713 Shipped to your node9 dashboard.") : chalk34.gray(" Could not reach the dashboard \u2014 saved locally only.")
80878
81227
  );
80879
81228
  }
80880
81229
  }
@@ -80883,7 +81232,7 @@ function registerPostureCommand(program2) {
80883
81232
  }
80884
81233
 
80885
81234
  // src/cli/commands/scan-repo.ts
80886
- import chalk35 from "chalk";
81235
+ import chalk36 from "chalk";
80887
81236
 
80888
81237
  // src/ci-check/fetch.ts
80889
81238
  var import_undici = __toESM(require_undici());
@@ -86395,7 +86744,7 @@ async function scanRepo(input, onProgress) {
86395
86744
  }
86396
86745
 
86397
86746
  // src/ci-check/render.ts
86398
- import chalk34 from "chalk";
86747
+ import chalk35 from "chalk";
86399
86748
  var listed = (fs109) => fs109.filter((f) => f.tier !== "review");
86400
86749
  function excerpt(f) {
86401
86750
  const m = /`([^`]+)`/.exec(f.signals[0] ?? "");
@@ -86440,23 +86789,23 @@ function reviewTerminal(findings) {
86440
86789
  const L = [];
86441
86790
  if (items) {
86442
86791
  L.push(
86443
- chalk34.bold(`\u{1F50D} ${items} ${items === 1 ? "item" : "items"} worth a look (not counted)`) + chalk34.gray(": risky-looking text in a context that is usually harmless. Please confirm.")
86792
+ chalk35.bold(`\u{1F50D} ${items} ${items === 1 ? "item" : "items"} worth a look (not counted)`) + chalk35.gray(": risky-looking text in a context that is usually harmless. Please confirm.")
86444
86793
  );
86445
86794
  const files = [...byFile.keys()];
86446
86795
  for (const file2 of files.slice(0, REVIEW_FILES)) {
86447
86796
  const fs109 = byFile.get(file2);
86448
- L.push(chalk34.gray(` ${file2}`));
86797
+ L.push(chalk35.gray(` ${file2}`));
86449
86798
  for (const f of fs109.slice(0, REVIEW_LINES_PER_FILE))
86450
86799
  L.push(` \u2022 ${f.line ? `line ${f.line}: ` : ""}${excerpt(f)}`);
86451
86800
  if (fs109.length > REVIEW_LINES_PER_FILE)
86452
- L.push(chalk34.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
86801
+ L.push(chalk35.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
86453
86802
  }
86454
86803
  const more = files.length - REVIEW_FILES;
86455
- if (more > 0) L.push(chalk34.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
86804
+ if (more > 0) L.push(chalk35.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
86456
86805
  }
86457
86806
  if (grants)
86458
86807
  L.push(
86459
- chalk34.gray(
86808
+ chalk35.gray(
86460
86809
  ` ${grants} skill${grants === 1 ? " is" : "s are"} granted broad tools (Bash, Write or Edit); usually what the skill needs.`
86461
86810
  )
86462
86811
  );
@@ -86470,45 +86819,45 @@ var ICON2 = {
86470
86819
  advisory: "\u{1F7E2}"
86471
86820
  };
86472
86821
  var COLOR = {
86473
- critical: chalk34.red.bold,
86474
- high: chalk34.red,
86475
- medium: chalk34.yellow,
86476
- advisory: chalk34.gray
86822
+ critical: chalk35.red.bold,
86823
+ high: chalk35.red,
86824
+ medium: chalk35.yellow,
86825
+ advisory: chalk35.gray
86477
86826
  };
86478
86827
  var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security?ref=cli_scan_repo";
86479
86828
  function renderCta(res) {
86480
86829
  const L = [];
86481
- L.push(chalk34.dim(" " + "\u2500".repeat(63)));
86830
+ L.push(chalk35.dim(" " + "\u2500".repeat(63)));
86482
86831
  if (res.worst === "critical" || res.worst === "high") {
86483
86832
  const n = res.findings.filter(
86484
86833
  (f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
86485
86834
  ).length;
86486
86835
  L.push(
86487
- " " + chalk34.red.bold(
86836
+ " " + chalk35.red.bold(
86488
86837
  `\u{1F534} ${n} ${n === 1 ? "issue" : "issues"} to fix, then stop the next at the PR.`
86489
86838
  )
86490
86839
  );
86491
86840
  L.push("");
86492
- L.push(" " + chalk34.bold("Catch this class of issue on every PR, automatically:"));
86841
+ L.push(" " + chalk35.bold("Catch this class of issue on every PR, automatically:"));
86493
86842
  } else if (res.worst) {
86494
- L.push(" " + chalk34.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
86843
+ L.push(" " + chalk35.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
86495
86844
  L.push("");
86496
- L.push(" " + chalk34.bold("Check every PR for agent-CI risk:"));
86845
+ L.push(" " + chalk35.bold("Check every PR for agent-CI risk:"));
86497
86846
  } else if (res.incomplete) {
86498
- L.push(" " + chalk34.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
86847
+ L.push(" " + chalk35.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
86499
86848
  L.push("");
86500
- L.push(" " + chalk34.bold("Get a complete check on every PR (CI reads the tree directly):"));
86849
+ L.push(" " + chalk35.bold("Get a complete check on every PR (CI reads the tree directly):"));
86501
86850
  } else {
86502
86851
  L.push(
86503
- " " + chalk34.green(
86852
+ " " + chalk35.green(
86504
86853
  groupReview(res.findings).byFile.size ? "\u2705 No alerts. Check the items worth a look above." : "\u2705 Agent CI is well-configured: 0 unmitigated issues."
86505
86854
  )
86506
86855
  );
86507
86856
  L.push("");
86508
- L.push(" " + chalk34.bold("Keep it green as you add agent workflows. Check every PR:"));
86857
+ L.push(" " + chalk35.bold("Keep it green as you add agent workflows. Check every PR:"));
86509
86858
  }
86510
- L.push(" " + chalk34.dim("\u2192 ") + chalk34.cyan.underline(ACTION_URL));
86511
- L.push(" " + chalk34.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
86859
+ L.push(" " + chalk35.dim("\u2192 ") + chalk35.cyan.underline(ACTION_URL));
86860
+ L.push(" " + chalk35.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
86512
86861
  return L;
86513
86862
  }
86514
86863
  function ownedHint(source) {
@@ -86548,21 +86897,21 @@ function renderScan(res, diff) {
86548
86897
  const L = [];
86549
86898
  const shown = listed(res.findings);
86550
86899
  const n = shown.length;
86551
- const head = res.worst === "critical" || res.worst === "high" ? chalk34.red.bold("\u26A0\uFE0F agent-security risk found") : res.worst ? chalk34.yellow("agent-security notes") : res.incomplete ? chalk34.yellow.bold("\u26A0\uFE0F INCOMPLETE: could not read all files") : groupReview(res.findings).byFile.size ? chalk34.green("\u2705 agent-security: no alerts (items worth a look below)") : chalk34.green("\u2705 agent-security: clean");
86552
- L.push(`\u{1F6E1}\uFE0F ${chalk34.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
86900
+ const head = res.worst === "critical" || res.worst === "high" ? chalk35.red.bold("\u26A0\uFE0F agent-security risk found") : res.worst ? chalk35.yellow("agent-security notes") : res.incomplete ? chalk35.yellow.bold("\u26A0\uFE0F INCOMPLETE: could not read all files") : groupReview(res.findings).byFile.size ? chalk35.green("\u2705 agent-security: no alerts (items worth a look below)") : chalk35.green("\u2705 agent-security: clean");
86901
+ L.push(`\u{1F6E1}\uFE0F ${chalk35.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
86553
86902
  L.push(
86554
- chalk34.gray(
86903
+ chalk35.gray(
86555
86904
  ` inspected ${res.inspected.length} config file(s), ${n} finding(s)` + (res.suppressedCount ? ` \xB7 ${res.suppressedCount} suppressed by .node9-ignore.json` : "")
86556
86905
  )
86557
86906
  );
86558
86907
  if (diff) {
86559
86908
  const introduced = listed(diff.added).length + diff.escalated.length;
86560
86909
  L.push(
86561
- diff.base !== "ok" ? chalk34.yellow.bold(
86910
+ diff.base !== "ok" ? chalk35.yellow.bold(
86562
86911
  ` \u26A0\uFE0F base ${diff.base === "did-not-run" ? "could not be read" : "scan was incomplete"}, so it cannot say what is new; showing everything`
86563
- ) : introduced > 0 ? chalk34.red.bold(
86912
+ ) : introduced > 0 ? chalk35.red.bold(
86564
86913
  ` \u26A0\uFE0F this change introduced ${introduced} finding(s)` + (diff.escalated.length ? ` (${diff.escalated.length} by widening an existing one)` : "")
86565
- ) : chalk34.green(
86914
+ ) : chalk35.green(
86566
86915
  ` \u2705 this change introduced nothing` + (diff.unchanged.length ? ` (${diff.unchanged.length} pre-existing)` : "") + (diff.removed.length ? `, and fixed ${diff.removed.length}` : "")
86567
86916
  )
86568
86917
  );
@@ -86570,7 +86919,7 @@ function renderScan(res, diff) {
86570
86919
  if (res.incomplete) {
86571
86920
  const rateLimited = res.notes.some((nt) => /rate limit/i.test(nt));
86572
86921
  L.push(
86573
- chalk34.yellow.bold(
86922
+ chalk35.yellow.bold(
86574
86923
  rateLimited ? " \u26A0\uFE0F Rate-limited: some files were unread. NOT a clean bill of health; set GITHUB_TOKEN (or run `gh auth login`) and re-run." : " \u26A0\uFE0F A network error left some files unread. NOT a clean bill of health; re-run."
86575
86924
  )
86576
86925
  );
@@ -86579,36 +86928,36 @@ function renderScan(res, diff) {
86579
86928
  for (const f of shown) {
86580
86929
  const ex = f.explain;
86581
86930
  L.push(
86582
- `${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk34.bold(ex ? ex.headline : f.title)}`
86931
+ `${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk35.bold(ex ? ex.headline : f.title)}`
86583
86932
  );
86584
- L.push(chalk34.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
86933
+ L.push(chalk35.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
86585
86934
  if (ex) {
86586
86935
  L.push(` ${ex.happens}`);
86587
86936
  for (const s of ex.saw) L.push(` \u2022 ${s}`);
86588
- ex.fix.forEach((x, i) => L.push(chalk34.cyan(` ${i + 1}. ${x}`)));
86937
+ ex.fix.forEach((x, i) => L.push(chalk35.cyan(` ${i + 1}. ${x}`)));
86589
86938
  } else {
86590
86939
  for (const s of f.signals) L.push(` \u2022 ${s}`);
86591
86940
  if (f.mitigations?.length)
86592
- L.push(chalk34.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
86593
- L.push(chalk34.cyan(` \u2192 ${f.fix}`));
86941
+ L.push(chalk35.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
86942
+ L.push(chalk35.cyan(` \u2192 ${f.fix}`));
86594
86943
  }
86595
86944
  L.push("");
86596
86945
  }
86597
86946
  if (n === 0 && !res.incomplete) {
86598
86947
  L.push(
86599
- chalk34.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
86948
+ chalk35.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
86600
86949
  );
86601
86950
  L.push("");
86602
86951
  }
86603
86952
  L.push(...reviewTerminal(res.findings));
86604
- for (const note of res.notes) L.push(chalk34.gray(` note: ${note}`));
86953
+ for (const note of res.notes) L.push(chalk35.gray(` note: ${note}`));
86605
86954
  const hasHigh = shown.some(
86606
86955
  (f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
86607
86956
  );
86608
86957
  if (hasHigh && !ownedHint(res.source)) {
86609
86958
  L.push("");
86610
86959
  L.push(
86611
- chalk34.yellow(
86960
+ chalk35.yellow(
86612
86961
  " \u26A0\uFE0F This looks like a live issue on a repo you may not own. Disclose it privately\n to the maintainers; do not publish it. (node9 never weaponizes findings.)"
86613
86962
  )
86614
86963
  );
@@ -86802,7 +87151,7 @@ function makeProgress(target, quiet) {
86802
87151
  let frame = 0;
86803
87152
  let last = `scanning ${target}\u2026`;
86804
87153
  const timer = setInterval(() => {
86805
- process.stderr.write(`\r${chalk35.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
87154
+ process.stderr.write(`\r${chalk36.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
86806
87155
  }, 80);
86807
87156
  return {
86808
87157
  onProgress: (p) => {
@@ -86865,236 +87214,13 @@ function registerScanRepoCommand(program2) {
86865
87214
  if (!telemetryDisabled()) {
86866
87215
  await sendScanRepoPing(buildScanRepoPing(res, target));
86867
87216
  if (!opts.json && !opts.markdown && process.stderr.isTTY) {
86868
- process.stderr.write(chalk35.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
87217
+ process.stderr.write(chalk36.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
86869
87218
  }
86870
87219
  }
86871
87220
  }
86872
87221
  );
86873
87222
  }
86874
87223
 
86875
- // src/cli/commands/egress.ts
86876
- init_config();
86877
- init_keyed_guard();
86878
- init_dist();
86879
- init_egress_config();
86880
- init_egress_config();
86881
- import chalk36 from "chalk";
86882
- function guard(fn) {
86883
- try {
86884
- fn();
86885
- return true;
86886
- } catch (err2) {
86887
- console.error(chalk36.red(`
86888
- \u2717 ${err2.message}
86889
- `));
86890
- process.exitCode = 1;
86891
- return false;
86892
- }
86893
- }
86894
- function mutate(action, change) {
86895
- if (!cliGuardPolicyWrite(action)) return false;
86896
- return guard(() => setEgress(change));
86897
- }
86898
- function addHost(list2, host2) {
86899
- if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
86900
- return guard(() => addEgressHost(list2, host2));
86901
- }
86902
- function exempt2(address) {
86903
- if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
86904
- return guard(() => addSsrfExemption(address));
86905
- }
86906
- var INTERNAL_FIELDS = {
86907
- allowed: { ssrfStrict: false, allowPrivate: true },
86908
- listed: { ssrfStrict: false, allowPrivate: false },
86909
- blocked: { ssrfStrict: true, allowPrivate: false }
86910
- };
86911
- var INTERNAL_SAID = {
86912
- allowed: "reachable without listing them",
86913
- listed: "reachable only if they are on your allowlist",
86914
- blocked: "blocked"
86915
- };
86916
- function readInternalState(e) {
86917
- if (e.ssrfStrict === true) return "blocked";
86918
- return e.allowPrivate === false ? "listed" : "allowed";
86919
- }
86920
- function setInternal(value) {
86921
- const state = value.trim().toLowerCase();
86922
- if (!(state in INTERNAL_FIELDS)) {
86923
- console.error(
86924
- chalk36.red(`
86925
- \u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk36.gray(
86926
- "\n allowed loopback, 10/172.16/192.168 and CGNAT are reachable (default)\n listed they are reachable only if you allowlist them\n blocked they are blocked at the floor\n"
86927
- )
86928
- );
86929
- process.exitCode = 1;
86930
- return;
86931
- }
86932
- if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
86933
- _resetConfigCache();
86934
- const effective = readInternalState(getConfig().policy.egress);
86935
- if (effective !== state) {
86936
- console.log(
86937
- chalk36.yellow(
86938
- `
86939
- \u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
86940
- Change it in the dashboard, Enforcement \u2192 Network.
86941
- `
86942
- )
86943
- );
86944
- return;
86945
- }
86946
- const line = `
86947
- \u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
86948
- `;
86949
- console.log(state === "allowed" ? chalk36.yellow(line) : chalk36.green(line));
86950
- }
86951
- function showFloor(e, ssrfStrictSource) {
86952
- console.log(
86953
- chalk36.gray("\n Protected addresses") + chalk36.gray(" \u2014 in shell commands and declared URLs")
86954
- );
86955
- console.log(
86956
- chalk36.gray(
86957
- " always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
86958
- )
86959
- );
86960
- const state = readInternalState(e);
86961
- const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
86962
- const STATE_LABEL = {
86963
- allowed: "allowed",
86964
- listed: "allowlist only",
86965
- blocked: "blocked"
86966
- };
86967
- console.log(
86968
- ` Internal addresses: ${state === "blocked" ? chalk36.green(STATE_LABEL[state]) : chalk36.yellow(STATE_LABEL[state])}` + chalk36.gray(` loopback, 10/172.16/192.168 and CGNAT are ${INTERNAL_SAID[state]}`)
86969
- );
86970
- console.log(chalk36.gray(` set by: ${by}`));
86971
- const exemptions = e.ssrfAllow ?? [];
86972
- console.log(chalk36.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
86973
- console.log(
86974
- chalk36.gray(
86975
- " Covered: shell commands, and tools that declare a URL (WebFetch, an MCP\n fetch tool, browser navigate).\n Not covered: an interpreter one-liner (node -e, python3 -c) hides its\n destination inside a program and does not reach this gate.\n CGNAT (100.64/10) is NOT in the always-blocked set: it is reachable\n until Internal addresses is on, and an exemption can release it."
86976
- )
86977
- );
86978
- }
86979
- function showStatus() {
86980
- const cfg = getConfig();
86981
- const e = cfg.policy.egress;
86982
- const state = !e.enabled ? chalk36.red("OFF \u2014 your agent can reach any host, except the protected ones below") : e.mode === "block" ? chalk36.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk36.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
86983
- console.log(chalk36.cyan.bold("\n\u{1F310} Egress control"));
86984
- console.log(" State: " + state);
86985
- if (cfg.policySource === "workspace") {
86986
- console.log(
86987
- chalk36.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
86988
- );
86989
- }
86990
- console.log(
86991
- chalk36.gray(
86992
- ` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
86993
- )
86994
- );
86995
- showFloor(e, cfg.ssrfStrictSource);
86996
- if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
86997
- if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
86998
- if (!e.enabled) {
86999
- console.log(chalk36.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
87000
- console.log(chalk36.gray(" node9 egress lock (hard-block unknown hosts)"));
87001
- }
87002
- console.log("");
87003
- }
87004
- function registerEgressCommand(program2) {
87005
- const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
87006
- egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
87007
- if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
87008
- console.log(chalk36.green("\n\u2713 Egress is now watched (review mode)."));
87009
- console.log(
87010
- chalk36.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
87011
- );
87012
- console.log(
87013
- chalk36.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
87014
- );
87015
- });
87016
- egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
87017
- if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
87018
- console.log(chalk36.green("\n\u2713 Egress is now locked (block mode)."));
87019
- console.log(chalk36.gray(" Routine hosts are still allowed; unknown hosts are denied."));
87020
- console.log(chalk36.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
87021
- });
87022
- egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
87023
- if (!addHost("allow", host2)) return;
87024
- console.log(chalk36.green(`
87025
- \u2713 Allowed egress to ${host2}.
87026
- `));
87027
- });
87028
- egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
87029
- if (!addHost("deny", host2)) return;
87030
- console.log(chalk36.green(`
87031
- \u2713 Denied egress to ${host2}.
87032
- `));
87033
- });
87034
- egress.command("off").description("Turn egress control off").action(() => {
87035
- if (!mutate("egress off", { enabled: false })) return;
87036
- console.log(
87037
- chalk36.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
87038
- );
87039
- });
87040
- egress.command("internal <allowed|listed|blocked>").description(
87041
- "How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
87042
- ).action((value) => setInternal(value));
87043
- egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
87044
- const v = value.trim().toLowerCase();
87045
- if (v !== "on" && v !== "off") {
87046
- console.error(chalk36.red(`
87047
- \u2717 Expected "on" or "off", got "${value}".
87048
- `));
87049
- process.exitCode = 1;
87050
- return;
87051
- }
87052
- const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
87053
- console.log(chalk36.gray(`
87054
- (\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
87055
- setInternal(state);
87056
- });
87057
- egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
87058
- const a = normalizeEgressHost(address);
87059
- const slash = a.indexOf("/");
87060
- const base2 = slash === -1 ? a : a.slice(0, slash);
87061
- const prefixText = slash === -1 ? null : a.slice(slash + 1);
87062
- if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
87063
- console.error(
87064
- chalk36.red(`
87065
- \u2717 "${address}" is not an address or a range.`) + chalk36.gray(
87066
- "\n Exemptions are matched as an address (10.0.0.5) or a CIDR\n range (100.64.0.0/10), never a name.\n"
87067
- )
87068
- );
87069
- process.exitCode = 1;
87070
- return;
87071
- }
87072
- const m = classifySsrf(base2);
87073
- if (m && !m.overridable) {
87074
- console.error(
87075
- chalk36.red(`
87076
- \u2717 ${a} cannot be exempted.`) + chalk36.gray(
87077
- `
87078
- ${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
87079
- `
87080
- )
87081
- );
87082
- process.exitCode = 1;
87083
- return;
87084
- }
87085
- if (!exempt2(a)) return;
87086
- console.log(chalk36.green(`
87087
- \u2713 ${a} is exempt from the floor.`));
87088
- if (!m)
87089
- console.log(
87090
- chalk36.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
87091
- );
87092
- else console.log("");
87093
- });
87094
- egress.command("status").description("Show the current egress state").action(showStatus);
87095
- egress.action(showStatus);
87096
- }
87097
-
87098
87224
  // src/cli/commands/checks.ts
87099
87225
  init_dist();
87100
87226
  init_config();
@@ -87135,7 +87261,9 @@ function renderChecks(report) {
87135
87261
  const lines = [];
87136
87262
  const where = report.policySource === "workspace" ? "workspace (app.node9.ai)" : "local (~/.node9/config.json)";
87137
87263
  lines.push("");
87138
- lines.push(`${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${report.mode}`);
87264
+ lines.push(
87265
+ `${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${levelOf(report.mode)}`
87266
+ );
87139
87267
  lines.push("");
87140
87268
  const row = (c) => {
87141
87269
  const value = (VALUE_COLOR[c.value] ?? chalk37.white)(c.value.padEnd(7));