@node9/proxy 2.43.1 → 2.45.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
@@ -32029,16 +32029,124 @@ var init_credentials = __esm({
32029
32029
  }
32030
32030
  });
32031
32031
 
32032
- // src/daemon/service.ts
32032
+ // src/daemon/startup-log.ts
32033
32033
  import fs38 from "fs";
32034
32034
  import path39 from "path";
32035
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";
32036
32144
  import { spawnSync as spawnSync2, execFileSync } from "child_process";
32037
32145
  function resolveNode9Binary() {
32038
32146
  try {
32039
32147
  const script = process.argv[1];
32040
- if (typeof script === "string" && path39.isAbsolute(script) && fs38.existsSync(script)) {
32041
- return fs38.realpathSync(script);
32148
+ if (typeof script === "string" && path40.isAbsolute(script) && fs39.existsSync(script)) {
32149
+ return fs39.realpathSync(script);
32042
32150
  }
32043
32151
  } catch {
32044
32152
  }
@@ -32062,11 +32170,11 @@ function daemonServiceEnv() {
32062
32170
  return env;
32063
32171
  }
32064
32172
  function launchdPlist(binaryPath) {
32065
- const logDir = path39.join(os32.homedir(), ".node9");
32173
+ const logDir = path40.join(os33.homedir(), ".node9");
32066
32174
  const nodePath = xmlEscape(process.execPath);
32067
32175
  const scriptPath = xmlEscape(binaryPath);
32068
- const outLog = xmlEscape(path39.join(logDir, "daemon.log"));
32069
- 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"));
32070
32178
  const envEntries = Object.entries(daemonServiceEnv()).map(([k, v]) => ` <key>${xmlEscape(k)}</key>
32071
32179
  <string>${xmlEscape(v)}</string>`).join("\n");
32072
32180
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -32100,9 +32208,9 @@ ${envEntries}
32100
32208
  `;
32101
32209
  }
32102
32210
  function installLaunchd(binaryPath) {
32103
- const dir2 = path39.dirname(LAUNCHD_PLIST);
32104
- if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32105
- 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");
32106
32214
  spawnSync2("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
32107
32215
  const r = spawnSync2("launchctl", ["load", "-w", LAUNCHD_PLIST], {
32108
32216
  encoding: "utf8",
@@ -32113,13 +32221,13 @@ function installLaunchd(binaryPath) {
32113
32221
  }
32114
32222
  }
32115
32223
  function uninstallLaunchd() {
32116
- if (fs38.existsSync(LAUNCHD_PLIST)) {
32224
+ if (fs39.existsSync(LAUNCHD_PLIST)) {
32117
32225
  spawnSync2("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
32118
- fs38.unlinkSync(LAUNCHD_PLIST);
32226
+ fs39.unlinkSync(LAUNCHD_PLIST);
32119
32227
  }
32120
32228
  }
32121
32229
  function isLaunchdInstalled() {
32122
- return fs38.existsSync(LAUNCHD_PLIST);
32230
+ return fs39.existsSync(LAUNCHD_PLIST);
32123
32231
  }
32124
32232
  function systemdUnit(binaryPath) {
32125
32233
  const envLines = Object.entries(daemonServiceEnv()).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
@@ -32139,12 +32247,12 @@ WantedBy=default.target
32139
32247
  `;
32140
32248
  }
32141
32249
  function installSystemd(binaryPath) {
32142
- if (!fs38.existsSync(SYSTEMD_UNIT_DIR)) {
32143
- fs38.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
32250
+ if (!fs39.existsSync(SYSTEMD_UNIT_DIR)) {
32251
+ fs39.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
32144
32252
  }
32145
- fs38.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
32253
+ fs39.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
32146
32254
  try {
32147
- execFileSync("loginctl", ["enable-linger", os32.userInfo().username], { timeout: 3e3 });
32255
+ execFileSync("loginctl", ["enable-linger", os33.userInfo().username], { timeout: 3e3 });
32148
32256
  } catch {
32149
32257
  }
32150
32258
  const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
@@ -32164,21 +32272,21 @@ function installSystemd(binaryPath) {
32164
32272
  }
32165
32273
  }
32166
32274
  function uninstallSystemd() {
32167
- if (fs38.existsSync(SYSTEMD_UNIT)) {
32275
+ if (fs39.existsSync(SYSTEMD_UNIT)) {
32168
32276
  spawnSync2("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
32169
32277
  encoding: "utf8",
32170
32278
  timeout: 5e3
32171
32279
  });
32172
32280
  spawnSync2("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
32173
- fs38.unlinkSync(SYSTEMD_UNIT);
32281
+ fs39.unlinkSync(SYSTEMD_UNIT);
32174
32282
  }
32175
32283
  }
32176
32284
  function isSystemdInstalled() {
32177
- return fs38.existsSync(SYSTEMD_UNIT);
32285
+ return fs39.existsSync(SYSTEMD_UNIT);
32178
32286
  }
32179
- function windowsStartupDir(homeDir2 = os32.homedir()) {
32180
- const appData = process.env.APPDATA || path39.join(homeDir2, "AppData", "Roaming");
32181
- 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");
32182
32290
  }
32183
32291
  function windowsLauncherVbs(nodePath, scriptPath) {
32184
32292
  for (const p of [nodePath, scriptPath]) {
@@ -32198,23 +32306,23 @@ function windowsLauncherVbs(nodePath, scriptPath) {
32198
32306
  }
32199
32307
  function installWindowsStartup(binaryPath) {
32200
32308
  const target = WIN_LAUNCHER();
32201
- const dir2 = path39.dirname(target);
32202
- if (!fs38.existsSync(dir2)) fs38.mkdirSync(dir2, { recursive: true });
32203
- 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");
32204
32312
  spawnSync2("wscript.exe", ["//B", target], { encoding: "utf8", timeout: 1e4 });
32205
32313
  }
32206
32314
  function uninstallWindowsStartup() {
32207
32315
  const target = WIN_LAUNCHER();
32208
- if (fs38.existsSync(target)) fs38.unlinkSync(target);
32316
+ if (fs39.existsSync(target)) fs39.unlinkSync(target);
32209
32317
  }
32210
32318
  function isWindowsStartupInstalled() {
32211
- return fs38.existsSync(WIN_LAUNCHER());
32319
+ return fs39.existsSync(WIN_LAUNCHER());
32212
32320
  }
32213
32321
  function stopRunningDaemon() {
32214
- const pidFile = path39.join(os32.homedir(), ".node9", "daemon.pid");
32215
- if (!fs38.existsSync(pidFile)) return;
32322
+ const pidFile = path40.join(os33.homedir(), ".node9", "daemon.pid");
32323
+ if (!fs39.existsSync(pidFile)) return;
32216
32324
  try {
32217
- const data = JSON.parse(fs38.readFileSync(pidFile, "utf-8"));
32325
+ const data = JSON.parse(fs39.readFileSync(pidFile, "utf-8"));
32218
32326
  const pid = data.pid;
32219
32327
  const MAX_PID2 = 4194304;
32220
32328
  if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
@@ -32234,7 +32342,7 @@ function stopRunningDaemon() {
32234
32342
  }
32235
32343
  }
32236
32344
  try {
32237
- fs38.unlinkSync(pidFile);
32345
+ fs39.unlinkSync(pidFile);
32238
32346
  } catch {
32239
32347
  }
32240
32348
  } catch {
@@ -32327,7 +32435,7 @@ function enableDaemonServiceQuiet() {
32327
32435
  if (process.platform === "linux") {
32328
32436
  const r = spawnSync2("systemctl", ["--user", "enable", "node9-daemon"], {
32329
32437
  encoding: "utf8",
32330
- timeout: 3e3
32438
+ timeout: 1e4
32331
32439
  });
32332
32440
  return r.status === 0;
32333
32441
  }
@@ -32345,19 +32453,35 @@ function ensureAutostartHealthy(autoStartDaemon) {
32345
32453
  enabled: isDaemonServiceEnabled(),
32346
32454
  autoStartDaemon
32347
32455
  });
32348
- if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "skipped";
32456
+ if (decision === "repair") return enableDaemonServiceQuiet() ? "repaired" : "failed";
32349
32457
  return decision === "ok" ? "ok" : decision === "unsupported" ? "unsupported" : "skipped";
32350
32458
  }
32351
- 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) {
32352
32471
  if (process.env.NODE9_TESTING === "1") return;
32472
+ let failures = 0;
32353
32473
  const run = () => {
32474
+ let result = "skipped";
32354
32475
  try {
32355
- ensureAutostartHealthy(autoStartDaemon());
32476
+ result = heal(autoStartDaemon());
32356
32477
  } catch {
32357
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();
32358
32483
  };
32359
32484
  setTimeout(run, 6e4).unref();
32360
- setInterval(run, AUTOSTART_HEAL_INTERVAL_MS).unref();
32361
32485
  }
32362
32486
  function restartDaemonService() {
32363
32487
  try {
@@ -32432,39 +32556,41 @@ function autostartState() {
32432
32556
  if (!isDaemonServiceInstalled()) return "absent";
32433
32557
  return isDaemonServiceEnabled() ? "enabled" : "disabled";
32434
32558
  }
32435
- 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;
32436
32560
  var init_service = __esm({
32437
32561
  "src/daemon/service.ts"() {
32438
32562
  "use strict";
32563
+ init_startup_log();
32439
32564
  init_api_url();
32440
32565
  LAUNCHD_LABEL = "ai.node9.daemon";
32441
- LAUNCHD_PLIST = path39.join(os32.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
32442
- SYSTEMD_UNIT_DIR = path39.join(os32.homedir(), ".config", "systemd", "user");
32443
- 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");
32444
32569
  STARTUP_FILE = "node9-daemon.vbs";
32445
- WIN_LAUNCHER = () => path39.join(windowsStartupDir(), STARTUP_FILE);
32570
+ WIN_LAUNCHER = () => path40.join(windowsStartupDir(), STARTUP_FILE);
32446
32571
  AUTOSTART_HEAL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
32572
+ AUTOSTART_HEAL_RETRY_MS = [10 * 60 * 1e3, 60 * 60 * 1e3];
32447
32573
  }
32448
32574
  });
32449
32575
 
32450
32576
  // src/apps/detect.ts
32451
- import fs39 from "fs";
32452
- import os33 from "os";
32453
- import path40 from "path";
32577
+ import fs40 from "fs";
32578
+ import os34 from "os";
32579
+ import path41 from "path";
32454
32580
  function readTail(file2, bytes) {
32455
32581
  let fd;
32456
32582
  try {
32457
- fd = fs39.openSync(file2, "r");
32458
- const size = fs39.fstatSync(fd).size;
32583
+ fd = fs40.openSync(file2, "r");
32584
+ const size = fs40.fstatSync(fd).size;
32459
32585
  const start = Math.max(0, size - bytes);
32460
32586
  const buf = Buffer.alloc(size - start);
32461
- fs39.readSync(fd, buf, 0, buf.length, start);
32587
+ fs40.readSync(fd, buf, 0, buf.length, start);
32462
32588
  const text = buf.toString("utf8");
32463
32589
  return start > 0 ? text.slice(text.indexOf("\n") + 1) : text;
32464
32590
  } catch {
32465
32591
  return "";
32466
32592
  } finally {
32467
- if (fd !== void 0) fs39.closeSync(fd);
32593
+ if (fd !== void 0) fs40.closeSync(fd);
32468
32594
  }
32469
32595
  }
32470
32596
  function programsOf(command) {
@@ -32478,7 +32604,7 @@ function programsOf(command) {
32478
32604
  i++;
32479
32605
  continue;
32480
32606
  }
32481
- const base2 = path40.basename(w.replace(/^["']|["']$/g, ""));
32607
+ const base2 = path41.basename(w.replace(/^["']|["']$/g, ""));
32482
32608
  if (WRAPPERS.has(base2)) {
32483
32609
  i++;
32484
32610
  continue;
@@ -32513,7 +32639,7 @@ function observeAudit(auditFile) {
32513
32639
  for (const p of programsOf(row.argsPreview)) commands.add(p);
32514
32640
  }
32515
32641
  const dir2 = typeof row.workingDir === "string" ? row.workingDir : "";
32516
- 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) {
32517
32643
  seenDirs.add(dir2);
32518
32644
  dirs.push(dir2);
32519
32645
  }
@@ -32523,21 +32649,21 @@ function observeAudit(auditFile) {
32523
32649
  function readSmall(file2) {
32524
32650
  let fd;
32525
32651
  try {
32526
- fd = fs39.openSync(file2, READ_FLAGS);
32527
- const st = fs39.fstatSync(fd);
32652
+ fd = fs40.openSync(file2, READ_FLAGS);
32653
+ const st = fs40.fstatSync(fd);
32528
32654
  if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
32529
32655
  const buf = Buffer.alloc(st.size);
32530
- const n = fs39.readSync(fd, buf, 0, st.size, 0);
32656
+ const n = fs40.readSync(fd, buf, 0, st.size, 0);
32531
32657
  return buf.toString("utf8", 0, n);
32532
32658
  } catch {
32533
32659
  return null;
32534
32660
  } finally {
32535
- if (fd !== void 0) fs39.closeSync(fd);
32661
+ if (fd !== void 0) fs40.closeSync(fd);
32536
32662
  }
32537
32663
  }
32538
32664
  function projectPackages(dir2) {
32539
32665
  const out = /* @__PURE__ */ new Set();
32540
- const pkg = readSmall(path40.join(dir2, "package.json"));
32666
+ const pkg = readSmall(path41.join(dir2, "package.json"));
32541
32667
  if (pkg) {
32542
32668
  try {
32543
32669
  const j = JSON.parse(pkg);
@@ -32554,7 +32680,7 @@ function projectPackages(dir2) {
32554
32680
  } catch {
32555
32681
  }
32556
32682
  }
32557
- const req = readSmall(path40.join(dir2, "requirements.txt"));
32683
+ const req = readSmall(path41.join(dir2, "requirements.txt"));
32558
32684
  if (req) {
32559
32685
  for (const line of req.split("\n")) {
32560
32686
  const m = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line);
@@ -32562,7 +32688,7 @@ function projectPackages(dir2) {
32562
32688
  out.add(m[1].toLowerCase());
32563
32689
  }
32564
32690
  }
32565
- const py = readSmall(path40.join(dir2, "pyproject.toml"));
32691
+ const py = readSmall(path41.join(dir2, "pyproject.toml"));
32566
32692
  if (py) {
32567
32693
  for (const m of py.matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)\s*(?:[<>=!~;[\]]|["'])/g))
32568
32694
  out.add(m[1].toLowerCase());
@@ -32582,7 +32708,7 @@ function projectPackages(dir2) {
32582
32708
  function projectEnvNames(dir2) {
32583
32709
  const out = /* @__PURE__ */ new Set();
32584
32710
  for (const f of [".env", ".env.local", ".env.development"]) {
32585
- const text = readSmall(path40.join(dir2, f));
32711
+ const text = readSmall(path41.join(dir2, f));
32586
32712
  if (!text) continue;
32587
32713
  for (const line of text.split("\n")) {
32588
32714
  const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
@@ -32618,14 +32744,14 @@ function matchApps(obs, projectFacts, catalog = APP_CATALOG) {
32618
32744
  }
32619
32745
  return found;
32620
32746
  }
32621
- function detectApps(home = os33.homedir()) {
32622
- 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"));
32623
32749
  for (const cfg of Object.values(readMcpToolsConfig())) {
32624
32750
  if (cfg?.name) obs.servers.add(cfg.name.toLowerCase());
32625
32751
  }
32626
32752
  const facts = obs.projects.filter((dir2) => {
32627
32753
  try {
32628
- return fs39.statSync(dir2).isDirectory();
32754
+ return fs40.statSync(dir2).isDirectory();
32629
32755
  } catch {
32630
32756
  return false;
32631
32757
  }
@@ -32660,7 +32786,7 @@ var init_detect = __esm({
32660
32786
  "pnpx",
32661
32787
  "bunx"
32662
32788
  ]);
32663
- 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;
32664
32790
  }
32665
32791
  });
32666
32792
 
@@ -32720,9 +32846,9 @@ var init_daemon2 = __esm({
32720
32846
  });
32721
32847
 
32722
32848
  // src/config/migrate.ts
32723
- import fs40 from "fs";
32724
- import path41 from "path";
32725
- import os34 from "os";
32849
+ import fs41 from "fs";
32850
+ import path42 from "path";
32851
+ import os35 from "os";
32726
32852
  function backupPath(filePath) {
32727
32853
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
32728
32854
  return `${filePath}.bak-${stamp}`;
@@ -32732,7 +32858,7 @@ function migrateConfigFile(options = {}) {
32732
32858
  if (options.dryRun) {
32733
32859
  let raw;
32734
32860
  try {
32735
- raw = JSON.parse(fs40.readFileSync(filePath, "utf8"));
32861
+ raw = JSON.parse(fs41.readFileSync(filePath, "utf8"));
32736
32862
  } catch (err2) {
32737
32863
  if (err2.code === "ENOENT") return { status: "no-file" };
32738
32864
  return { status: "failed", error: `cannot read ${filePath}: ${err2.message}` };
@@ -32755,14 +32881,14 @@ function migrateConfigFile(options = {}) {
32755
32881
  }
32756
32882
  }
32757
32883
  function undoMigration(filePath = globalConfigPath()) {
32758
- const dir2 = path41.dirname(filePath);
32759
- const base2 = path41.basename(filePath);
32760
- 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();
32761
32887
  const newest = backups.at(-1);
32762
32888
  if (!newest) return null;
32763
- const from = path41.join(dir2, newest);
32764
- atomicWriteSync(filePath, fs40.readFileSync(from, "utf8"), { mode: 384 });
32765
- fs40.unlinkSync(from);
32889
+ const from = path42.join(dir2, newest);
32890
+ atomicWriteSync(filePath, fs41.readFileSync(from, "utf8"), { mode: 384 });
32891
+ fs41.unlinkSync(from);
32766
32892
  return { restored: from };
32767
32893
  }
32768
32894
  function autoMigrateLocalConfig() {
@@ -32780,8 +32906,8 @@ function autoMigrateLocalConfig() {
32780
32906
  );
32781
32907
  } else if (outcome.status === "failed") {
32782
32908
  try {
32783
- fs40.appendFileSync(
32784
- path41.join(os34.homedir(), ".node9", "hook-debug.log"),
32909
+ fs41.appendFileSync(
32910
+ path42.join(os35.homedir(), ".node9", "hook-debug.log"),
32785
32911
  `[${(/* @__PURE__ */ new Date()).toISOString()}] config migrate failed: ${outcome.error}
32786
32912
  `
32787
32913
  );
@@ -33022,18 +33148,18 @@ var init_forecast = __esm({
33022
33148
  });
33023
33149
 
33024
33150
  // src/version.ts
33025
- import fs41 from "fs";
33026
- import path42 from "path";
33151
+ import fs42 from "fs";
33152
+ import path43 from "path";
33027
33153
  function readVersion() {
33028
33154
  let dir2 = __dirname;
33029
33155
  for (let i = 0; i < 5; i++) {
33030
- const p = path42.join(dir2, "package.json");
33156
+ const p = path43.join(dir2, "package.json");
33031
33157
  try {
33032
- const pkg = JSON.parse(fs41.readFileSync(p, "utf-8"));
33158
+ const pkg = JSON.parse(fs42.readFileSync(p, "utf-8"));
33033
33159
  if (pkg.name === "@node9/proxy" && typeof pkg.version === "string") return pkg.version;
33034
33160
  } catch {
33035
33161
  }
33036
- const parent = path42.dirname(dir2);
33162
+ const parent = path43.dirname(dir2);
33037
33163
  if (parent === dir2) break;
33038
33164
  dir2 = parent;
33039
33165
  }
@@ -33048,15 +33174,15 @@ var init_version = __esm({
33048
33174
  });
33049
33175
 
33050
33176
  // src/cli/commands/scan-cache.ts
33051
- import fs42 from "fs";
33052
- import os35 from "os";
33053
- import path43 from "path";
33177
+ import fs43 from "fs";
33178
+ import os36 from "os";
33179
+ import path44 from "path";
33054
33180
  import { createHash as createHash6 } from "crypto";
33055
33181
  function flushScanCaches() {
33056
33182
  for (const c of activeCaches) c.flush();
33057
33183
  }
33058
33184
  function scanCacheRoot() {
33059
- return path43.join(os35.homedir(), ".node9", "scan-cache");
33185
+ return path44.join(os36.homedir(), ".node9", "scan-cache");
33060
33186
  }
33061
33187
  function configHashOf(config) {
33062
33188
  const h = createHash6("sha256");
@@ -33064,14 +33190,14 @@ function configHashOf(config) {
33064
33190
  h.update(JSON.stringify(config.policySource));
33065
33191
  h.update(JSON.stringify(config.policy));
33066
33192
  for (const name of SIDECAR_FILES) {
33067
- const st = fileStamp(path43.join(os35.homedir(), ".node9", name));
33193
+ const st = fileStamp(path44.join(os36.homedir(), ".node9", name));
33068
33194
  h.update(`|${name}:${st ? `${st.size}:${st.mtimeMs}` : "absent"}`);
33069
33195
  }
33070
33196
  return h.digest("hex").slice(0, 16);
33071
33197
  }
33072
33198
  function fileStamp(filePath) {
33073
33199
  try {
33074
- const st = fs42.statSync(filePath);
33200
+ const st = fs43.statSync(filePath);
33075
33201
  return { size: st.size, mtimeMs: st.mtimeMs };
33076
33202
  } catch {
33077
33203
  return null;
@@ -33095,8 +33221,8 @@ var init_scan_cache = __esm({
33095
33221
  "trusted-hosts.json",
33096
33222
  "trusted-recipients.json",
33097
33223
  "rules-cache.json",
33098
- path43.join("osv", "npm", "meta.json"),
33099
- path43.join("osv", "PyPI", "meta.json")
33224
+ path44.join("osv", "npm", "meta.json"),
33225
+ path44.join("osv", "PyPI", "meta.json")
33100
33226
  ];
33101
33227
  ScanCache = class {
33102
33228
  root;
@@ -33125,7 +33251,7 @@ var init_scan_cache = __esm({
33125
33251
  if (!this.enabled) return fresh;
33126
33252
  try {
33127
33253
  const raw = JSON.parse(
33128
- fs42.readFileSync(path43.join(this.root, "index.json"), "utf8")
33254
+ fs43.readFileSync(path44.join(this.root, "index.json"), "utf8")
33129
33255
  );
33130
33256
  if (raw.cacheVersion !== SCAN_CACHE_VERSION || raw.engineVersion !== CANONICAL_EXTRACTOR_VERSION || raw.proxyVersion !== PROXY_VERSION || raw.configHash !== configHash || typeof raw.files !== "object") {
33131
33257
  this.dropAll();
@@ -33138,7 +33264,7 @@ var init_scan_cache = __esm({
33138
33264
  }
33139
33265
  dropAll() {
33140
33266
  try {
33141
- fs42.rmSync(this.root, { recursive: true, force: true });
33267
+ fs43.rmSync(this.root, { recursive: true, force: true });
33142
33268
  } catch {
33143
33269
  }
33144
33270
  }
@@ -33162,7 +33288,7 @@ var init_scan_cache = __esm({
33162
33288
  }
33163
33289
  }
33164
33290
  try {
33165
- 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"));
33166
33292
  if ((stored.judgedFrom ?? null) !== (e.judgedFrom ?? null) || !stored.result) {
33167
33293
  this.misses++;
33168
33294
  return null;
@@ -33177,14 +33303,14 @@ var init_scan_cache = __esm({
33177
33303
  put(filePath, stamp, agent, result, hasher, judgeFrom = null) {
33178
33304
  if (!this.enabled) return;
33179
33305
  const name = createHash6("sha256").update(`${agent}|${filePath}`).digest("hex").slice(0, 40);
33180
- const rel = path43.join("files", name.slice(0, 2), `${name}.json`);
33306
+ const rel = path44.join("files", name.slice(0, 2), `${name}.json`);
33181
33307
  const configHashes = {};
33182
33308
  if (hasher)
33183
33309
  for (const cwd of result.configCwds ?? []) configHashes[cwd] = hasher.configHashFor(cwd);
33184
33310
  try {
33185
- const abs = path43.join(this.root, rel);
33186
- fs42.mkdirSync(path43.dirname(abs), { recursive: true, mode: 448 });
33187
- fs42.writeFileSync(
33311
+ const abs = path44.join(this.root, rel);
33312
+ fs43.mkdirSync(path44.dirname(abs), { recursive: true, mode: 448 });
33313
+ fs43.writeFileSync(
33188
33314
  abs,
33189
33315
  JSON.stringify({ judgedFrom: judgeFrom ? judgeFrom.toISOString() : null, result }),
33190
33316
  { mode: 384 }
@@ -33207,9 +33333,9 @@ var init_scan_cache = __esm({
33207
33333
  /** Drop the entries of transcripts that no longer exist, and their files. */
33208
33334
  prune() {
33209
33335
  for (const [key, e] of Object.entries(this.index.files)) {
33210
- if (fs42.existsSync(key)) continue;
33336
+ if (fs43.existsSync(key)) continue;
33211
33337
  try {
33212
- fs42.rmSync(path43.join(this.root, e.result), { force: true });
33338
+ fs43.rmSync(path44.join(this.root, e.result), { force: true });
33213
33339
  } catch {
33214
33340
  }
33215
33341
  delete this.index.files[key];
@@ -33230,10 +33356,10 @@ var init_scan_cache = __esm({
33230
33356
  this.lastFlush = Date.now();
33231
33357
  if (!this.dirty) return;
33232
33358
  try {
33233
- fs42.mkdirSync(this.root, { recursive: true, mode: 448 });
33234
- const tmp = path43.join(this.root, `index.${process.pid}.tmp`);
33235
- fs42.writeFileSync(tmp, JSON.stringify(this.index), { mode: 384 });
33236
- 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"));
33237
33363
  this.dirty = false;
33238
33364
  } catch {
33239
33365
  }
@@ -33243,9 +33369,9 @@ var init_scan_cache = __esm({
33243
33369
  });
33244
33370
 
33245
33371
  // src/cli/commands/scan-judge.ts
33246
- import fs43 from "fs";
33247
- import os36 from "os";
33248
- import path44 from "path";
33372
+ import fs44 from "fs";
33373
+ import os37 from "os";
33374
+ import path45 from "path";
33249
33375
  function findingDedupKey(f) {
33250
33376
  return `${f.source.rule.name ?? "<unnamed>"}|${preview(f.input)}|${f.project}|${f.repo ?? ""}`;
33251
33377
  }
@@ -33344,24 +33470,24 @@ var init_scan_judge = __esm({
33344
33470
  * file, so worktrees count), home-relative; undefined when none. Cached per
33345
33471
  * directory: history repeats the same few cwds thousands of times. */
33346
33472
  repoOf(cwd) {
33347
- if (!cwd || !path44.isAbsolute(cwd)) return void 0;
33473
+ if (!cwd || !path45.isAbsolute(cwd)) return void 0;
33348
33474
  if (this.repos.has(cwd)) return this.repos.get(cwd);
33349
33475
  let dir2 = cwd;
33350
33476
  let found;
33351
33477
  for (; ; ) {
33352
33478
  try {
33353
- if (fs43.existsSync(path44.join(dir2, ".git"))) {
33479
+ if (fs44.existsSync(path45.join(dir2, ".git"))) {
33354
33480
  found = dir2;
33355
33481
  break;
33356
33482
  }
33357
33483
  } catch {
33358
33484
  break;
33359
33485
  }
33360
- const parent = path44.dirname(dir2);
33486
+ const parent = path45.dirname(dir2);
33361
33487
  if (parent === dir2) break;
33362
33488
  dir2 = parent;
33363
33489
  }
33364
- const home = os36.homedir();
33490
+ const home = os37.homedir();
33365
33491
  const label3 = found && found.startsWith(home) ? "~" + found.slice(home.length) : found;
33366
33492
  this.repos.set(cwd, label3);
33367
33493
  return label3;
@@ -33506,15 +33632,15 @@ var init_scan_judge = __esm({
33506
33632
  });
33507
33633
 
33508
33634
  // src/pricing/litellm.ts
33509
- import fs44 from "fs";
33510
- import path45 from "path";
33511
- import os37 from "os";
33635
+ import fs45 from "fs";
33636
+ import path46 from "path";
33637
+ import os38 from "os";
33512
33638
  function normalizeModel(raw) {
33513
33639
  return raw.replace(/-\d{8}$/, "").toLowerCase();
33514
33640
  }
33515
33641
  function readCache(opts) {
33516
33642
  try {
33517
- const raw = JSON.parse(fs44.readFileSync(CACHE_FILE(), "utf-8"));
33643
+ const raw = JSON.parse(fs45.readFileSync(CACHE_FILE(), "utf-8"));
33518
33644
  if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
33519
33645
  return null;
33520
33646
  }
@@ -33529,18 +33655,18 @@ function readCache(opts) {
33529
33655
  function writeCache(prices) {
33530
33656
  try {
33531
33657
  const target = CACHE_FILE();
33532
- const dir2 = path45.dirname(target);
33533
- 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 });
33534
33660
  const tmp = target + ".tmp";
33535
33661
  const body = {
33536
33662
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
33537
33663
  prices
33538
33664
  };
33539
- fs44.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
33540
- fs44.renameSync(tmp, target);
33665
+ fs45.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
33666
+ fs45.renameSync(tmp, target);
33541
33667
  } catch (err2) {
33542
33668
  try {
33543
- fs44.appendFileSync(
33669
+ fs45.appendFileSync(
33544
33670
  HOOK_DEBUG_LOG,
33545
33671
  `[pricing] cache write failed: ${err2.message}
33546
33672
  `
@@ -33698,7 +33824,7 @@ var init_litellm = __esm({
33698
33824
  "gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
33699
33825
  "gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
33700
33826
  };
33701
- CACHE_FILE = () => path45.join(os37.homedir(), ".node9", "model-pricing.json");
33827
+ CACHE_FILE = () => path46.join(os38.homedir(), ".node9", "model-pricing.json");
33702
33828
  TTL_MS2 = 24 * 60 * 60 * 1e3;
33703
33829
  memCache = null;
33704
33830
  memCacheAt = 0;
@@ -33708,11 +33834,11 @@ var init_litellm = __esm({
33708
33834
  });
33709
33835
 
33710
33836
  // src/cost-codex.ts
33711
- import fs45 from "fs";
33712
- import os38 from "os";
33713
- import path46 from "path";
33837
+ import fs46 from "fs";
33838
+ import os39 from "os";
33839
+ import path47 from "path";
33714
33840
  function codexSessionsDir() {
33715
- 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");
33716
33842
  }
33717
33843
  function codexPriceFor(model) {
33718
33844
  return pricingFor(codexModel(model), { exact: true }) ?? CODEX_FALLBACK;
@@ -33744,14 +33870,14 @@ function codexSessionCost(model, tokens, request2) {
33744
33870
  function statAndFirstLine(file2) {
33745
33871
  const CAP = 4 * 1024 * 1024;
33746
33872
  const CHUNK = 64 * 1024;
33747
- const fd = fs45.openSync(file2, "r");
33873
+ const fd = fs46.openSync(file2, "r");
33748
33874
  try {
33749
- const stat = fs45.fstatSync(fd);
33875
+ const stat = fs46.fstatSync(fd);
33750
33876
  const limit = Math.min(stat.size, CAP);
33751
33877
  const parts = [];
33752
33878
  for (let pos = 0; pos < limit; pos += CHUNK) {
33753
33879
  const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
33754
- const read3 = fs45.readSync(fd, buf, 0, buf.length, pos);
33880
+ const read3 = fs46.readSync(fd, buf, 0, buf.length, pos);
33755
33881
  if (read3 <= 0) break;
33756
33882
  const slice = buf.subarray(0, read3);
33757
33883
  const nl = slice.indexOf(10);
@@ -33760,15 +33886,15 @@ function statAndFirstLine(file2) {
33760
33886
  }
33761
33887
  return { stat, first: Buffer.concat(parts).toString("utf8") };
33762
33888
  } finally {
33763
- fs45.closeSync(fd);
33889
+ fs46.closeSync(fd);
33764
33890
  }
33765
33891
  }
33766
33892
  function listCodexSessionFiles(base2 = codexSessionsDir()) {
33767
33893
  const files = [];
33768
33894
  const walk2 = (dir2) => {
33769
33895
  try {
33770
- for (const entry of fs45.readdirSync(dir2, { withFileTypes: true })) {
33771
- 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);
33772
33898
  if (entry.isDirectory()) walk2(file2);
33773
33899
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file2);
33774
33900
  }
@@ -33776,7 +33902,7 @@ function listCodexSessionFiles(base2 = codexSessionsDir()) {
33776
33902
  }
33777
33903
  };
33778
33904
  walk2(base2);
33779
- 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"));
33780
33906
  const sessions = /* @__PURE__ */ new Map();
33781
33907
  for (const file2 of files.sort()) {
33782
33908
  try {
@@ -33974,13 +34100,13 @@ var init_cost_codex = __esm({
33974
34100
  CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
33975
34101
  codexSource = {
33976
34102
  id: "codex",
33977
- 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")),
33978
34104
  collect(sinceMs) {
33979
34105
  const entries = [];
33980
34106
  for (const file2 of listCodexSessionFiles()) {
33981
34107
  try {
33982
- if (sinceMs !== void 0 && fs45.statSync(file2).mtimeMs < sinceMs) continue;
33983
- 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")));
33984
34110
  } catch {
33985
34111
  }
33986
34112
  }
@@ -33991,11 +34117,11 @@ var init_cost_codex = __esm({
33991
34117
  });
33992
34118
 
33993
34119
  // src/cost-gemini.ts
33994
- import fs46 from "fs";
33995
- import os39 from "os";
33996
- import path47 from "path";
34120
+ import fs47 from "fs";
34121
+ import os40 from "os";
34122
+ import path48 from "path";
33997
34123
  function geminiTmpDir() {
33998
- return path47.join(os39.homedir(), ".gemini", "tmp");
34124
+ return path48.join(os40.homedir(), ".gemini", "tmp");
33999
34125
  }
34000
34126
  function geminiPriceFor(model) {
34001
34127
  let tuple = pricingFor(model);
@@ -34010,14 +34136,14 @@ function geminiPriceFor(model) {
34010
34136
  }
34011
34137
  function safeReaddir(dir2) {
34012
34138
  try {
34013
- return fs46.readdirSync(dir2);
34139
+ return fs47.readdirSync(dir2);
34014
34140
  } catch {
34015
34141
  return [];
34016
34142
  }
34017
34143
  }
34018
34144
  function isDir(p) {
34019
34145
  try {
34020
- return fs46.statSync(p).isDirectory();
34146
+ return fs47.statSync(p).isDirectory();
34021
34147
  } catch {
34022
34148
  return false;
34023
34149
  }
@@ -34025,11 +34151,11 @@ function isDir(p) {
34025
34151
  function listGeminiSessionFiles(base2) {
34026
34152
  const out = [];
34027
34153
  for (const project of safeReaddir(base2)) {
34028
- const chats = path47.join(base2, project, "chats");
34154
+ const chats = path48.join(base2, project, "chats");
34029
34155
  if (!isDir(chats)) continue;
34030
34156
  for (const f of safeReaddir(chats)) {
34031
34157
  if (f.startsWith("session-") && f.endsWith(".jsonl")) {
34032
- out.push({ file: path47.join(chats, f), project });
34158
+ out.push({ file: path48.join(chats, f), project });
34033
34159
  }
34034
34160
  }
34035
34161
  }
@@ -34096,7 +34222,7 @@ var init_cost_gemini = __esm({
34096
34222
  id: "gemini",
34097
34223
  available() {
34098
34224
  try {
34099
- return fs46.existsSync(geminiTmpDir());
34225
+ return fs47.existsSync(geminiTmpDir());
34100
34226
  } catch {
34101
34227
  return false;
34102
34228
  }
@@ -34105,13 +34231,13 @@ var init_cost_gemini = __esm({
34105
34231
  const combined2 = /* @__PURE__ */ new Map();
34106
34232
  for (const { file: file2, project } of listGeminiSessionFiles(geminiTmpDir())) {
34107
34233
  try {
34108
- if (sinceMs !== void 0 && fs46.statSync(file2).mtimeMs < sinceMs) continue;
34234
+ if (sinceMs !== void 0 && fs47.statSync(file2).mtimeMs < sinceMs) continue;
34109
34235
  } catch {
34110
34236
  continue;
34111
34237
  }
34112
34238
  let content;
34113
34239
  try {
34114
- content = fs46.readFileSync(file2, "utf8");
34240
+ content = fs47.readFileSync(file2, "utf8");
34115
34241
  } catch {
34116
34242
  continue;
34117
34243
  }
@@ -34136,15 +34262,15 @@ var init_cost_gemini = __esm({
34136
34262
  });
34137
34263
 
34138
34264
  // src/cost-copilot.ts
34139
- import fs47 from "fs";
34140
- import os40 from "os";
34141
- import path48 from "path";
34265
+ import fs48 from "fs";
34266
+ import os41 from "os";
34267
+ import path49 from "path";
34142
34268
  function copilotSessionsDir() {
34143
- return path48.join(os40.homedir(), ".copilot", "session-state");
34269
+ return path49.join(os41.homedir(), ".copilot", "session-state");
34144
34270
  }
34145
34271
  function safeReaddir2(dir2) {
34146
34272
  try {
34147
- return fs47.readdirSync(dir2);
34273
+ return fs48.readdirSync(dir2);
34148
34274
  } catch {
34149
34275
  return [];
34150
34276
  }
@@ -34220,7 +34346,7 @@ var init_cost_copilot = __esm({
34220
34346
  id: "copilot",
34221
34347
  available() {
34222
34348
  try {
34223
- return fs47.existsSync(copilotSessionsDir());
34349
+ return fs48.existsSync(copilotSessionsDir());
34224
34350
  } catch {
34225
34351
  return false;
34226
34352
  }
@@ -34229,15 +34355,15 @@ var init_cost_copilot = __esm({
34229
34355
  const base2 = copilotSessionsDir();
34230
34356
  const combined2 = /* @__PURE__ */ new Map();
34231
34357
  for (const sid of safeReaddir2(base2)) {
34232
- const file2 = path48.join(base2, sid, "events.jsonl");
34358
+ const file2 = path49.join(base2, sid, "events.jsonl");
34233
34359
  try {
34234
- if (sinceMs !== void 0 && fs47.statSync(file2).mtimeMs < sinceMs) continue;
34360
+ if (sinceMs !== void 0 && fs48.statSync(file2).mtimeMs < sinceMs) continue;
34235
34361
  } catch {
34236
34362
  continue;
34237
34363
  }
34238
34364
  let content;
34239
34365
  try {
34240
- content = fs47.readFileSync(file2, "utf8");
34366
+ content = fs48.readFileSync(file2, "utf8");
34241
34367
  } catch {
34242
34368
  continue;
34243
34369
  }
@@ -34262,21 +34388,21 @@ var init_cost_copilot = __esm({
34262
34388
  });
34263
34389
 
34264
34390
  // src/session-files.ts
34265
- import * as fs48 from "fs";
34266
- import * as path49 from "path";
34391
+ import * as fs49 from "fs";
34392
+ import * as path50 from "path";
34267
34393
  function listSessionFiles(dir2, maxDepth = 6) {
34268
34394
  const out = [];
34269
34395
  const walk2 = (d, rel, depth) => {
34270
34396
  if (depth > maxDepth) return;
34271
34397
  let entries;
34272
34398
  try {
34273
- entries = fs48.readdirSync(d, { withFileTypes: true });
34399
+ entries = fs49.readdirSync(d, { withFileTypes: true });
34274
34400
  } catch {
34275
34401
  return;
34276
34402
  }
34277
34403
  for (const e of entries) {
34278
- const childRel = rel ? path49.join(rel, e.name) : e.name;
34279
- 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);
34280
34406
  else if (e.name.endsWith(".jsonl")) out.push(childRel);
34281
34407
  }
34282
34408
  };
@@ -34284,7 +34410,7 @@ function listSessionFiles(dir2, maxDepth = 6) {
34284
34410
  return out;
34285
34411
  }
34286
34412
  function sessionIdOf(relPath) {
34287
- return path49.basename(relPath).replace(/\.jsonl$/, "");
34413
+ return path50.basename(relPath).replace(/\.jsonl$/, "");
34288
34414
  }
34289
34415
  var init_session_files = __esm({
34290
34416
  "src/session-files.ts"() {
@@ -34293,17 +34419,17 @@ var init_session_files = __esm({
34293
34419
  });
34294
34420
 
34295
34421
  // src/costSync.ts
34296
- import fs49 from "fs";
34297
- import path50 from "path";
34298
- import os41 from "os";
34422
+ import fs50 from "fs";
34423
+ import path51 from "path";
34424
+ import os42 from "os";
34299
34425
  function decodeProjectDirName(dirName) {
34300
34426
  return dirName.replace(/-/g, "/");
34301
34427
  }
34302
34428
  function parseJSONLFile(filePath, fallbackWorkingDir) {
34303
- const runId = path50.basename(filePath, ".jsonl");
34429
+ const runId = path51.basename(filePath, ".jsonl");
34304
34430
  let content;
34305
34431
  try {
34306
- content = fs49.readFileSync(filePath, "utf8");
34432
+ content = fs50.readFileSync(filePath, "utf8");
34307
34433
  } catch {
34308
34434
  return /* @__PURE__ */ new Map();
34309
34435
  }
@@ -34403,7 +34529,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
34403
34529
  signal: AbortSignal.timeout(15e3)
34404
34530
  });
34405
34531
  if (!res.ok) {
34406
- fs49.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
34532
+ fs50.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
34407
34533
  `);
34408
34534
  } else {
34409
34535
  let stored;
@@ -34425,8 +34551,8 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
34425
34551
  }
34426
34552
  function debugLog(line) {
34427
34553
  try {
34428
- fs49.mkdirSync(path50.dirname(HOOK_DEBUG_LOG), { recursive: true });
34429
- fs49.appendFileSync(HOOK_DEBUG_LOG, line);
34554
+ fs50.mkdirSync(path51.dirname(HOOK_DEBUG_LOG), { recursive: true });
34555
+ fs50.appendFileSync(HOOK_DEBUG_LOG, line);
34430
34556
  } catch (err2) {
34431
34557
  console.error(`[cost-sync] could not write ${HOOK_DEBUG_LOG}: ${safeMessage(err2)}`);
34432
34558
  }
@@ -34439,10 +34565,10 @@ async function syncCost() {
34439
34565
  if (entries.length === 0) return;
34440
34566
  let username = "unknown";
34441
34567
  try {
34442
- username = os41.userInfo().username;
34568
+ username = os42.userInfo().username;
34443
34569
  } catch {
34444
34570
  }
34445
- const machineId = `${os41.hostname()}:${username}`;
34571
+ const machineId = `${os42.hostname()}:${username}`;
34446
34572
  await postCostBatches(creds.apiUrl, creds.apiKey, machineId, entries);
34447
34573
  }
34448
34574
  function startCostSync() {
@@ -34470,22 +34596,22 @@ var init_costSync = __esm({
34470
34596
  claudeSource = {
34471
34597
  id: "claude",
34472
34598
  available() {
34473
- return fs49.existsSync(path50.join(os41.homedir(), ".claude", "projects"));
34599
+ return fs50.existsSync(path51.join(os42.homedir(), ".claude", "projects"));
34474
34600
  },
34475
34601
  collect(sinceMs) {
34476
- const projectsDir = path50.join(os41.homedir(), ".claude", "projects");
34477
- if (!fs49.existsSync(projectsDir)) return [];
34602
+ const projectsDir = path51.join(os42.homedir(), ".claude", "projects");
34603
+ if (!fs50.existsSync(projectsDir)) return [];
34478
34604
  const combined2 = /* @__PURE__ */ new Map();
34479
34605
  let dirs;
34480
34606
  try {
34481
- dirs = fs49.readdirSync(projectsDir);
34607
+ dirs = fs50.readdirSync(projectsDir);
34482
34608
  } catch {
34483
34609
  return [];
34484
34610
  }
34485
34611
  for (const dir2 of dirs) {
34486
- const dirPath = path50.join(projectsDir, dir2);
34612
+ const dirPath = path51.join(projectsDir, dir2);
34487
34613
  try {
34488
- if (!fs49.statSync(dirPath).isDirectory()) continue;
34614
+ if (!fs50.statSync(dirPath).isDirectory()) continue;
34489
34615
  } catch {
34490
34616
  continue;
34491
34617
  }
@@ -34497,10 +34623,10 @@ var init_costSync = __esm({
34497
34623
  }
34498
34624
  const fallbackWorkingDir = decodeProjectDirName(dir2);
34499
34625
  for (const file2 of files) {
34500
- const filePath = path50.join(dirPath, file2);
34626
+ const filePath = path51.join(dirPath, file2);
34501
34627
  if (sinceMs !== void 0) {
34502
34628
  try {
34503
- if (fs49.statSync(filePath).mtimeMs < sinceMs) continue;
34629
+ if (fs50.statSync(filePath).mtimeMs < sinceMs) continue;
34504
34630
  } catch {
34505
34631
  continue;
34506
34632
  }
@@ -34736,7 +34862,7 @@ function buildScanSummary(agents) {
34736
34862
  };
34737
34863
  }
34738
34864
  function wouldHaveVerb(summary, past = "would have blocked") {
34739
- 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;
34740
34866
  }
34741
34867
  function buildSections(findings) {
34742
34868
  const sectionMap = /* @__PURE__ */ new Map();
@@ -34872,10 +34998,10 @@ var init_scan_summary = __esm({
34872
34998
  });
34873
34999
 
34874
35000
  // src/credential-files.ts
34875
- import path51 from "path";
35001
+ import path52 from "path";
34876
35002
  function homeCredentialFiles(home) {
34877
35003
  const f = (parts, description, score, kind = "credential") => ({
34878
- full: path51.join(home, ...parts),
35004
+ full: path52.join(home, ...parts),
34879
35005
  label: "~/" + parts.join("/"),
34880
35006
  description,
34881
35007
  score,
@@ -34896,8 +35022,7 @@ function homeCredentialFiles(home) {
34896
35022
  ),
34897
35023
  f([".docker", "config.json"], "Docker registry auth tokens", 10),
34898
35024
  f([".netrc"], "FTP/HTTP credentials in plain text", 15),
34899
- f([".npmrc"], "npm auth token: can publish packages as you", 10),
34900
- f([".node9", "credentials.json"], "Node9 cloud API key", 10)
35025
+ f([".npmrc"], "npm auth token: can publish packages as you", 10)
34901
35026
  ];
34902
35027
  }
34903
35028
  var init_credential_files = __esm({
@@ -34908,9 +35033,9 @@ var init_credential_files = __esm({
34908
35033
 
34909
35034
  // src/cli/commands/blast.ts
34910
35035
  import chalk3 from "chalk";
34911
- import fs50 from "fs";
34912
- import path52 from "path";
34913
- import os42 from "os";
35036
+ import fs51 from "fs";
35037
+ import path53 from "path";
35038
+ import os43 from "os";
34914
35039
  function buildSensitivePaths(home, cwd) {
34915
35040
  const homeFiles = homeCredentialFiles(home).map(({ full, label: label3, description, score }) => ({
34916
35041
  full,
@@ -34921,19 +35046,19 @@ function buildSensitivePaths(home, cwd) {
34921
35046
  return [
34922
35047
  ...homeFiles,
34923
35048
  {
34924
- full: path52.join(cwd, ".env"),
35049
+ full: path53.join(cwd, ".env"),
34925
35050
  label: ".env (current folder)",
34926
35051
  description: "App secrets: database passwords, API keys",
34927
35052
  score: 20
34928
35053
  },
34929
35054
  {
34930
- full: path52.join(cwd, ".env.local"),
35055
+ full: path53.join(cwd, ".env.local"),
34931
35056
  label: ".env.local (current folder)",
34932
35057
  description: "Local overrides: often contains real credentials",
34933
35058
  score: 15
34934
35059
  },
34935
35060
  {
34936
- full: path52.join(cwd, ".env.production"),
35061
+ full: path53.join(cwd, ".env.production"),
34937
35062
  label: ".env.production (current folder)",
34938
35063
  description: "Production secrets",
34939
35064
  score: 20
@@ -34942,26 +35067,20 @@ function buildSensitivePaths(home, cwd) {
34942
35067
  }
34943
35068
  function isReadable(filePath) {
34944
35069
  try {
34945
- fs50.accessSync(filePath, fs50.constants.R_OK);
35070
+ fs51.accessSync(filePath, fs51.constants.R_OK);
34946
35071
  return true;
34947
35072
  } catch {
34948
35073
  return false;
34949
35074
  }
34950
35075
  }
34951
- function scoreLabel(score) {
34952
- if (score >= 80) return chalk3.green(`${score}/100 Good`);
34953
- if (score >= 50) return chalk3.yellow(`${score}/100 Moderate risk`);
34954
- if (score >= 25) return chalk3.red(`${score}/100 High risk`);
34955
- return chalk3.red.bold(`${score}/100 Critical`);
34956
- }
34957
35076
  function runBlast() {
34958
- const home = os42.homedir();
35077
+ const home = os43.homedir();
34959
35078
  const cwd = process.cwd();
34960
35079
  const paths = buildSensitivePaths(home, cwd);
34961
35080
  let scoreDeduction = 0;
34962
35081
  const reachable = [];
34963
35082
  for (const p of paths) {
34964
- if (fs50.existsSync(p.full) && isReadable(p.full)) {
35083
+ if (fs51.existsSync(p.full) && isReadable(p.full)) {
34965
35084
  reachable.push(p);
34966
35085
  scoreDeduction += p.score;
34967
35086
  }
@@ -34977,11 +35096,18 @@ function runBlast() {
34977
35096
  }
34978
35097
  return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
34979
35098
  }
35099
+ function labelColumnWidth(labels) {
35100
+ return Math.max(38, ...labels.map((l) => l.length + 2));
35101
+ }
34980
35102
  function registerBlastCommand(program2) {
34981
35103
  program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
34982
- const home = os42.homedir();
35104
+ const home = os43.homedir();
34983
35105
  const cwd = process.cwd();
34984
- const { reachable, envFindings, score } = runBlast();
35106
+ const { reachable, envFindings } = runBlast();
35107
+ const width = labelColumnWidth([
35108
+ ...reachable.map((p) => p.label),
35109
+ ...envFindings.map((f) => f.key)
35110
+ ]);
34985
35111
  console.log("");
34986
35112
  console.log(
34987
35113
  chalk3.bold(" \u{1F52D} Node9 Blast Radius") + chalk3.dim(" \xB7 what an AI agent can reach from here")
@@ -34992,7 +35118,7 @@ function registerBlastCommand(program2) {
34992
35118
  console.log(" " + chalk3.red.bold("Sensitive files reachable:"));
34993
35119
  for (const p of reachable) {
34994
35120
  console.log(
34995
- " " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(38)) + chalk3.dim(p.description)
35121
+ " " + chalk3.red("\u2717 ") + chalk3.yellow(p.label.padEnd(width)) + chalk3.dim(p.description)
34996
35122
  );
34997
35123
  }
34998
35124
  console.log("");
@@ -35001,7 +35127,7 @@ function registerBlastCommand(program2) {
35001
35127
  console.log(" " + chalk3.red.bold("Secrets in active environment:"));
35002
35128
  for (const f of envFindings) {
35003
35129
  console.log(
35004
- " " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(38)) + chalk3.dim(f.patternName)
35130
+ " " + chalk3.red("\u2717 ") + chalk3.yellow(f.key.padEnd(width)) + chalk3.dim(f.patternName)
35005
35131
  );
35006
35132
  }
35007
35133
  console.log("");
@@ -35009,21 +35135,24 @@ function registerBlastCommand(program2) {
35009
35135
  console.log(" " + chalk3.dim("\u2500".repeat(70)));
35010
35136
  if (reachable.length === 0 && envFindings.length === 0) {
35011
35137
  console.log(" " + chalk3.green("\u2705 No sensitive files or environment secrets found."));
35012
- console.log(" Security Score: " + scoreLabel(score));
35013
35138
  } else {
35014
35139
  console.log(
35015
- " Security Score: " + scoreLabel(score) + chalk3.dim(
35016
- ` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
35017
- )
35140
+ chalk3.bold(
35141
+ ` ${reachable.length} file${reachable.length !== 1 ? "s" : ""} and ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""}`
35142
+ ) + chalk3.dim(" every AI agent you start can read.")
35018
35143
  );
35019
35144
  console.log("");
35020
35145
  console.log(
35021
35146
  chalk3.dim(
35022
- " Every AI agent you start can read the files and env vars listed above.\n Run `node9 shield enable project-jail` to restrict agent file access.\n Run `node9 mask` to redact secrets from existing session history."
35147
+ " Run `node9 shield enable project-jail` to restrict agent file access.\n Run `node9 mask` to redact secrets from existing session history."
35023
35148
  )
35024
35149
  );
35025
35150
  }
35026
35151
  console.log("");
35152
+ console.log(
35153
+ chalk3.dim(" Machine score: ") + chalk3.cyan("node9 posture") + chalk3.dim(" (this list is its Secrets section)")
35154
+ );
35155
+ console.log("");
35027
35156
  });
35028
35157
  }
35029
35158
  var init_blast = __esm({
@@ -35116,6 +35245,12 @@ function relativeDate(timestamp2, now = /* @__PURE__ */ new Date()) {
35116
35245
  if (days > 90) return "90d+";
35117
35246
  return `${days}d`;
35118
35247
  }
35248
+ function relativeAgo(timestamp2, now = /* @__PURE__ */ new Date()) {
35249
+ const rel = relativeDate(timestamp2, now);
35250
+ if (rel === "?" || rel === "today") return rel;
35251
+ if (rel === "1d") return "yesterday";
35252
+ return `${rel} ago`;
35253
+ }
35119
35254
  var PANEL_WIDTH;
35120
35255
  var init_scan_derive = __esm({
35121
35256
  "src/cli/render/scan-derive.ts"() {
@@ -35125,13 +35260,14 @@ var init_scan_derive = __esm({
35125
35260
  });
35126
35261
 
35127
35262
  // src/protection.ts
35128
- var PROTECTIVE_SHIELD_DISCOUNTS;
35263
+ function isProtectiveShield(name) {
35264
+ return PROTECTIVE_SHIELDS.has(name);
35265
+ }
35266
+ var PROTECTIVE_SHIELDS;
35129
35267
  var init_protection = __esm({
35130
35268
  "src/protection.ts"() {
35131
35269
  "use strict";
35132
- PROTECTIVE_SHIELD_DISCOUNTS = {
35133
- "project-jail": 0.7
35134
- };
35270
+ PROTECTIVE_SHIELDS = /* @__PURE__ */ new Set(["project-jail"]);
35135
35271
  }
35136
35272
  });
35137
35273
 
@@ -35169,35 +35305,21 @@ var init_scan_json = __esm({
35169
35305
  });
35170
35306
 
35171
35307
  // src/cli/render/scan-history.ts
35172
- import fs51 from "fs";
35173
- import path53 from "path";
35174
- import os43 from "os";
35308
+ import fs52 from "fs";
35309
+ import path54 from "path";
35310
+ import os44 from "os";
35175
35311
  function defaultHistoryPath() {
35176
- return path53.join(os43.homedir(), ".node9", "scan-history.json");
35177
- }
35178
- function readPreviousScan(opts = {}) {
35179
- const filePath = opts.path ?? defaultHistoryPath();
35180
- try {
35181
- if (!fs51.existsSync(filePath)) return null;
35182
- const raw = fs51.readFileSync(filePath, "utf8");
35183
- const parsed = JSON.parse(raw);
35184
- if (!Array.isArray(parsed) || parsed.length === 0) return null;
35185
- const last = parsed[parsed.length - 1];
35186
- if (!isValidRecord(last)) return null;
35187
- return last;
35188
- } catch {
35189
- return null;
35190
- }
35312
+ return path54.join(os44.homedir(), ".node9", "scan-history.json");
35191
35313
  }
35192
35314
  function appendScanHistory(record2, opts = {}) {
35193
35315
  const filePath = opts.path ?? defaultHistoryPath();
35194
35316
  const cap = opts.cap ?? SCAN_HISTORY_CAP;
35195
35317
  try {
35196
- fs51.mkdirSync(path53.dirname(filePath), { recursive: true });
35318
+ fs52.mkdirSync(path54.dirname(filePath), { recursive: true });
35197
35319
  let history = [];
35198
- if (fs51.existsSync(filePath)) {
35320
+ if (fs52.existsSync(filePath)) {
35199
35321
  try {
35200
- const parsed = JSON.parse(fs51.readFileSync(filePath, "utf8"));
35322
+ const parsed = JSON.parse(fs52.readFileSync(filePath, "utf8"));
35201
35323
  if (Array.isArray(parsed)) {
35202
35324
  history = parsed.filter(isValidRecord);
35203
35325
  }
@@ -35216,15 +35338,6 @@ function appendScanHistory(record2, opts = {}) {
35216
35338
  );
35217
35339
  }
35218
35340
  }
35219
- function computeScanDelta(current, previous, now = Date.now()) {
35220
- if (!previous) return null;
35221
- const prevMs = Date.parse(previous.timestamp);
35222
- if (Number.isNaN(prevMs)) return null;
35223
- const scoreDelta = current.score - previous.score;
35224
- const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
35225
- if (scoreDelta === 0 && daysAgo === 0) return null;
35226
- return { scoreDelta, daysAgo };
35227
- }
35228
35341
  function isValidRecord(x) {
35229
35342
  if (typeof x !== "object" || x === null) return false;
35230
35343
  const r = x;
@@ -35252,9 +35365,9 @@ __export(scan_watermark_exports, {
35252
35365
  tickForensicBroadcast: () => tickForensicBroadcast,
35253
35366
  tickScanWatcher: () => tickScanWatcher
35254
35367
  });
35255
- import fs52 from "fs";
35256
- import os44 from "os";
35257
- import path54 from "path";
35368
+ import fs53 from "fs";
35369
+ import os45 from "os";
35370
+ import path55 from "path";
35258
35371
  import readline from "readline";
35259
35372
  function freshWatermark() {
35260
35373
  return {
@@ -35267,7 +35380,7 @@ function freshWatermark() {
35267
35380
  function loadWatermark() {
35268
35381
  let raw;
35269
35382
  try {
35270
- raw = fs52.readFileSync(WATERMARK_FILE(), "utf-8");
35383
+ raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
35271
35384
  } catch {
35272
35385
  return { status: "fresh", wm: freshWatermark() };
35273
35386
  }
@@ -35319,28 +35432,28 @@ function loadWatermark() {
35319
35432
  function saveWatermark(wm) {
35320
35433
  if (wm.schemaVersion > WATERMARK_SCHEMA_VERSION) return;
35321
35434
  const target = WATERMARK_FILE();
35322
- const dir2 = path54.dirname(target);
35323
- if (!fs52.existsSync(dir2)) fs52.mkdirSync(dir2, { recursive: true });
35435
+ const dir2 = path55.dirname(target);
35436
+ if (!fs53.existsSync(dir2)) fs53.mkdirSync(dir2, { recursive: true });
35324
35437
  const tmp = target + ".tmp";
35325
- fs52.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
35326
- fs52.renameSync(tmp, target);
35438
+ fs53.writeFileSync(tmp, JSON.stringify(wm, null, 2) + "\n", "utf-8");
35439
+ fs53.renameSync(tmp, target);
35327
35440
  }
35328
35441
  function listJsonlFiles() {
35329
35442
  const root = PROJECTS_DIR();
35330
- if (!fs52.existsSync(root)) return [];
35443
+ if (!fs53.existsSync(root)) return [];
35331
35444
  const out = [];
35332
- for (const entry of fs52.readdirSync(root, { withFileTypes: true })) {
35445
+ for (const entry of fs53.readdirSync(root, { withFileTypes: true })) {
35333
35446
  if (!entry.isDirectory()) continue;
35334
- const projectDir = path54.join(root, entry.name);
35447
+ const projectDir = path55.join(root, entry.name);
35335
35448
  let inner;
35336
35449
  try {
35337
- inner = fs52.readdirSync(projectDir, { withFileTypes: true });
35450
+ inner = fs53.readdirSync(projectDir, { withFileTypes: true });
35338
35451
  } catch {
35339
35452
  continue;
35340
35453
  }
35341
35454
  for (const file2 of inner) {
35342
35455
  if (file2.isFile() && file2.name.endsWith(".jsonl")) {
35343
- out.push(path54.join(projectDir, file2.name));
35456
+ out.push(path55.join(projectDir, file2.name));
35344
35457
  }
35345
35458
  }
35346
35459
  }
@@ -35348,7 +35461,7 @@ function listJsonlFiles() {
35348
35461
  }
35349
35462
  function fileSize(p) {
35350
35463
  try {
35351
- return fs52.statSync(p).size;
35464
+ return fs53.statSync(p).size;
35352
35465
  } catch {
35353
35466
  return 0;
35354
35467
  }
@@ -35358,7 +35471,7 @@ async function scanDelta(filePath, fromByte, onLine) {
35358
35471
  if (size <= fromByte) return fromByte;
35359
35472
  const lastNl = findLastNewline(filePath, fromByte, size);
35360
35473
  const endsWithNewline = lastNl === size - 1;
35361
- const stream = fs52.createReadStream(filePath, {
35474
+ const stream = fs53.createReadStream(filePath, {
35362
35475
  start: fromByte,
35363
35476
  end: size - 1,
35364
35477
  highWaterMark: 64 * 1024
@@ -35387,7 +35500,7 @@ function findLastNewline(filePath, from, size) {
35387
35500
  const CHUNK = 64 * 1024;
35388
35501
  let fd;
35389
35502
  try {
35390
- fd = fs52.openSync(filePath, "r");
35503
+ fd = fs53.openSync(filePath, "r");
35391
35504
  } catch {
35392
35505
  return -1;
35393
35506
  }
@@ -35396,7 +35509,7 @@ function findLastNewline(filePath, from, size) {
35396
35509
  let end = size;
35397
35510
  while (end > from) {
35398
35511
  const start = Math.max(from, end - CHUNK);
35399
- const n = fs52.readSync(fd, buf, 0, end - start, start);
35512
+ const n = fs53.readSync(fd, buf, 0, end - start, start);
35400
35513
  const idx = buf.subarray(0, n).lastIndexOf(10);
35401
35514
  if (idx !== -1) return start + idx;
35402
35515
  end = start;
@@ -35405,7 +35518,7 @@ function findLastNewline(filePath, from, size) {
35405
35518
  } catch {
35406
35519
  return -1;
35407
35520
  } finally {
35408
- fs52.closeSync(fd);
35521
+ fs53.closeSync(fd);
35409
35522
  }
35410
35523
  }
35411
35524
  function safeCanaryCtxValues() {
@@ -35416,7 +35529,7 @@ function safeCanaryCtxValues() {
35416
35529
  }
35417
35530
  }
35418
35531
  function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
35419
- const cwd = typeof obj.cwd === "string" && path54.isAbsolute(obj.cwd) ? obj.cwd : void 0;
35532
+ const cwd = typeof obj.cwd === "string" && path55.isAbsolute(obj.cwd) ? obj.cwd : void 0;
35420
35533
  return {
35421
35534
  sessionId,
35422
35535
  lineIndex,
@@ -35432,7 +35545,7 @@ function canonicalContextForLine(obj, sessionId, lineIndex, canaryVals = []) {
35432
35545
  // line-level DLP runs above already
35433
35546
  canaryValues: canaryVals,
35434
35547
  ...cwd && { cwd },
35435
- homeDirs: [os44.homedir()]
35548
+ homeDirs: [os45.homedir()]
35436
35549
  };
35437
35550
  }
35438
35551
  function extractFindingsFromLine(line, sessionId, lineIndex, canaryVals = []) {
@@ -35520,7 +35633,7 @@ async function tickForensicBroadcast(offsets) {
35520
35633
  continue;
35521
35634
  }
35522
35635
  if (size <= offset) continue;
35523
- const sessionId = path54.basename(file2, ".jsonl");
35636
+ const sessionId = path55.basename(file2, ".jsonl");
35524
35637
  const newOffset = await scanDelta(file2, offset, (obj, lineIndex) => {
35525
35638
  out.push(...extractFindingsFromLine(obj, sessionId, lineIndex, canaryVals));
35526
35639
  });
@@ -35576,7 +35689,7 @@ function emptyTick(uploadAs) {
35576
35689
  function readRawWatermarkPreservingOffsets() {
35577
35690
  let raw;
35578
35691
  try {
35579
- raw = fs52.readFileSync(WATERMARK_FILE(), "utf-8");
35692
+ raw = fs53.readFileSync(WATERMARK_FILE(), "utf-8");
35580
35693
  } catch {
35581
35694
  return null;
35582
35695
  }
@@ -35611,13 +35724,13 @@ async function runActualTick(wm) {
35611
35724
  if (!known) {
35612
35725
  let mtimeMs = 0;
35613
35726
  try {
35614
- mtimeMs = fs52.statSync(filePath).mtime.getTime();
35727
+ mtimeMs = fs53.statSync(filePath).mtime.getTime();
35615
35728
  } catch {
35616
35729
  continue;
35617
35730
  }
35618
35731
  if (mtimeMs >= watermarkCreatedAt) {
35619
35732
  filesNew++;
35620
- const sessionId2 = path54.basename(filePath, ".jsonl");
35733
+ const sessionId2 = path55.basename(filePath, ".jsonl");
35621
35734
  const newScannedTo2 = await scanDelta(filePath, 0, (obj, lineIndex) => {
35622
35735
  totalToolCalls++;
35623
35736
  toolCallsBySession[sessionId2] = (toolCallsBySession[sessionId2] ?? 0) + 1;
@@ -35635,7 +35748,7 @@ async function runActualTick(wm) {
35635
35748
  filesSkipped++;
35636
35749
  continue;
35637
35750
  }
35638
- const sessionId = path54.basename(filePath, ".jsonl");
35751
+ const sessionId = path55.basename(filePath, ".jsonl");
35639
35752
  const newScannedTo = await scanDelta(filePath, known.scannedTo, (obj, lineIndex) => {
35640
35753
  totalToolCalls++;
35641
35754
  toolCallsBySession[sessionId] = (toolCallsBySession[sessionId] ?? 0) + 1;
@@ -35670,8 +35783,8 @@ var init_scan_watermark = __esm({
35670
35783
  init_registry2();
35671
35784
  init_config();
35672
35785
  init_dist();
35673
- PROJECTS_DIR = () => path54.join(os44.homedir(), ".claude", "projects");
35674
- WATERMARK_FILE = () => path54.join(os44.homedir(), ".node9", "scan-watermark.json");
35786
+ PROJECTS_DIR = () => path55.join(os45.homedir(), ".claude", "projects");
35787
+ WATERMARK_FILE = () => path55.join(os45.homedir(), ".node9", "scan-watermark.json");
35675
35788
  MAX_LINE_BYTES = 2 * 1024 * 1024;
35676
35789
  WATERMARK_SCHEMA_VERSION = 2;
35677
35790
  LONG_OUTPUT_THRESHOLD_BYTES2 = LONG_OUTPUT_THRESHOLD_BYTES;
@@ -35687,10 +35800,10 @@ __export(scan_upload_history_exports, {
35687
35800
  parseSinceCutoff: () => parseSinceCutoff,
35688
35801
  runUploadHistory: () => runUploadHistory
35689
35802
  });
35690
- import fs53 from "fs";
35803
+ import fs54 from "fs";
35691
35804
  import https from "https";
35692
- import os45 from "os";
35693
- import path55 from "path";
35805
+ import os46 from "os";
35806
+ import path56 from "path";
35694
35807
  import chalk5 from "chalk";
35695
35808
  function emptySignals2() {
35696
35809
  return {
@@ -35725,40 +35838,40 @@ function parseSinceCutoff(raw, now = /* @__PURE__ */ new Date()) {
35725
35838
  return now.getTime() - 90 * 864e5;
35726
35839
  }
35727
35840
  function* iterateJsonlFiles(cutoffMs) {
35728
- const projectsDir = path55.join(os45.homedir(), ".claude", "projects");
35841
+ const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
35729
35842
  let dirs;
35730
35843
  try {
35731
- dirs = fs53.readdirSync(projectsDir);
35844
+ dirs = fs54.readdirSync(projectsDir);
35732
35845
  } catch {
35733
35846
  return;
35734
35847
  }
35735
35848
  for (const dir2 of dirs) {
35736
- const dirPath = path55.join(projectsDir, dir2);
35849
+ const dirPath = path56.join(projectsDir, dir2);
35737
35850
  let stats;
35738
35851
  try {
35739
- stats = fs53.statSync(dirPath);
35852
+ stats = fs54.statSync(dirPath);
35740
35853
  } catch {
35741
35854
  continue;
35742
35855
  }
35743
35856
  if (!stats.isDirectory()) continue;
35744
35857
  let files;
35745
35858
  try {
35746
- files = fs53.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
35859
+ files = fs54.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
35747
35860
  } catch {
35748
35861
  continue;
35749
35862
  }
35750
35863
  for (const file2 of files) {
35751
- const filePath = path55.join(dirPath, file2);
35864
+ const filePath = path56.join(dirPath, file2);
35752
35865
  let mtime = 0;
35753
35866
  try {
35754
- mtime = fs53.statSync(filePath).mtimeMs;
35867
+ mtime = fs54.statSync(filePath).mtimeMs;
35755
35868
  } catch {
35756
35869
  continue;
35757
35870
  }
35758
35871
  if (mtime < cutoffMs) continue;
35759
35872
  yield {
35760
35873
  filePath,
35761
- sessionId: path55.basename(file2, ".jsonl"),
35874
+ sessionId: path56.basename(file2, ".jsonl"),
35762
35875
  projectDir: dir2
35763
35876
  };
35764
35877
  }
@@ -35826,7 +35939,7 @@ async function runUploadHistory(opts) {
35826
35939
  filesScanned++;
35827
35940
  let content;
35828
35941
  try {
35829
- content = fs53.readFileSync(filePath, "utf8");
35942
+ content = fs54.readFileSync(filePath, "utf8");
35830
35943
  } catch {
35831
35944
  continue;
35832
35945
  }
@@ -35888,7 +36001,7 @@ async function runUploadHistory(opts) {
35888
36001
  ` Parsed ${linesParsed.toLocaleString()} JSONL lines from ${filesScanned} session${filesScanned === 1 ? "" : "s"} (skipped ${linesSkipped} malformed)`
35889
36002
  )
35890
36003
  );
35891
- console.log(chalk5.gray(` Findings: ${findings.length}, score: ${summary.score}/100`));
36004
+ console.log(chalk5.gray(` Findings: ${findings.length}`));
35892
36005
  const scanUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/scan/report") : `${creds.apiUrl.replace(/\/$/, "")}/scan/report`;
35893
36006
  await postJson(scanUrl, creds.apiKey, {
35894
36007
  ...summary,
@@ -35900,10 +36013,10 @@ async function runUploadHistory(opts) {
35900
36013
  const costUrl = creds.apiUrl.endsWith("/policies/sync") ? creds.apiUrl.replace(/\/policies\/sync$/, "/cost-sync") : `${creds.apiUrl.replace(/\/$/, "")}/cost-sync`;
35901
36014
  let username = "unknown";
35902
36015
  try {
35903
- username = os45.userInfo().username;
36016
+ username = os46.userInfo().username;
35904
36017
  } catch {
35905
36018
  }
35906
- const machineId = `${os45.hostname()}:${username}`;
36019
+ const machineId = `${os46.hostname()}:${username}`;
35907
36020
  await postJson(costUrl, creds.apiKey, {
35908
36021
  machineId,
35909
36022
  entries: dailyEntries
@@ -35977,9 +36090,9 @@ var init_scan_upload_history = __esm({
35977
36090
 
35978
36091
  // src/cli/commands/scan.ts
35979
36092
  import chalk6 from "chalk";
35980
- import fs54 from "fs";
35981
- import path56 from "path";
35982
- import os46 from "os";
36093
+ import fs55 from "fs";
36094
+ import path57 from "path";
36095
+ import os47 from "os";
35983
36096
  import stringWidth2 from "string-width";
35984
36097
  function dayKey(ts) {
35985
36098
  if (!ts) return "unknown";
@@ -36122,7 +36235,7 @@ function emptyScanDedup() {
36122
36235
  return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
36123
36236
  }
36124
36237
  function displayHome(p) {
36125
- const home = os46.homedir();
36238
+ const home = os47.homedir();
36126
36239
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
36127
36240
  }
36128
36241
  function dlpKey(patternName, redactedSample, projLabel, timestamp2) {
@@ -36261,7 +36374,7 @@ function detectLoops(calls, project, sessionId, agent) {
36261
36374
  return findings.sort((a, b) => b.count - a.count);
36262
36375
  }
36263
36376
  function geminiChatFiles(chatsDir) {
36264
- const files = fs54.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
36377
+ const files = fs55.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
36265
36378
  const seen = /* @__PURE__ */ new Set();
36266
36379
  return files.filter((f) => {
36267
36380
  const id = f.replace(/\.jsonl?$/, "");
@@ -36273,14 +36386,14 @@ function geminiChatFiles(chatsDir) {
36273
36386
  function countScanFiles(startDate = null) {
36274
36387
  let total = 0;
36275
36388
  const counts = (p) => fileMayHoldWindow(fileStamp(p), startDate);
36276
- const claudeDir = path56.join(os46.homedir(), ".claude", "projects");
36277
- if (fs54.existsSync(claudeDir)) {
36389
+ const claudeDir = path57.join(os47.homedir(), ".claude", "projects");
36390
+ if (fs55.existsSync(claudeDir)) {
36278
36391
  try {
36279
- for (const proj of fs54.readdirSync(claudeDir)) {
36280
- const p = path56.join(claudeDir, proj);
36392
+ for (const proj of fs55.readdirSync(claudeDir)) {
36393
+ const p = path57.join(claudeDir, proj);
36281
36394
  try {
36282
- if (!fs54.statSync(p).isDirectory()) continue;
36283
- total += listSessionFiles(p).filter((f) => counts(path56.join(p, f))).length;
36395
+ if (!fs55.statSync(p).isDirectory()) continue;
36396
+ total += listSessionFiles(p).filter((f) => counts(path57.join(p, f))).length;
36284
36397
  } catch {
36285
36398
  continue;
36286
36399
  }
@@ -36288,18 +36401,18 @@ function countScanFiles(startDate = null) {
36288
36401
  } catch {
36289
36402
  }
36290
36403
  }
36291
- const geminiDir = path56.join(os46.homedir(), ".gemini", "tmp");
36292
- if (fs54.existsSync(geminiDir)) {
36404
+ const geminiDir = path57.join(os47.homedir(), ".gemini", "tmp");
36405
+ if (fs55.existsSync(geminiDir)) {
36293
36406
  try {
36294
- for (const slug2 of fs54.readdirSync(geminiDir)) {
36295
- const p = path56.join(geminiDir, slug2);
36407
+ for (const slug2 of fs55.readdirSync(geminiDir)) {
36408
+ const p = path57.join(geminiDir, slug2);
36296
36409
  try {
36297
- if (!fs54.statSync(p).isDirectory()) continue;
36298
- const chatsDir = path56.join(p, "chats");
36299
- if (fs54.existsSync(chatsDir)) {
36410
+ if (!fs55.statSync(p).isDirectory()) continue;
36411
+ const chatsDir = path57.join(p, "chats");
36412
+ if (fs55.existsSync(chatsDir)) {
36300
36413
  try {
36301
36414
  total += geminiChatFiles(chatsDir).filter(
36302
- (f) => counts(path56.join(chatsDir, f))
36415
+ (f) => counts(path57.join(chatsDir, f))
36303
36416
  ).length;
36304
36417
  } catch {
36305
36418
  }
@@ -36312,13 +36425,13 @@ function countScanFiles(startDate = null) {
36312
36425
  }
36313
36426
  }
36314
36427
  for (const surface of ["antigravity-cli", "antigravity-ide"]) {
36315
- const brainDir = path56.join(os46.homedir(), ".gemini", surface, "brain");
36316
- if (!fs54.existsSync(brainDir)) continue;
36428
+ const brainDir = path57.join(os47.homedir(), ".gemini", surface, "brain");
36429
+ if (!fs55.existsSync(brainDir)) continue;
36317
36430
  try {
36318
- for (const conv of fs54.readdirSync(brainDir)) {
36319
- const convPath = path56.join(brainDir, conv);
36431
+ for (const conv of fs55.readdirSync(brainDir)) {
36432
+ const convPath = path57.join(brainDir, conv);
36320
36433
  try {
36321
- if (!fs54.statSync(convPath).isDirectory()) continue;
36434
+ if (!fs55.statSync(convPath).isDirectory()) continue;
36322
36435
  const transcript = antigravityTranscriptPath(convPath);
36323
36436
  if (transcript && counts(transcript)) total += 1;
36324
36437
  } catch {
@@ -36328,12 +36441,12 @@ function countScanFiles(startDate = null) {
36328
36441
  } catch {
36329
36442
  }
36330
36443
  }
36331
- const copilotDir = path56.join(os46.homedir(), ".copilot", "session-state");
36332
- if (fs54.existsSync(copilotDir)) {
36444
+ const copilotDir = path57.join(os47.homedir(), ".copilot", "session-state");
36445
+ if (fs55.existsSync(copilotDir)) {
36333
36446
  try {
36334
- for (const sid of fs54.readdirSync(copilotDir)) {
36335
- const events = path56.join(copilotDir, sid, "events.jsonl");
36336
- if (fs54.existsSync(events) && counts(events)) total += 1;
36447
+ for (const sid of fs55.readdirSync(copilotDir)) {
36448
+ const events = path57.join(copilotDir, sid, "events.jsonl");
36449
+ if (fs55.existsSync(events) && counts(events)) total += 1;
36337
36450
  }
36338
36451
  } catch {
36339
36452
  }
@@ -36357,7 +36470,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
36357
36470
  const session = { sessionId, costUSD: 0, toolCalls: 0 };
36358
36471
  let raw;
36359
36472
  try {
36360
- raw = fs54.readFileSync(path56.join(projPath, file2), "utf-8");
36473
+ raw = fs55.readFileSync(path57.join(projPath, file2), "utf-8");
36361
36474
  } catch {
36362
36475
  return false;
36363
36476
  }
@@ -36426,7 +36539,7 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
36426
36539
  if (block.type !== "tool_result") continue;
36427
36540
  const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
36428
36541
  if (filePath) {
36429
- const ext = path56.extname(filePath).toLowerCase();
36542
+ const ext = path57.extname(filePath).toLowerCase();
36430
36543
  if (CODE_EXTENSIONS.has(ext)) continue;
36431
36544
  }
36432
36545
  const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
@@ -36546,9 +36659,9 @@ async function processClaudeFile(file2, projPath, projLabel, judge, judgeFrom, r
36546
36659
  return true;
36547
36660
  }
36548
36661
  async function processClaudeProject(proj, projectsDir, judge, cache7, startDate, result, dedup, canaryVals, onProgress, onLine) {
36549
- const projPath = path56.join(projectsDir, proj);
36662
+ const projPath = path57.join(projectsDir, proj);
36550
36663
  try {
36551
- if (!fs54.statSync(projPath).isDirectory()) return;
36664
+ if (!fs55.statSync(projPath).isDirectory()) return;
36552
36665
  } catch {
36553
36666
  return;
36554
36667
  }
@@ -36560,7 +36673,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
36560
36673
  return;
36561
36674
  }
36562
36675
  for (const file2 of files) {
36563
- const filePath = path56.join(projPath, file2);
36676
+ const filePath = path57.join(projPath, file2);
36564
36677
  const stamp = fileStamp(filePath);
36565
36678
  if (!fileMayHoldWindow(stamp, startDate)) continue;
36566
36679
  result.filesScanned++;
@@ -36591,7 +36704,7 @@ async function processClaudeProject(proj, projectsDir, judge, cache7, startDate,
36591
36704
  }
36592
36705
  function claudeProjectLabel(dirName) {
36593
36706
  const name = decodeURIComponent(dirName);
36594
- const homeDashed = os46.homedir().replace(/\//g, "-");
36707
+ const homeDashed = os47.homedir().replace(/\//g, "-");
36595
36708
  const label3 = name.startsWith(homeDashed) ? "~" + decodeProjectDirName(name.slice(homeDashed.length)) : decodeProjectDirName(name);
36596
36709
  return stripTerminalEscapes(label3).slice(0, 40);
36597
36710
  }
@@ -36702,12 +36815,12 @@ function mergeFileScan(result, file2, dedup, startDate = null) {
36702
36815
  }
36703
36816
  }
36704
36817
  async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
36705
- const projectsDir = path56.join(os46.homedir(), ".claude", "projects");
36818
+ const projectsDir = path57.join(os47.homedir(), ".claude", "projects");
36706
36819
  const result = emptyClaudeScan();
36707
- if (!fs54.existsSync(projectsDir)) return result;
36820
+ if (!fs55.existsSync(projectsDir)) return result;
36708
36821
  let projDirs;
36709
36822
  try {
36710
- projDirs = fs54.readdirSync(projectsDir);
36823
+ projDirs = fs55.readdirSync(projectsDir);
36711
36824
  } catch {
36712
36825
  return result;
36713
36826
  }
@@ -36735,7 +36848,7 @@ async function scanClaudeHistory(startDate, onProgress, onLine, opts = {}) {
36735
36848
  }
36736
36849
  async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36737
36850
  const canaryVals = safeCanaryScanValues();
36738
- const tmpDir = path56.join(os46.homedir(), ".gemini", "tmp");
36851
+ const tmpDir = path57.join(os47.homedir(), ".gemini", "tmp");
36739
36852
  const result = {
36740
36853
  filesScanned: 0,
36741
36854
  sessions: 0,
@@ -36752,31 +36865,31 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36752
36865
  perSession: []
36753
36866
  };
36754
36867
  const dedup = emptyScanDedup();
36755
- if (!fs54.existsSync(tmpDir)) return result;
36868
+ if (!fs55.existsSync(tmpDir)) return result;
36756
36869
  let slugDirs;
36757
36870
  try {
36758
- slugDirs = fs54.readdirSync(tmpDir);
36871
+ slugDirs = fs55.readdirSync(tmpDir);
36759
36872
  } catch {
36760
36873
  return result;
36761
36874
  }
36762
36875
  const judge = new HistoryJudge();
36763
36876
  const cache7 = openScanCache(judge, opts);
36764
36877
  for (const slug2 of slugDirs) {
36765
- const slugPath = path56.join(tmpDir, slug2);
36878
+ const slugPath = path57.join(tmpDir, slug2);
36766
36879
  try {
36767
- if (!fs54.statSync(slugPath).isDirectory()) continue;
36880
+ if (!fs55.statSync(slugPath).isDirectory()) continue;
36768
36881
  } catch {
36769
36882
  continue;
36770
36883
  }
36771
36884
  let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
36772
36885
  try {
36773
36886
  projLabel = stripTerminalEscapes(
36774
- fs54.readFileSync(path56.join(slugPath, ".project_root"), "utf-8").trim()
36775
- ).replace(os46.homedir(), "~").slice(0, 40);
36887
+ fs55.readFileSync(path57.join(slugPath, ".project_root"), "utf-8").trim()
36888
+ ).replace(os47.homedir(), "~").slice(0, 40);
36776
36889
  } catch {
36777
36890
  }
36778
- const chatsDir = path56.join(slugPath, "chats");
36779
- if (!fs54.existsSync(chatsDir)) continue;
36891
+ const chatsDir = path57.join(slugPath, "chats");
36892
+ if (!fs55.existsSync(chatsDir)) continue;
36780
36893
  let chatFiles;
36781
36894
  try {
36782
36895
  chatFiles = geminiChatFiles(chatsDir);
@@ -36785,7 +36898,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36785
36898
  }
36786
36899
  for (const chatFile of chatFiles) {
36787
36900
  const sessionId = chatFile.replace(/\.jsonl?$/, "");
36788
- const filePath = path56.join(chatsDir, chatFile);
36901
+ const filePath = path57.join(chatsDir, chatFile);
36789
36902
  const stamp = fileStamp(filePath);
36790
36903
  if (!fileMayHoldWindow(stamp, startDate)) continue;
36791
36904
  result.filesScanned++;
@@ -36799,7 +36912,7 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36799
36912
  const fileDedup = emptyScanDedup();
36800
36913
  let raw;
36801
36914
  try {
36802
- raw = fs54.readFileSync(path56.join(chatsDir, chatFile), "utf-8");
36915
+ raw = fs55.readFileSync(path57.join(chatsDir, chatFile), "utf-8");
36803
36916
  } catch {
36804
36917
  continue;
36805
36918
  }
@@ -36936,13 +37049,13 @@ async function scanGeminiHistory(startDate, onProgress, onLine, opts = {}) {
36936
37049
  return result;
36937
37050
  }
36938
37051
  function antigravityBrainDirs() {
36939
- return ["antigravity-cli", "antigravity-ide"].map((surface) => path56.join(os46.homedir(), ".gemini", surface, "brain")).filter((p) => fs54.existsSync(p));
37052
+ return ["antigravity-cli", "antigravity-ide"].map((surface) => path57.join(os47.homedir(), ".gemini", surface, "brain")).filter((p) => fs55.existsSync(p));
36940
37053
  }
36941
37054
  function antigravityTranscriptPath(convPath) {
36942
- const logsDir = path56.join(convPath, ".system_generated", "logs");
37055
+ const logsDir = path57.join(convPath, ".system_generated", "logs");
36943
37056
  for (const name of ["transcript_full.jsonl", "transcript.jsonl"]) {
36944
- const p = path56.join(logsDir, name);
36945
- if (fs54.existsSync(p)) return p;
37057
+ const p = path57.join(logsDir, name);
37058
+ if (fs55.existsSync(p)) return p;
36946
37059
  }
36947
37060
  return null;
36948
37061
  }
@@ -36972,14 +37085,14 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
36972
37085
  for (const brainDir of brainDirs) {
36973
37086
  let convDirs;
36974
37087
  try {
36975
- convDirs = fs54.readdirSync(brainDir);
37088
+ convDirs = fs55.readdirSync(brainDir);
36976
37089
  } catch {
36977
37090
  continue;
36978
37091
  }
36979
37092
  for (const conv of convDirs) {
36980
- const convPath = path56.join(brainDir, conv);
37093
+ const convPath = path57.join(brainDir, conv);
36981
37094
  try {
36982
- if (!fs54.statSync(convPath).isDirectory()) continue;
37095
+ if (!fs55.statSync(convPath).isDirectory()) continue;
36983
37096
  } catch {
36984
37097
  continue;
36985
37098
  }
@@ -36998,7 +37111,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
36998
37111
  const fileDedup = emptyScanDedup();
36999
37112
  let raw;
37000
37113
  try {
37001
- raw = fs54.readFileSync(transcriptFile, "utf-8");
37114
+ raw = fs55.readFileSync(transcriptFile, "utf-8");
37002
37115
  } catch {
37003
37116
  continue;
37004
37117
  }
@@ -37073,7 +37186,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
37073
37186
  day.bashCalls++;
37074
37187
  const cwd = String(input.cwd ?? "");
37075
37188
  if (cwd && projLabel === conv.slice(0, 8)) {
37076
- projLabel = stripTerminalEscapes(cwd).replace(os46.homedir(), "~").slice(0, 40);
37189
+ projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
37077
37190
  }
37078
37191
  }
37079
37192
  const rawCmd = String(input.command ?? "").trimStart();
@@ -37099,7 +37212,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
37099
37212
  timestamp: timestamp2,
37100
37213
  sessionId,
37101
37214
  project: projLabel,
37102
- cwd: typeof input.cwd === "string" && path56.isAbsolute(input.cwd) ? input.cwd : void 0
37215
+ cwd: typeof input.cwd === "string" && path57.isAbsolute(input.cwd) ? input.cwd : void 0
37103
37216
  },
37104
37217
  fileResult,
37105
37218
  fileDedup
@@ -37122,7 +37235,7 @@ async function scanAntigravityHistory(startDate, onProgress, onLine, opts = {})
37122
37235
  }
37123
37236
  async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37124
37237
  const canaryVals = safeCanaryScanValues();
37125
- const sessionDir = path56.join(os46.homedir(), ".copilot", "session-state");
37238
+ const sessionDir = path57.join(os47.homedir(), ".copilot", "session-state");
37126
37239
  const result = {
37127
37240
  filesScanned: 0,
37128
37241
  sessions: 0,
@@ -37140,18 +37253,18 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37140
37253
  perSession: []
37141
37254
  };
37142
37255
  const dedup = emptyScanDedup();
37143
- if (!fs54.existsSync(sessionDir)) return result;
37256
+ if (!fs55.existsSync(sessionDir)) return result;
37144
37257
  let sessionIds;
37145
37258
  try {
37146
- sessionIds = fs54.readdirSync(sessionDir);
37259
+ sessionIds = fs55.readdirSync(sessionDir);
37147
37260
  } catch {
37148
37261
  return result;
37149
37262
  }
37150
37263
  const judge = new HistoryJudge();
37151
37264
  const cache7 = openScanCache(judge, opts);
37152
37265
  for (const sessionId of sessionIds) {
37153
- const eventsPath = path56.join(sessionDir, sessionId, "events.jsonl");
37154
- if (!fs54.existsSync(eventsPath)) continue;
37266
+ const eventsPath = path57.join(sessionDir, sessionId, "events.jsonl");
37267
+ if (!fs55.existsSync(eventsPath)) continue;
37155
37268
  const stamp = fileStamp(eventsPath);
37156
37269
  if (!fileMayHoldWindow(stamp, startDate)) continue;
37157
37270
  result.filesScanned++;
@@ -37165,7 +37278,7 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37165
37278
  const fileDedup = emptyScanDedup();
37166
37279
  let raw;
37167
37280
  try {
37168
- raw = fs54.readFileSync(eventsPath, "utf-8");
37281
+ raw = fs55.readFileSync(eventsPath, "utf-8");
37169
37282
  } catch {
37170
37283
  continue;
37171
37284
  }
@@ -37190,8 +37303,8 @@ async function scanCopilotHistory(startDate, onProgress, onLine, opts = {}) {
37190
37303
  if (ev.type === "session.start") {
37191
37304
  const cwd = ev.data?.context?.cwd;
37192
37305
  if (typeof cwd === "string" && cwd) {
37193
- projLabel = stripTerminalEscapes(cwd).replace(os46.homedir(), "~").slice(0, 40);
37194
- if (path56.isAbsolute(cwd)) sessionCwd = cwd;
37306
+ projLabel = stripTerminalEscapes(cwd).replace(os47.homedir(), "~").slice(0, 40);
37307
+ if (path57.isAbsolute(cwd)) sessionCwd = cwd;
37195
37308
  }
37196
37309
  continue;
37197
37310
  }
@@ -37324,7 +37437,7 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
37324
37437
  const fileDedup = emptyScanDedup();
37325
37438
  let lines;
37326
37439
  try {
37327
- lines = fs54.readFileSync(filePath, "utf-8").split("\n");
37440
+ lines = fs55.readFileSync(filePath, "utf-8").split("\n");
37328
37441
  } catch {
37329
37442
  continue;
37330
37443
  }
@@ -37347,8 +37460,8 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
37347
37460
  sessionId = String(payload["id"] ?? filePath);
37348
37461
  startTime = String(payload["timestamp"] ?? "");
37349
37462
  const cwd = String(payload["cwd"] ?? "");
37350
- projLabel = stripTerminalEscapes(cwd.replace(os46.homedir(), "~")).slice(0, 40);
37351
- if (path56.isAbsolute(cwd)) sessionCwd = cwd;
37463
+ projLabel = stripTerminalEscapes(cwd.replace(os47.homedir(), "~")).slice(0, 40);
37464
+ if (path57.isAbsolute(cwd)) sessionCwd = cwd;
37352
37465
  continue;
37353
37466
  }
37354
37467
  if (entry.type === "event_msg" && payload["type"] === "user_message") {
@@ -37465,17 +37578,17 @@ async function scanCodexHistory(startDate, onProgress, onLine, opts = {}) {
37465
37578
  return result;
37466
37579
  }
37467
37580
  function scanShellConfig() {
37468
- const home = os46.homedir();
37581
+ const home = os47.homedir();
37469
37582
  const configFiles = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map(
37470
- (f) => path56.join(home, f)
37583
+ (f) => path57.join(home, f)
37471
37584
  );
37472
37585
  const findings = [];
37473
37586
  const seen = /* @__PURE__ */ new Set();
37474
37587
  for (const filePath of configFiles) {
37475
- if (!fs54.existsSync(filePath)) continue;
37588
+ if (!fs55.existsSync(filePath)) continue;
37476
37589
  let lines;
37477
37590
  try {
37478
- lines = fs54.readFileSync(filePath, "utf-8").split("\n");
37591
+ lines = fs55.readFileSync(filePath, "utf-8").split("\n");
37479
37592
  } catch {
37480
37593
  continue;
37481
37594
  }
@@ -37669,6 +37782,9 @@ function renderCompactScorecard(input) {
37669
37782
  );
37670
37783
  console.log("");
37671
37784
  }
37785
+ console.log(
37786
+ chalk6.dim("\u2192 Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
37787
+ );
37672
37788
  console.log(
37673
37789
  chalk6.dim("\u2192 ") + chalk6.cyan("npx node9-ai scan") + chalk6.dim(" run this on your machine")
37674
37790
  );
@@ -37779,6 +37895,9 @@ function renderNarrativeScorecard(input) {
37779
37895
  }
37780
37896
  console.log("");
37781
37897
  }
37898
+ console.log(
37899
+ chalk6.dim("\u2192 Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
37900
+ );
37782
37901
  console.log(
37783
37902
  chalk6.dim("\u2192 ") + chalk6.cyan("npx node9-ai scan") + chalk6.dim(" run this on your machine")
37784
37903
  );
@@ -37805,25 +37924,25 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
37805
37924
  const topLines = [];
37806
37925
  if (scan.canaryFindings.length > 0) {
37807
37926
  const latest = scan.canaryFindings[0];
37808
- const rel = relativeDate(latest.timestamp, now);
37927
+ const rel = relativeAgo(latest.timestamp, now);
37809
37928
  const n = scan.canaryFindings.length;
37810
37929
  topLines.push(
37811
37930
  mkLine(
37812
37931
  ["\u{1FAA4} ", chalk6.red],
37813
37932
  [`${n} decoy credential${n !== 1 ? "s" : ""} tripped `, chalk6.bold],
37814
- [`(latest: ${rel} ago, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
37933
+ [`(latest: ${rel}, ${latest.kind} at ${displayHome(latest.path)})`, chalk6.dim]
37815
37934
  )
37816
37935
  );
37817
37936
  }
37818
37937
  if (scan.dlpFindings.length > 0) {
37819
37938
  const latest = scan.dlpFindings[0];
37820
- const rel = relativeDate(latest.timestamp, now);
37939
+ const rel = relativeAgo(latest.timestamp, now);
37821
37940
  const noun = `credential leak${scan.dlpFindings.length !== 1 ? "s" : ""}`;
37822
37941
  topLines.push(
37823
37942
  mkLine(
37824
37943
  ["\u{1F6A8} ", chalk6.red],
37825
37944
  [`${scan.dlpFindings.length} ${noun} in tool input `, chalk6.bold],
37826
- [`(latest: ${rel} ago, ${latest.patternName})`, chalk6.dim]
37945
+ [`(latest: ${rel}, ${latest.patternName})`, chalk6.dim]
37827
37946
  )
37828
37947
  );
37829
37948
  }
@@ -38021,24 +38140,24 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
38021
38140
  const shieldImpacts = rollupByShield(summary.sections);
38022
38141
  const shieldLines = [];
38023
38142
  const ranked = [...shieldImpacts].sort((a, b) => {
38024
- const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
38025
- const bDiscount = PROTECTIVE_SHIELD_DISCOUNTS[b.shieldName] ?? 0;
38026
- if (aDiscount !== bDiscount) return bDiscount - aDiscount;
38143
+ const aProt = isProtectiveShield(a.shieldName) ? 1 : 0;
38144
+ const bProt = isProtectiveShield(b.shieldName) ? 1 : 0;
38145
+ if (aProt !== bProt) return bProt - aProt;
38027
38146
  return b.totalCatches - a.totalCatches;
38028
38147
  });
38029
38148
  for (const impact of ranked) {
38030
38149
  if (impact.totalCatches === 0) continue;
38031
- const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
38150
+ const protective = isProtectiveShield(impact.shieldName);
38032
38151
  const isOn = enabledSet.has(impact.shieldName);
38033
- const icon = discount > 0 ? "\u{1F6E1}\uFE0F " : "\u2610 ";
38152
+ const icon = protective ? "\u{1F6E1}\uFE0F " : "\u2610 ";
38034
38153
  const wouldCatch = `catches ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
38035
- const suffix = isOn ? "\u2713 enabled" : discount > 0 ? "\u2192 blocks these reads in-path" : "";
38154
+ const suffix = isOn ? "\u2713 enabled" : protective ? "\u2192 blocks these reads in-path" : "";
38036
38155
  shieldLines.push(
38037
38156
  mkLine(
38038
- [icon, discount > 0 ? chalk6.cyan : chalk6.dim],
38157
+ [icon, protective ? chalk6.cyan : chalk6.dim],
38039
38158
  [impact.shieldName.padEnd(14), chalk6.bold],
38040
38159
  [wouldCatch.padEnd(22), chalk6.dim],
38041
- [suffix, isOn || discount > 0 ? chalk6.green.bold : chalk6.dim]
38160
+ [suffix, isOn || protective ? chalk6.green.bold : chalk6.dim]
38042
38161
  )
38043
38162
  );
38044
38163
  if (impact.topRuleLabels.length > 0) {
@@ -38067,10 +38186,10 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
38067
38186
  shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", chalk6.dim]));
38068
38187
  }
38069
38188
  const topRec = ranked.find(
38070
- (r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0 && !enabledSet.has(r.shieldName)
38189
+ (r) => r.totalCatches > 0 && isProtectiveShield(r.shieldName) && !enabledSet.has(r.shieldName)
38071
38190
  );
38072
38191
  const allCovered = !topRec && ranked.some(
38073
- (r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0 && enabledSet.has(r.shieldName)
38192
+ (r) => r.totalCatches > 0 && isProtectiveShield(r.shieldName) && enabledSet.has(r.shieldName)
38074
38193
  );
38075
38194
  if (topRec) {
38076
38195
  const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 widest coverage)`;
@@ -38275,9 +38394,7 @@ function registerScanCommand(program2) {
38275
38394
  process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
38276
38395
  return;
38277
38396
  }
38278
- let scanDelta2 = null;
38279
38397
  if (!screenshotMode) {
38280
- const previous = readPreviousScan();
38281
38398
  const currentRecord = {
38282
38399
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
38283
38400
  score: blast.score,
@@ -38289,7 +38406,6 @@ function registerScanCommand(program2) {
38289
38406
  totalCalls: scan.totalToolCalls
38290
38407
  };
38291
38408
  appendScanHistory(currentRecord);
38292
- scanDelta2 = computeScanDelta(currentRecord, previous);
38293
38409
  }
38294
38410
  if (options.compact) {
38295
38411
  renderCompactScorecard({
@@ -38328,19 +38444,9 @@ function registerScanCommand(program2) {
38328
38444
  }
38329
38445
  {
38330
38446
  const totalRisky = scan.canaryFindings.length + totalFindings + scan.dlpFindings.length;
38331
- const score = classifyScore(blast.score);
38332
- const severityDisplay = score.band === "critical" ? chalk6.red.bold(score.label) : score.color(score.label);
38333
- const trendSuffix = (() => {
38334
- if (!scanDelta2) return "";
38335
- const { scoreDelta, daysAgo } = scanDelta2;
38336
- if (scoreDelta === 0) return "";
38337
- const arrow = scoreDelta > 0 ? chalk6.green(`\u25B2${scoreDelta}`) : chalk6.red(`\u25BC${Math.abs(scoreDelta)}`);
38338
- const since = daysAgo === 0 ? "today" : daysAgo === 1 ? "yesterday" : `${daysAgo} days ago`;
38339
- return chalk6.dim(" \xB7 ") + arrow + chalk6.dim(` since ${since}`);
38340
- })();
38341
- if (!useInkForHero) {
38447
+ if (!useInkForHero && totalRisky > 0) {
38342
38448
  console.log(
38343
- " " + (score.band === "critical" ? chalk6.red.bold("\u26A0 ") : "") + chalk6.bold("Security Score ") + score.color.bold(`${blast.score}/100`) + " " + severityDisplay + trendSuffix + chalk6.dim(" \xB7 ") + (totalRisky > 0 ? chalk6.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) : chalk6.green("No risky operations"))
38449
+ " " + chalk6.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) + chalk6.dim(" in the scanned history")
38344
38450
  );
38345
38451
  }
38346
38452
  const cardParts = [];
@@ -38392,7 +38498,7 @@ function registerScanCommand(program2) {
38392
38498
  if (!drillDown) {
38393
38499
  const useInk2 = !options.classic;
38394
38500
  if (useInk2) {
38395
- const scanInkPath = path56.join(__dirname, "scan-ink.mjs");
38501
+ const scanInkPath = path57.join(__dirname, "scan-ink.mjs");
38396
38502
  const dynamicImport = new Function("id", "return import(id)");
38397
38503
  const mod = await dynamicImport(`file://${scanInkPath}`);
38398
38504
  const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
@@ -38422,6 +38528,10 @@ function registerScanCommand(program2) {
38422
38528
  keyed
38423
38529
  });
38424
38530
  }
38531
+ console.log(
38532
+ chalk6.dim(" Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
38533
+ );
38534
+ console.log("");
38425
38535
  const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
38426
38536
  console.log(" " + chalk6.green(cta));
38427
38537
  console.log(
@@ -38616,6 +38726,10 @@ function registerScanCommand(program2) {
38616
38726
  );
38617
38727
  console.log("");
38618
38728
  }
38729
+ console.log(
38730
+ chalk6.dim(" Machine score: run ") + chalk6.cyan("node9 posture") + chalk6.dim(" for this machine's score and what to fix first.")
38731
+ );
38732
+ console.log("");
38619
38733
  if (isWired) {
38620
38734
  console.log(chalk6.green(" \u2705 node9 is active \u2014 your future sessions are protected."));
38621
38735
  console.log(
@@ -38749,12 +38863,12 @@ var init_scan = __esm({
38749
38863
  });
38750
38864
 
38751
38865
  // src/daemon/build-id.ts
38752
- import fs55 from "fs";
38753
- import path57 from "path";
38866
+ import fs56 from "fs";
38867
+ import path58 from "path";
38754
38868
  function readOwnVersion() {
38755
38869
  for (const rel of ["../package.json", "../../package.json"]) {
38756
38870
  try {
38757
- const raw = fs55.readFileSync(path57.join(__dirname, rel), "utf-8");
38871
+ const raw = fs56.readFileSync(path58.join(__dirname, rel), "utf-8");
38758
38872
  const v = JSON.parse(raw).version;
38759
38873
  if (typeof v === "string" && v.length > 0) return v;
38760
38874
  } catch {
@@ -38765,7 +38879,7 @@ function readOwnVersion() {
38765
38879
  function computeBuildId(entry = process.argv[1] ?? "") {
38766
38880
  let mtimeMs = 0;
38767
38881
  try {
38768
- if (entry) mtimeMs = fs55.statSync(entry).mtimeMs;
38882
+ if (entry) mtimeMs = fs56.statSync(entry).mtimeMs;
38769
38883
  } catch {
38770
38884
  }
38771
38885
  return { version: readOwnVersion(), mtimeMs };
@@ -38906,8 +39020,8 @@ var init_suggestion_tracker = __esm({
38906
39020
  });
38907
39021
 
38908
39022
  // src/daemon/taint-store.ts
38909
- import fs56 from "fs";
38910
- import path58 from "path";
39023
+ import fs57 from "fs";
39024
+ import path59 from "path";
38911
39025
  var DEFAULT_TTL_MS, TaintStore, SESSION_TAINT_TTL_MS2, SessionTaintStore;
38912
39026
  var init_taint_store = __esm({
38913
39027
  "src/daemon/taint-store.ts"() {
@@ -38977,9 +39091,9 @@ var init_taint_store = __esm({
38977
39091
  /** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
38978
39092
  _resolve(filePath) {
38979
39093
  try {
38980
- return fs56.realpathSync.native(path58.resolve(filePath));
39094
+ return fs57.realpathSync.native(path59.resolve(filePath));
38981
39095
  } catch {
38982
- return path58.resolve(filePath);
39096
+ return path59.resolve(filePath);
38983
39097
  }
38984
39098
  }
38985
39099
  };
@@ -39035,7 +39149,7 @@ var init_taint_store = __esm({
39035
39149
  });
39036
39150
 
39037
39151
  // src/daemon/written-store.ts
39038
- import path59 from "path";
39152
+ import path60 from "path";
39039
39153
  var SESSION_WRITE_TTL_MS, MAX_WRITTEN, MAX_PATH, SessionWriteStore;
39040
39154
  var init_written_store = __esm({
39041
39155
  "src/daemon/written-store.ts"() {
@@ -39047,7 +39161,7 @@ var init_written_store = __esm({
39047
39161
  records = /* @__PURE__ */ new Map();
39048
39162
  key(p) {
39049
39163
  if (typeof p !== "string" || !p || p.length > MAX_PATH) return null;
39050
- return path59.resolve(p);
39164
+ return path60.resolve(p);
39051
39165
  }
39052
39166
  /** Record that these absolute paths were (or are about to be) written. */
39053
39167
  record(paths, ttlMs = SESSION_WRITE_TTL_MS) {
@@ -39378,9 +39492,9 @@ var init_cards = __esm({
39378
39492
 
39379
39493
  // src/daemon/state.ts
39380
39494
  import net2 from "net";
39381
- import fs57 from "fs";
39382
- import path60 from "path";
39383
- import os47 from "os";
39495
+ import fs58 from "fs";
39496
+ import path61 from "path";
39497
+ import os48 from "os";
39384
39498
  import { randomUUID as randomUUID6 } from "crypto";
39385
39499
  function onCardDecided(card) {
39386
39500
  const r = card.record;
@@ -39406,8 +39520,8 @@ function onCardDecided(card) {
39406
39520
  }
39407
39521
  function loadInsightCounts() {
39408
39522
  try {
39409
- if (!fs57.existsSync(INSIGHT_COUNTS_FILE)) return;
39410
- const data = JSON.parse(fs57.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
39523
+ if (!fs58.existsSync(INSIGHT_COUNTS_FILE)) return;
39524
+ const data = JSON.parse(fs58.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
39411
39525
  for (const [tool, count] of Object.entries(data)) {
39412
39526
  if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
39413
39527
  }
@@ -39475,16 +39589,16 @@ function appendAuditLog(data) {
39475
39589
  ...data.channel && { channel: data.channel },
39476
39590
  ...data.cardId && { cardId: data.cardId }
39477
39591
  };
39478
- const dir2 = path60.dirname(AUDIT_LOG_FILE);
39479
- if (!fs57.existsSync(dir2)) fs57.mkdirSync(dir2, { recursive: true });
39480
- fs57.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
39592
+ const dir2 = path61.dirname(AUDIT_LOG_FILE);
39593
+ if (!fs58.existsSync(dir2)) fs58.mkdirSync(dir2, { recursive: true });
39594
+ fs58.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
39481
39595
  } catch {
39482
39596
  }
39483
39597
  }
39484
39598
  function getAuditHistory(limit = 20) {
39485
39599
  try {
39486
- if (!fs57.existsSync(AUDIT_LOG_FILE)) return [];
39487
- const lines = fs57.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
39600
+ if (!fs58.existsSync(AUDIT_LOG_FILE)) return [];
39601
+ const lines = fs58.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
39488
39602
  if (lines.length === 1 && lines[0] === "") return [];
39489
39603
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
39490
39604
  } catch {
@@ -39493,7 +39607,7 @@ function getAuditHistory(limit = 20) {
39493
39607
  }
39494
39608
  function getOrgName() {
39495
39609
  try {
39496
- if (fs57.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
39610
+ if (fs58.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
39497
39611
  } catch {
39498
39612
  }
39499
39613
  return null;
@@ -39505,8 +39619,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
39505
39619
  try {
39506
39620
  let trust = { entries: [] };
39507
39621
  try {
39508
- if (fs57.existsSync(TRUST_FILE2))
39509
- trust = JSON.parse(fs57.readFileSync(TRUST_FILE2, "utf-8"));
39622
+ if (fs58.existsSync(TRUST_FILE2))
39623
+ trust = JSON.parse(fs58.readFileSync(TRUST_FILE2, "utf-8"));
39510
39624
  } catch {
39511
39625
  }
39512
39626
  trust.entries = trust.entries.filter(
@@ -39523,8 +39637,8 @@ function writeTrustEntry(toolName, durationMs, commandPattern) {
39523
39637
  }
39524
39638
  function readPersistentDecisions() {
39525
39639
  try {
39526
- if (fs57.existsSync(DECISIONS_FILE)) {
39527
- return JSON.parse(fs57.readFileSync(DECISIONS_FILE, "utf-8"));
39640
+ if (fs58.existsSync(DECISIONS_FILE)) {
39641
+ return JSON.parse(fs58.readFileSync(DECISIONS_FILE, "utf-8"));
39528
39642
  }
39529
39643
  } catch {
39530
39644
  }
@@ -39552,7 +39666,7 @@ function estimateToolCost(tool, args) {
39552
39666
  const filePath = a.file_path ?? a.path;
39553
39667
  if (filePath) {
39554
39668
  try {
39555
- const bytes = fs57.statSync(filePath).size;
39669
+ const bytes = fs58.statSync(filePath).size;
39556
39670
  return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
39557
39671
  } catch {
39558
39672
  }
@@ -39620,7 +39734,7 @@ function abandonPending() {
39620
39734
  cards.abandonLocalOnly();
39621
39735
  if (autoStarted && cards.open().length === 0) {
39622
39736
  try {
39623
- fs57.unlinkSync(DAEMON_PID_FILE);
39737
+ fs58.unlinkSync(DAEMON_PID_FILE);
39624
39738
  } catch {
39625
39739
  }
39626
39740
  setTimeout(() => {
@@ -39631,8 +39745,8 @@ function abandonPending() {
39631
39745
  }
39632
39746
  function logActivitySocket(msg) {
39633
39747
  try {
39634
- fs57.appendFileSync(
39635
- path60.join(homeDir, ".node9", "hook-debug.log"),
39748
+ fs58.appendFileSync(
39749
+ path61.join(homeDir, ".node9", "hook-debug.log"),
39636
39750
  `[${(/* @__PURE__ */ new Date()).toISOString()}] [activity-socket] ${msg}
39637
39751
  `
39638
39752
  );
@@ -39654,13 +39768,13 @@ function shouldRebind(now = Date.now()) {
39654
39768
  function startActivitySocket() {
39655
39769
  bindActivitySocket();
39656
39770
  activityHealthInterval = setInterval(() => {
39657
- if (!fs57.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
39771
+ if (!fs58.existsSync(ACTIVITY_SOCKET_PATH2)) attemptRebind("health-probe");
39658
39772
  }, ACTIVITY_HEALTH_PROBE_MS);
39659
39773
  activityHealthInterval.unref();
39660
39774
  process.on("exit", () => {
39661
39775
  if (activityHealthInterval) clearInterval(activityHealthInterval);
39662
39776
  try {
39663
- fs57.unlinkSync(ACTIVITY_SOCKET_PATH2);
39777
+ fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
39664
39778
  } catch {
39665
39779
  }
39666
39780
  });
@@ -39688,7 +39802,7 @@ function attemptRebind(reason) {
39688
39802
  }
39689
39803
  function bindActivitySocket() {
39690
39804
  try {
39691
- fs57.unlinkSync(ACTIVITY_SOCKET_PATH2);
39805
+ fs58.unlinkSync(ACTIVITY_SOCKET_PATH2);
39692
39806
  } catch {
39693
39807
  }
39694
39808
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -39797,14 +39911,14 @@ var init_state3 = __esm({
39797
39911
  init_session_history();
39798
39912
  init_atomic_write();
39799
39913
  init_cards();
39800
- homeDir = os47.homedir();
39801
- DAEMON_PID_FILE = path60.join(homeDir, ".node9", "daemon.pid");
39802
- DECISIONS_FILE = path60.join(homeDir, ".node9", "decisions.json");
39803
- AUDIT_LOG_FILE = path60.join(homeDir, ".node9", "audit.log");
39804
- TRUST_FILE2 = path60.join(homeDir, ".node9", "trust.json");
39805
- GLOBAL_CONFIG_FILE = path60.join(homeDir, ".node9", "config.json");
39806
- CREDENTIALS_FILE = path60.join(homeDir, ".node9", "credentials.json");
39807
- INSIGHT_COUNTS_FILE = path60.join(homeDir, ".node9", "insight-counts.json");
39914
+ homeDir = os48.homedir();
39915
+ DAEMON_PID_FILE = path61.join(homeDir, ".node9", "daemon.pid");
39916
+ DECISIONS_FILE = path61.join(homeDir, ".node9", "decisions.json");
39917
+ AUDIT_LOG_FILE = path61.join(homeDir, ".node9", "audit.log");
39918
+ TRUST_FILE2 = path61.join(homeDir, ".node9", "trust.json");
39919
+ GLOBAL_CONFIG_FILE = path61.join(homeDir, ".node9", "config.json");
39920
+ CREDENTIALS_FILE = path61.join(homeDir, ".node9", "credentials.json");
39921
+ INSIGHT_COUNTS_FILE = path61.join(homeDir, ".node9", "insight-counts.json");
39808
39922
  sseClients = /* @__PURE__ */ new Set();
39809
39923
  cards = new CardStore({
39810
39924
  onDecided: onCardDecided,
@@ -39827,7 +39941,7 @@ var init_state3 = __esm({
39827
39941
  "2h": 2 * 60 * 6e4
39828
39942
  };
39829
39943
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
39830
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path60.join(os47.tmpdir(), "node9-activity.sock");
39944
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path61.join(os48.tmpdir(), "node9-activity.sock");
39831
39945
  ACTIVITY_RING_SIZE = 100;
39832
39946
  activityRing = [];
39833
39947
  LARGE_RESPONSE_RING_SIZE = 20;
@@ -39866,13 +39980,13 @@ var init_state3 = __esm({
39866
39980
  });
39867
39981
 
39868
39982
  // src/posture/secrets.ts
39869
- import fs58 from "fs";
39870
- import path61 from "path";
39871
- import os48 from "os";
39983
+ import fs59 from "fs";
39984
+ import path62 from "path";
39985
+ import os49 from "os";
39872
39986
  function displayPath(p, home) {
39873
39987
  if (p === home) return "~";
39874
- const prefix = home.endsWith(path61.sep) ? home : home + path61.sep;
39875
- if (p.startsWith(prefix)) return "~" + path61.sep + p.slice(prefix.length);
39988
+ const prefix = home.endsWith(path62.sep) ? home : home + path62.sep;
39989
+ if (p.startsWith(prefix)) return "~" + path62.sep + p.slice(prefix.length);
39876
39990
  return p;
39877
39991
  }
39878
39992
  function safeRead(file2) {
@@ -39883,8 +39997,8 @@ function safeRead(file2) {
39883
39997
  function candidateFiles(home, cwd) {
39884
39998
  const files = /* @__PURE__ */ new Set();
39885
39999
  try {
39886
- for (const name of fs58.readdirSync(cwd)) {
39887
- if (name === ".env" || name.startsWith(".env.")) files.add(path61.join(cwd, name));
40000
+ for (const name of fs59.readdirSync(cwd)) {
40001
+ if (name === ".env" || name.startsWith(".env.")) files.add(path62.join(cwd, name));
39888
40002
  }
39889
40003
  } catch {
39890
40004
  }
@@ -39892,7 +40006,7 @@ function candidateFiles(home, cwd) {
39892
40006
  if (spec.hookFile) files.add(spec.hookFile(home));
39893
40007
  if (spec.mcpFile) files.add(spec.mcpFile(home));
39894
40008
  }
39895
- files.add(path61.join(home, ".env"));
40009
+ files.add(path62.join(home, ".env"));
39896
40010
  return [...files];
39897
40011
  }
39898
40012
  function credentialMaterial(home) {
@@ -39906,7 +40020,7 @@ function plantedDecoyPaths() {
39906
40020
  }
39907
40021
  }
39908
40022
  function checkSecrets(ctx) {
39909
- const home = ctx.home || os48.homedir();
40023
+ const home = ctx.home || os49.homedir();
39910
40024
  const findings = [];
39911
40025
  const planted = plantedDecoyPaths();
39912
40026
  const plaintext = [];
@@ -39942,7 +40056,7 @@ function checkSecrets(ctx) {
39942
40056
  for (const file2 of credentialMaterial(home)) {
39943
40057
  if (planted.has(file2)) continue;
39944
40058
  try {
39945
- if (fs58.statSync(file2).isFile()) {
40059
+ if (fs59.statSync(file2).isFile()) {
39946
40060
  creds.push(displayPath(file2, home));
39947
40061
  credPaths.push(file2);
39948
40062
  }
@@ -40112,10 +40226,10 @@ var init_templates = __esm({
40112
40226
  });
40113
40227
 
40114
40228
  // src/posture/egress.ts
40115
- import fs59 from "fs";
40229
+ import fs60 from "fs";
40116
40230
  function sandboxEgressWallActive() {
40117
40231
  try {
40118
- return fs59.existsSync(ALLOWED_DOMAINS_PATH);
40232
+ return fs60.existsSync(ALLOWED_DOMAINS_PATH);
40119
40233
  } catch {
40120
40234
  return false;
40121
40235
  }
@@ -40127,7 +40241,7 @@ function evaluateEgressConfig(egress) {
40127
40241
  severity: "high",
40128
40242
  title: "Egress is locked, but node9 is not enforcing it",
40129
40243
  what: "Egress is set to block, but node9 is not applying the policy.",
40130
- why: "node9 isn't wired in (or is in observe mode), so the lock has no effect.",
40244
+ why: "node9 isn't wired in (or is in audit mode), so the lock has no effect.",
40131
40245
  who: "The lock protects nothing until node9 is enforcing in-path.",
40132
40246
  owner: "node9",
40133
40247
  detail: [],
@@ -40144,7 +40258,7 @@ function evaluateEgressConfig(egress) {
40144
40258
  severity: "medium",
40145
40259
  title: "Egress is in review, but node9 is not enforcing it",
40146
40260
  what: "Egress is set to review (approval-gate), but node9 is not applying the policy.",
40147
- why: "node9 isn't wired in (or is in observe mode), so the gate has no effect.",
40261
+ why: "node9 isn't wired in (or is in audit mode), so the gate has no effect.",
40148
40262
  who: "Nothing gates outbound until node9 is enforcing in-path.",
40149
40263
  owner: "node9",
40150
40264
  detail: [],
@@ -40285,14 +40399,14 @@ var init_gate = __esm({
40285
40399
  });
40286
40400
 
40287
40401
  // src/posture/supply-chain.ts
40288
- import os49 from "os";
40289
- import path62 from "path";
40402
+ import os50 from "os";
40403
+ import path63 from "path";
40290
40404
  import { parse as parseToml6 } from "smol-toml";
40291
40405
  function isNode9Managed(command, args = []) {
40292
40406
  if (!command) return false;
40293
- if (path62.basename(command).toLowerCase() === "node9") return true;
40294
- if (PACKAGE_RUNNERS.has(path62.basename(command).toLowerCase())) {
40295
- return args.some((a) => a === "node9" || path62.basename(a).toLowerCase() === "node9");
40407
+ if (path63.basename(command).toLowerCase() === "node9") return true;
40408
+ if (PACKAGE_RUNNERS.has(path63.basename(command).toLowerCase())) {
40409
+ return args.some((a) => a === "node9" || path63.basename(a).toLowerCase() === "node9");
40296
40410
  }
40297
40411
  return false;
40298
40412
  }
@@ -40314,7 +40428,7 @@ function readServers(file2, format, agent) {
40314
40428
  }
40315
40429
  }
40316
40430
  function checkSupplyChain(ctx) {
40317
- const home = ctx.home || os49.homedir();
40431
+ const home = ctx.home || os50.homedir();
40318
40432
  const servers = [];
40319
40433
  for (const spec of AGENT_SPECS) {
40320
40434
  if (!spec.mcpFile) continue;
@@ -40413,11 +40527,11 @@ var init_privilege = __esm({
40413
40527
  });
40414
40528
 
40415
40529
  // src/posture/containment.ts
40416
- import fs60 from "fs";
40530
+ import fs61 from "fs";
40417
40531
  function inContainer() {
40418
- if (fs60.existsSync("/.dockerenv") || fs60.existsSync("/run/.containerenv")) return true;
40532
+ if (fs61.existsSync("/.dockerenv") || fs61.existsSync("/run/.containerenv")) return true;
40419
40533
  try {
40420
- const cgroup = fs60.readFileSync("/proc/1/cgroup", "utf8");
40534
+ const cgroup = fs61.readFileSync("/proc/1/cgroup", "utf8");
40421
40535
  if (/docker|kubepods|containerd|lxc|libpod/.test(cgroup)) return true;
40422
40536
  } catch {
40423
40537
  }
@@ -40463,7 +40577,7 @@ var init_containment = __esm({
40463
40577
  });
40464
40578
 
40465
40579
  // src/posture/inbound.ts
40466
- import fs61 from "fs";
40580
+ import fs62 from "fs";
40467
40581
  function buildNetworkFix(labels) {
40468
40582
  const shielded = [
40469
40583
  ...new Map(
@@ -40521,7 +40635,7 @@ function collectListeners() {
40521
40635
  const byPort = /* @__PURE__ */ new Map();
40522
40636
  for (const file2 of ["/proc/net/tcp", "/proc/net/tcp6"]) {
40523
40637
  try {
40524
- for (const l of parseListeners(fs61.readFileSync(file2, "utf8"))) {
40638
+ for (const l of parseListeners(fs62.readFileSync(file2, "utf8"))) {
40525
40639
  if (!byPort.has(l.port)) byPort.set(l.port, l);
40526
40640
  }
40527
40641
  } catch {
@@ -40533,11 +40647,11 @@ function readProc(pid) {
40533
40647
  let comm = "unknown";
40534
40648
  let cmdline = "";
40535
40649
  try {
40536
- comm = fs61.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
40650
+ comm = fs62.readFileSync(`/proc/${pid}/comm`, "utf8").trim().replace(/-MainThread$/, "") || "unknown";
40537
40651
  } catch {
40538
40652
  }
40539
40653
  try {
40540
- cmdline = fs61.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
40654
+ cmdline = fs62.readFileSync(`/proc/${pid}/cmdline`).toString().replace(/\0/g, " ").trim();
40541
40655
  } catch {
40542
40656
  }
40543
40657
  return { comm, cmdline };
@@ -40547,21 +40661,21 @@ function resolveProcesses(inodes) {
40547
40661
  if (inodes.size === 0) return map;
40548
40662
  let pids;
40549
40663
  try {
40550
- pids = fs61.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
40664
+ pids = fs62.readdirSync("/proc").filter((d) => /^\d+$/.test(d));
40551
40665
  } catch {
40552
40666
  return map;
40553
40667
  }
40554
40668
  for (const pid of pids) {
40555
40669
  let fds;
40556
40670
  try {
40557
- fds = fs61.readdirSync(`/proc/${pid}/fd`);
40671
+ fds = fs62.readdirSync(`/proc/${pid}/fd`);
40558
40672
  } catch {
40559
40673
  continue;
40560
40674
  }
40561
40675
  for (const fd of fds) {
40562
40676
  let link;
40563
40677
  try {
40564
- link = fs61.readlinkSync(`/proc/${pid}/fd/${fd}`);
40678
+ link = fs62.readlinkSync(`/proc/${pid}/fd/${fd}`);
40565
40679
  } catch {
40566
40680
  continue;
40567
40681
  }
@@ -40666,9 +40780,9 @@ var init_inbound = __esm({
40666
40780
  });
40667
40781
 
40668
40782
  // src/posture/coverage.ts
40669
- import os50 from "os";
40783
+ import os51 from "os";
40670
40784
  function checkCoverage(ctx) {
40671
- const home = ctx.home || os50.homedir();
40785
+ const home = ctx.home || os51.homedir();
40672
40786
  const findings = [];
40673
40787
  const protectedAgents = getAgentWiring(home).filter((r) => r.isProtected);
40674
40788
  if (protectedAgents.length === 0) {
@@ -40686,13 +40800,14 @@ function checkCoverage(ctx) {
40686
40800
  return findings;
40687
40801
  }
40688
40802
  const mode = getConfig(ctx.cwd).settings.mode;
40689
- if (mode === "observe" || mode === "audit") {
40803
+ if (isRecordOnly(mode)) {
40804
+ const level = levelOf(mode);
40690
40805
  findings.push({
40691
40806
  category: "Coverage",
40692
40807
  severity: "high",
40693
- title: `node9 is in ${mode} mode \u2014 watching, not blocking`,
40808
+ title: `node9 is in ${level} mode \u2014 watching, not blocking`,
40694
40809
  what: "node9 is watching but not actually blocking anything.",
40695
- why: `It's in ${mode} mode, which logs risky actions but lets them through.`,
40810
+ why: `It's in ${level} mode, which logs risky actions but lets them through.`,
40696
40811
  who: "The guardrails above are observed, not enforced.",
40697
40812
  detail: [],
40698
40813
  owner: "node9",
@@ -40705,12 +40820,13 @@ var init_coverage = __esm({
40705
40820
  "src/posture/coverage.ts"() {
40706
40821
  "use strict";
40707
40822
  init_config();
40823
+ init_dist();
40708
40824
  init_agent_wiring();
40709
40825
  }
40710
40826
  });
40711
40827
 
40712
40828
  // src/posture/governance.ts
40713
- import path63 from "path";
40829
+ import path64 from "path";
40714
40830
  function checkData(ctx) {
40715
40831
  const dlp = getConfig(ctx.cwd).policy.dlp;
40716
40832
  if (!dlp?.enabled) {
@@ -40854,7 +40970,7 @@ var init_governance = __esm({
40854
40970
  "src/posture/governance.ts"() {
40855
40971
  "use strict";
40856
40972
  init_config();
40857
- jailProbePath = (home) => path63.join(home, ".aws", "credentials");
40973
+ jailProbePath = (home) => path64.join(home, ".aws", "credentials");
40858
40974
  }
40859
40975
  });
40860
40976
 
@@ -41008,7 +41124,7 @@ async function annotateCoverage(findings, ctx) {
41008
41124
  const worst = verdicts.some((v) => v === "review") ? "review" : "block";
41009
41125
  f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
41010
41126
  if (isRecordOnly(mode) && f.fix?.includes("shield enable project-jail")) {
41011
- 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.`;
41127
+ 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.`;
41012
41128
  }
41013
41129
  }
41014
41130
  continue;
@@ -41033,7 +41149,7 @@ var init_enforcement = __esm({
41033
41149
  });
41034
41150
 
41035
41151
  // src/posture/index.ts
41036
- import os51 from "os";
41152
+ import os52 from "os";
41037
41153
  function dropEnforcementRedundant(findings) {
41038
41154
  const coveragePresent = findings.some((f) => f.category === "Coverage");
41039
41155
  if (!coveragePresent) return findings;
@@ -41059,7 +41175,7 @@ async function runChecks(checks, ctx) {
41059
41175
  }
41060
41176
  async function runPosture(opts = {}) {
41061
41177
  const ctx = {
41062
- home: opts.home ?? os51.homedir(),
41178
+ home: opts.home ?? os52.homedir(),
41063
41179
  cwd: opts.cwd ?? process.cwd(),
41064
41180
  agent: opts.agent
41065
41181
  };
@@ -41436,20 +41552,20 @@ var init_ship2 = __esm({
41436
41552
 
41437
41553
  // src/machine-id.ts
41438
41554
  import * as crypto7 from "crypto";
41439
- import * as fs62 from "fs";
41440
- import * as os52 from "os";
41441
- import * as path64 from "path";
41442
- function getMachineId(homeDir2 = os52.homedir()) {
41443
- const file2 = path64.join(homeDir2, ".node9", "machine-id");
41555
+ import * as fs63 from "fs";
41556
+ import * as os53 from "os";
41557
+ import * as path65 from "path";
41558
+ function getMachineId(homeDir2 = os53.homedir()) {
41559
+ const file2 = path65.join(homeDir2, ".node9", "machine-id");
41444
41560
  try {
41445
- const existing = fs62.readFileSync(file2, "utf-8").trim();
41561
+ const existing = fs63.readFileSync(file2, "utf-8").trim();
41446
41562
  if (UUID_RE.test(existing)) return existing;
41447
41563
  } catch {
41448
41564
  }
41449
41565
  const id = crypto7.randomUUID();
41450
41566
  try {
41451
- fs62.mkdirSync(path64.dirname(file2), { recursive: true });
41452
- fs62.writeFileSync(file2, id + "\n", { mode: 384 });
41567
+ fs63.mkdirSync(path65.dirname(file2), { recursive: true });
41568
+ fs63.writeFileSync(file2, id + "\n", { mode: 384 });
41453
41569
  } catch {
41454
41570
  }
41455
41571
  return id;
@@ -41489,10 +41605,10 @@ var init_sync_trigger = __esm({
41489
41605
  });
41490
41606
 
41491
41607
  // src/daemon/sync.ts
41492
- import fs63 from "fs";
41608
+ import fs64 from "fs";
41493
41609
  import https4 from "https";
41494
- import os53 from "os";
41495
- import path65 from "path";
41610
+ import os54 from "os";
41611
+ import path66 from "path";
41496
41612
  function emptySignals3() {
41497
41613
  return {
41498
41614
  dlpFindings: 0,
@@ -41540,7 +41656,7 @@ function readCredentials() {
41540
41656
  }
41541
41657
  function readCachedEtag() {
41542
41658
  try {
41543
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
41659
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
41544
41660
  return typeof raw.etag === "string" ? raw.etag : void 0;
41545
41661
  } catch {
41546
41662
  return void 0;
@@ -41548,7 +41664,7 @@ function readCachedEtag() {
41548
41664
  }
41549
41665
  function readCachedSyncIntervalHours() {
41550
41666
  try {
41551
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
41667
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
41552
41668
  return typeof raw.syncIntervalHours === "number" ? raw.syncIntervalHours : void 0;
41553
41669
  } catch {
41554
41670
  return void 0;
@@ -41565,7 +41681,7 @@ function effectiveSyncIntervalMs() {
41565
41681
  }
41566
41682
  function readSyncHealth() {
41567
41683
  try {
41568
- const raw = JSON.parse(fs63.readFileSync(syncHealthFile(), "utf-8"));
41684
+ const raw = JSON.parse(fs64.readFileSync(syncHealthFile(), "utf-8"));
41569
41685
  return {
41570
41686
  lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : void 0,
41571
41687
  lastChangedAt: typeof raw.lastChangedAt === "string" ? raw.lastChangedAt : void 0,
@@ -41580,17 +41696,17 @@ function readSyncHealth() {
41580
41696
  function writeSyncHealth(h) {
41581
41697
  try {
41582
41698
  const file2 = syncHealthFile();
41583
- const dir2 = path65.dirname(file2);
41584
- if (!fs63.existsSync(dir2)) fs63.mkdirSync(dir2, { recursive: true });
41699
+ const dir2 = path66.dirname(file2);
41700
+ if (!fs64.existsSync(dir2)) fs64.mkdirSync(dir2, { recursive: true });
41585
41701
  const tmp = `${file2}.${process.pid}.tmp`;
41586
- fs63.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
41587
- fs63.renameSync(tmp, file2);
41702
+ fs64.writeFileSync(tmp, JSON.stringify(h, null, 2) + "\n", "utf-8");
41703
+ fs64.renameSync(tmp, file2);
41588
41704
  } catch {
41589
41705
  }
41590
41706
  }
41591
41707
  function readCacheFetchedAt() {
41592
41708
  try {
41593
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
41709
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
41594
41710
  return typeof raw.fetchedAt === "string" ? raw.fetchedAt : void 0;
41595
41711
  } catch {
41596
41712
  return void 0;
@@ -41627,13 +41743,13 @@ function safeNode9Version() {
41627
41743
  let dir2 = __dirname;
41628
41744
  for (let up = 0; up < 5; up++) {
41629
41745
  try {
41630
- const pkg = JSON.parse(fs63.readFileSync(path65.join(dir2, "package.json"), "utf-8"));
41746
+ const pkg = JSON.parse(fs64.readFileSync(path66.join(dir2, "package.json"), "utf-8"));
41631
41747
  if (pkg.name === "@node9/proxy" || pkg.name === "node9-ai") {
41632
41748
  return pkg.version;
41633
41749
  }
41634
41750
  } catch {
41635
41751
  }
41636
- const parent = path65.dirname(dir2);
41752
+ const parent = path66.dirname(dir2);
41637
41753
  if (parent === dir2) break;
41638
41754
  dir2 = parent;
41639
41755
  }
@@ -41886,13 +42002,13 @@ function extractManagedConfig(body) {
41886
42002
  }
41887
42003
  function sweepStaleTmp(target) {
41888
42004
  try {
41889
- const dir2 = path65.dirname(target);
41890
- const prefix = `${path65.basename(target)}.`;
41891
- for (const name of fs63.readdirSync(dir2)) {
42005
+ const dir2 = path66.dirname(target);
42006
+ const prefix = `${path66.basename(target)}.`;
42007
+ for (const name of fs64.readdirSync(dir2)) {
41892
42008
  if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
41893
- const full = path65.join(dir2, name);
42009
+ const full = path66.join(dir2, name);
41894
42010
  try {
41895
- if (Date.now() - fs63.statSync(full).mtimeMs > 5 * 60 * 1e3) fs63.unlinkSync(full);
42011
+ if (Date.now() - fs64.statSync(full).mtimeMs > 5 * 60 * 1e3) fs64.unlinkSync(full);
41896
42012
  } catch {
41897
42013
  }
41898
42014
  }
@@ -41995,7 +42111,7 @@ async function pushBlastSnapshot(creds) {
41995
42111
  }
41996
42112
  async function pushPostureSnapshot(creds) {
41997
42113
  try {
41998
- const home = os53.homedir();
42114
+ const home = os54.homedir();
41999
42115
  const result = await runPosture({ home, cwd: home });
42000
42116
  await shipPosture(result, creds);
42001
42117
  } catch {
@@ -42169,7 +42285,7 @@ async function runCloudSync() {
42169
42285
  }
42170
42286
  function getCloudSyncStatus() {
42171
42287
  try {
42172
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
42288
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
42173
42289
  if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
42174
42290
  return {
42175
42291
  cached: true,
@@ -42186,7 +42302,7 @@ function getCloudSyncStatus() {
42186
42302
  }
42187
42303
  function getCloudRules() {
42188
42304
  try {
42189
- const raw = JSON.parse(fs63.readFileSync(rulesCacheFile(), "utf-8"));
42305
+ const raw = JSON.parse(fs64.readFileSync(rulesCacheFile(), "utf-8"));
42190
42306
  return Array.isArray(raw.rules) ? raw.rules : null;
42191
42307
  } catch {
42192
42308
  return null;
@@ -42256,12 +42372,12 @@ var init_sync = __esm({
42256
42372
  loop: "loops",
42257
42373
  "long-output-redacted": "longOutputRedactions"
42258
42374
  };
42259
- rulesCacheFile = () => path65.join(os53.homedir(), ".node9", "rules-cache.json");
42260
- rulesCacheBackupFile = () => path65.join(os53.homedir(), ".node9", "rules-cache.last-good.json");
42375
+ rulesCacheFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.json");
42376
+ rulesCacheBackupFile = () => path66.join(os54.homedir(), ".node9", "rules-cache.last-good.json");
42261
42377
  DEFAULT_INTERVAL_HOURS = 5;
42262
42378
  MIN_INTERVAL_SECONDS = 15;
42263
42379
  MAX_INTERVAL_SECONDS = 24 * 60 * 60;
42264
- syncHealthFile = () => path65.join(os53.homedir(), ".node9", "sync-health.json");
42380
+ syncHealthFile = () => path66.join(os54.homedir(), ".node9", "sync-health.json");
42265
42381
  STALE_MIN_MS = 3 * 60 * 60 * 1e3;
42266
42382
  STALE_MAX_MS = 24 * 60 * 60 * 1e3;
42267
42383
  STALE_FACTOR = 3;
@@ -42286,9 +42402,9 @@ __export(audit_shipper_exports, {
42286
42402
  startAuditShipper: () => startAuditShipper,
42287
42403
  writeWatermark: () => writeWatermark
42288
42404
  });
42289
- import fs64 from "fs";
42290
- import path66 from "path";
42291
- import os54 from "os";
42405
+ import fs65 from "fs";
42406
+ import path67 from "path";
42407
+ import os55 from "os";
42292
42408
  import crypto8 from "crypto";
42293
42409
  function exposureOf(v) {
42294
42410
  if (!v || typeof v !== "object") return null;
@@ -42298,21 +42414,21 @@ function exposureOf(v) {
42298
42414
  return { kind: o.kind, destinations: Math.max(0, Math.min(1e3, Math.floor(n))) };
42299
42415
  }
42300
42416
  function fileSignature(filePath) {
42301
- const fd = fs64.openSync(filePath, "r");
42417
+ const fd = fs65.openSync(filePath, "r");
42302
42418
  try {
42303
42419
  const buf = Buffer.alloc(512);
42304
- const read3 = fs64.readSync(fd, buf, 0, 512, 0);
42420
+ const read3 = fs65.readSync(fd, buf, 0, 512, 0);
42305
42421
  const slice = buf.subarray(0, read3);
42306
42422
  const nl = slice.indexOf(10);
42307
42423
  const firstLine = nl === -1 ? slice : slice.subarray(0, nl);
42308
42424
  return crypto8.createHash("sha256").update(firstLine).digest("hex").slice(0, 16);
42309
42425
  } finally {
42310
- fs64.closeSync(fd);
42426
+ fs65.closeSync(fd);
42311
42427
  }
42312
42428
  }
42313
42429
  function readWatermark(watermarkPath) {
42314
42430
  try {
42315
- const raw = JSON.parse(fs64.readFileSync(watermarkPath, "utf-8"));
42431
+ const raw = JSON.parse(fs65.readFileSync(watermarkPath, "utf-8"));
42316
42432
  if (typeof raw.fileSig === "string" && typeof raw.offset === "number" && raw.offset >= 0)
42317
42433
  return raw;
42318
42434
  } catch {
@@ -42321,8 +42437,8 @@ function readWatermark(watermarkPath) {
42321
42437
  }
42322
42438
  function writeWatermark(watermarkPath, wm) {
42323
42439
  const tmp = `${watermarkPath}.tmp`;
42324
- fs64.writeFileSync(tmp, JSON.stringify(wm));
42325
- fs64.renameSync(tmp, watermarkPath);
42440
+ fs65.writeFileSync(tmp, JSON.stringify(wm));
42441
+ fs65.renameSync(tmp, watermarkPath);
42326
42442
  }
42327
42443
  function buildWireRows(chunk2) {
42328
42444
  const lastNl = chunk2.lastIndexOf(10);
@@ -42409,11 +42525,11 @@ async function shipOnce(deps = {}) {
42409
42525
  if (!creds?.apiKey) return { status: "no-creds", shipped: 0 };
42410
42526
  const endpoint = buildBatchEndpoint(creds.apiUrl);
42411
42527
  if (!endpoint) return { status: "no-creds", shipped: 0 };
42412
- if (!fs64.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
42528
+ if (!fs65.existsSync(auditLogPath)) return { status: "idle", shipped: 0 };
42413
42529
  let shipped = 0;
42414
42530
  try {
42415
42531
  for (let chunkN = 0; chunkN < MAX_CHUNKS_PER_TICK; chunkN++) {
42416
- const size = fs64.statSync(auditLogPath).size;
42532
+ const size = fs65.statSync(auditLogPath).size;
42417
42533
  if (size === 0) break;
42418
42534
  const sig = fileSignature(auditLogPath);
42419
42535
  const wm = readWatermark(watermarkPath);
@@ -42421,12 +42537,12 @@ async function shipOnce(deps = {}) {
42421
42537
  if (offset >= size) break;
42422
42538
  const toRead = Math.min(size - offset, MAX_CHUNK_BYTES);
42423
42539
  const buf = Buffer.alloc(toRead);
42424
- const fd = fs64.openSync(auditLogPath, "r");
42540
+ const fd = fs65.openSync(auditLogPath, "r");
42425
42541
  let read3;
42426
42542
  try {
42427
- read3 = fs64.readSync(fd, buf, 0, toRead, offset);
42543
+ read3 = fs65.readSync(fd, buf, 0, toRead, offset);
42428
42544
  } finally {
42429
- fs64.closeSync(fd);
42545
+ fs65.closeSync(fd);
42430
42546
  }
42431
42547
  const { rows, consumed } = buildWireRows(buf.subarray(0, read3));
42432
42548
  if (consumed === 0) break;
@@ -42473,8 +42589,8 @@ async function shipOnce(deps = {}) {
42473
42589
  }
42474
42590
  function shipLagBytes(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK) {
42475
42591
  try {
42476
- if (!fs64.existsSync(auditLogPath)) return 0;
42477
- const size = fs64.statSync(auditLogPath).size;
42592
+ if (!fs65.existsSync(auditLogPath)) return 0;
42593
+ const size = fs65.statSync(auditLogPath).size;
42478
42594
  const wm = readWatermark(watermarkPath);
42479
42595
  if (!wm) return size;
42480
42596
  if (wm.fileSig !== fileSignature(auditLogPath)) return size;
@@ -42499,20 +42615,20 @@ function startAuditShipper() {
42499
42615
  }
42500
42616
  function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHIP_WATERMARK, maxBytes = MAX_CHUNK_BYTES) {
42501
42617
  try {
42502
- const fd = fs64.openSync(auditLogPath, "r");
42618
+ const fd = fs65.openSync(auditLogPath, "r");
42503
42619
  let chunk2;
42504
42620
  let truncated;
42505
42621
  try {
42506
- const size = fs64.fstatSync(fd).size;
42622
+ const size = fs65.fstatSync(fd).size;
42507
42623
  const wm = readWatermark(watermarkPath);
42508
42624
  const offset = wm && wm.fileSig === fileSignature(auditLogPath) && wm.offset <= size ? wm.offset : 0;
42509
42625
  const length = Math.min(size - offset, maxBytes);
42510
42626
  const buffer = Buffer.alloc(length);
42511
- const read3 = fs64.readSync(fd, buffer, 0, length, offset);
42627
+ const read3 = fs65.readSync(fd, buffer, 0, length, offset);
42512
42628
  chunk2 = buffer.subarray(0, read3);
42513
42629
  truncated = size - offset > length;
42514
42630
  } finally {
42515
- fs64.closeSync(fd);
42631
+ fs65.closeSync(fd);
42516
42632
  }
42517
42633
  const { rows } = buildWireRows(chunk2);
42518
42634
  const oldest = rows.reduce((old, row) => {
@@ -42549,7 +42665,7 @@ var init_audit_shipper = __esm({
42549
42665
  "policy",
42550
42666
  "abandoned"
42551
42667
  ]);
42552
- AUDIT_SHIP_WATERMARK = path66.join(os54.homedir(), ".node9", "audit-ship.json");
42668
+ AUDIT_SHIP_WATERMARK = path67.join(os55.homedir(), ".node9", "audit-ship.json");
42553
42669
  DEFAULT_INTERVAL_MS = 2e4;
42554
42670
  MAX_BATCH = 500;
42555
42671
  MAX_CHUNK_BYTES = 4 * 1024 * 1024;
@@ -42631,12 +42747,12 @@ var init_decision = __esm({
42631
42747
  });
42632
42748
 
42633
42749
  // src/daemon/dlp-scanner.ts
42634
- import fs65 from "fs";
42635
- import path67 from "path";
42636
- import os55 from "os";
42750
+ import fs66 from "fs";
42751
+ import path68 from "path";
42752
+ import os56 from "os";
42637
42753
  function loadIndex() {
42638
42754
  try {
42639
- const raw = JSON.parse(fs65.readFileSync(INDEX_FILE, "utf-8"));
42755
+ const raw = JSON.parse(fs66.readFileSync(INDEX_FILE, "utf-8"));
42640
42756
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
42641
42757
  const r = raw;
42642
42758
  if (r.offsets && typeof r.offsets === "object") return r.offsets;
@@ -42648,13 +42764,13 @@ function loadIndex() {
42648
42764
  }
42649
42765
  function saveIndex(index) {
42650
42766
  try {
42651
- fs65.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
42767
+ fs66.writeFileSync(INDEX_FILE, JSON.stringify(index), { encoding: "utf-8", mode: 384 });
42652
42768
  } catch {
42653
42769
  }
42654
42770
  }
42655
42771
  function appendAuditEntry(entry) {
42656
42772
  try {
42657
- fs65.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
42773
+ fs66.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
42658
42774
  } catch {
42659
42775
  }
42660
42776
  }
@@ -42668,52 +42784,52 @@ function runDlpScan() {
42668
42784
  canaryVals = [];
42669
42785
  }
42670
42786
  const canaryFindings = [];
42671
- if (!fs65.existsSync(PROJECTS_DIR2)) return;
42787
+ if (!fs66.existsSync(PROJECTS_DIR2)) return;
42672
42788
  const index = loadIndex();
42673
42789
  const seenThisPass = /* @__PURE__ */ new Set();
42674
42790
  const newFindings = [];
42675
42791
  let updated = false;
42676
42792
  let projDirs;
42677
42793
  try {
42678
- projDirs = fs65.readdirSync(PROJECTS_DIR2);
42794
+ projDirs = fs66.readdirSync(PROJECTS_DIR2);
42679
42795
  } catch {
42680
42796
  return;
42681
42797
  }
42682
42798
  for (const proj of projDirs) {
42683
- const projPath = path67.join(PROJECTS_DIR2, proj);
42799
+ const projPath = path68.join(PROJECTS_DIR2, proj);
42684
42800
  try {
42685
- if (!fs65.lstatSync(projPath).isDirectory()) continue;
42686
- const real = fs65.realpathSync(projPath);
42687
- if (!real.startsWith(PROJECTS_DIR2 + path67.sep) && real !== PROJECTS_DIR2) continue;
42801
+ if (!fs66.lstatSync(projPath).isDirectory()) continue;
42802
+ const real = fs66.realpathSync(projPath);
42803
+ if (!real.startsWith(PROJECTS_DIR2 + path68.sep) && real !== PROJECTS_DIR2) continue;
42688
42804
  } catch {
42689
42805
  continue;
42690
42806
  }
42691
42807
  let files;
42692
42808
  try {
42693
- files = fs65.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
42809
+ files = fs66.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
42694
42810
  } catch {
42695
42811
  continue;
42696
42812
  }
42697
42813
  for (const file2 of files) {
42698
- const filePath = path67.join(projPath, file2);
42814
+ const filePath = path68.join(projPath, file2);
42699
42815
  const lastOffset = index[filePath] ?? 0;
42700
42816
  let size;
42701
42817
  try {
42702
- size = fs65.statSync(filePath).size;
42818
+ size = fs66.statSync(filePath).size;
42703
42819
  } catch {
42704
42820
  continue;
42705
42821
  }
42706
42822
  if (size <= lastOffset) continue;
42707
42823
  let fd;
42708
42824
  try {
42709
- fd = fs65.openSync(filePath, "r");
42825
+ fd = fs66.openSync(filePath, "r");
42710
42826
  } catch {
42711
42827
  continue;
42712
42828
  }
42713
42829
  try {
42714
42830
  const chunkSize = size - lastOffset;
42715
42831
  const buf = Buffer.alloc(chunkSize);
42716
- fs65.readSync(fd, buf, 0, chunkSize, lastOffset);
42832
+ fs66.readSync(fd, buf, 0, chunkSize, lastOffset);
42717
42833
  const chunk2 = buf.toString("utf-8");
42718
42834
  for (const line of chunk2.split("\n")) {
42719
42835
  if (!line.trim()) continue;
@@ -42734,7 +42850,7 @@ function runDlpScan() {
42734
42850
  const match = scanText(text);
42735
42851
  const canary = canaryVals.length > 0 ? matchCanary(text, canaryVals) : null;
42736
42852
  if (!match && !canary) continue;
42737
- const projLabel = decodeURIComponent(proj).replace(os55.homedir(), "~").slice(0, 40);
42853
+ const projLabel = decodeURIComponent(proj).replace(os56.homedir(), "~").slice(0, 40);
42738
42854
  const ts = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
42739
42855
  if (canary) {
42740
42856
  const rec = canaryById.get(canary.id);
@@ -42784,7 +42900,7 @@ function runDlpScan() {
42784
42900
  updated = true;
42785
42901
  } finally {
42786
42902
  try {
42787
- fs65.closeSync(fd);
42903
+ fs66.closeSync(fd);
42788
42904
  } catch {
42789
42905
  }
42790
42906
  }
@@ -42843,15 +42959,15 @@ var init_dlp_scanner = __esm({
42843
42959
  init_registry2();
42844
42960
  init_native();
42845
42961
  init_state3();
42846
- INDEX_FILE = path67.join(os55.homedir(), ".node9", "dlp-index.json");
42847
- PROJECTS_DIR2 = path67.join(os55.homedir(), ".claude", "projects");
42962
+ INDEX_FILE = path68.join(os56.homedir(), ".node9", "dlp-index.json");
42963
+ PROJECTS_DIR2 = path68.join(os56.homedir(), ".claude", "projects");
42848
42964
  }
42849
42965
  });
42850
42966
 
42851
42967
  // src/daemon/mcp-reconciler.ts
42852
- import fs66 from "fs";
42853
- import path68 from "path";
42854
- import os56 from "os";
42968
+ import fs67 from "fs";
42969
+ import path69 from "path";
42970
+ import os57 from "os";
42855
42971
  import crypto9 from "crypto";
42856
42972
  function idKey(e) {
42857
42973
  const cmdHash = crypto9.createHash("sha256").update(`${e.command} ${e.args.join(" ")}`).digest("hex").slice(0, 16);
@@ -42859,7 +42975,7 @@ function idKey(e) {
42859
42975
  }
42860
42976
  function loadBaseline() {
42861
42977
  try {
42862
- const raw = JSON.parse(fs66.readFileSync(BASELINE_FILE2, "utf-8"));
42978
+ const raw = JSON.parse(fs67.readFileSync(BASELINE_FILE2, "utf-8"));
42863
42979
  return new Set(Array.isArray(raw) ? raw : []);
42864
42980
  } catch {
42865
42981
  return /* @__PURE__ */ new Set();
@@ -42867,7 +42983,7 @@ function loadBaseline() {
42867
42983
  }
42868
42984
  function saveBaseline(keys) {
42869
42985
  try {
42870
- fs66.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
42986
+ fs67.writeFileSync(BASELINE_FILE2, JSON.stringify([...keys].slice(-BASELINE_CAP)), {
42871
42987
  mode: 384
42872
42988
  });
42873
42989
  } catch {
@@ -43055,7 +43171,7 @@ var init_mcp_reconciler = __esm({
43055
43171
  init_cloud();
43056
43172
  init_audit();
43057
43173
  init_mcp_pin();
43058
- BASELINE_FILE2 = path68.join(os56.homedir(), ".node9", "mcp-baseline.json");
43174
+ BASELINE_FILE2 = path69.join(os57.homedir(), ".node9", "mcp-baseline.json");
43059
43175
  BASELINE_CAP = 500;
43060
43176
  DEFAULT_INTERVAL_MIN = 60;
43061
43177
  DEFAULT_STALE_DAYS = 7;
@@ -43129,114 +43245,6 @@ var init_hook_heal = __esm({
43129
43245
  }
43130
43246
  });
43131
43247
 
43132
- // src/daemon/startup-log.ts
43133
- import fs67 from "fs";
43134
- import path69 from "path";
43135
- import os57 from "os";
43136
- function capStartupLog(file2) {
43137
- try {
43138
- if (fs67.statSync(file2).size > MAX_STARTUP_LOG_BYTES) fs67.truncateSync(file2);
43139
- } catch {
43140
- }
43141
- }
43142
- function openStartupLogFd() {
43143
- try {
43144
- const file2 = DAEMON_STARTUP_LOG();
43145
- const dir2 = path69.dirname(file2);
43146
- if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
43147
- capStartupLog(file2);
43148
- return fs67.openSync(file2, "a");
43149
- } catch {
43150
- return void 0;
43151
- }
43152
- }
43153
- function recordStartupState(outcome, kind, detail) {
43154
- try {
43155
- if (outcome === "starting") {
43156
- const prev = readStartupState();
43157
- if (prev?.outcome === "starting") {
43158
- const prevAt = new Date(prev.at).getTime();
43159
- const age = Math.abs(Date.now() - prevAt);
43160
- if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
43161
- }
43162
- }
43163
- const file2 = DAEMON_STARTUP_STATE();
43164
- const dir2 = path69.dirname(file2);
43165
- if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
43166
- const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
43167
- if (kind) state.kind = kind;
43168
- if (detail) state.detail = detail.slice(0, MAX_DETAIL);
43169
- const tmp = `${file2}.${process.pid}.tmp`;
43170
- try {
43171
- fs67.writeFileSync(tmp, JSON.stringify(state), "utf-8");
43172
- fs67.renameSync(tmp, file2);
43173
- } catch (err2) {
43174
- try {
43175
- fs67.unlinkSync(tmp);
43176
- } catch {
43177
- }
43178
- throw err2;
43179
- }
43180
- } catch {
43181
- }
43182
- }
43183
- function readStartupState() {
43184
- try {
43185
- const raw = fs67.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
43186
- const s = JSON.parse(raw);
43187
- if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
43188
- return s;
43189
- } catch {
43190
- return null;
43191
- }
43192
- }
43193
- function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
43194
- const s = readStartupState();
43195
- if (!s) return null;
43196
- const at = new Date(s.at);
43197
- if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
43198
- switch (s.outcome) {
43199
- case "ok":
43200
- case "ok-elsewhere":
43201
- return null;
43202
- case "starting":
43203
- if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
43204
- return {
43205
- kind: "did-not-start",
43206
- detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
43207
- at,
43208
- // `at` is the FIRST attempt of the streak, not the most recent one.
43209
- label: "start attempts failing since"
43210
- };
43211
- case "failed":
43212
- return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
43213
- default:
43214
- return null;
43215
- }
43216
- }
43217
- function logDaemonStartup(kind, detail) {
43218
- try {
43219
- const file2 = DAEMON_STARTUP_LOG();
43220
- const dir2 = path69.dirname(file2);
43221
- if (!fs67.existsSync(dir2)) fs67.mkdirSync(dir2, { recursive: true });
43222
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-startup:${kind}${detail ? ` ${detail}` : ""}
43223
- `;
43224
- fs67.appendFileSync(file2, line, "utf-8");
43225
- } catch {
43226
- }
43227
- }
43228
- var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
43229
- var init_startup_log = __esm({
43230
- "src/daemon/startup-log.ts"() {
43231
- "use strict";
43232
- DAEMON_STARTUP_LOG = () => path69.join(os57.homedir(), ".node9", "daemon-startup.log");
43233
- MAX_STARTUP_LOG_BYTES = 256 * 1024;
43234
- DAEMON_STARTUP_STATE = () => path69.join(os57.homedir(), ".node9", "daemon-startup-state.json");
43235
- MAX_DETAIL = 200;
43236
- STARTING_GRACE_MS = 90 * 1e3;
43237
- }
43238
- });
43239
-
43240
43248
  // src/supply-chain/zip.ts
43241
43249
  import fs68 from "fs";
43242
43250
  import zlib from "zlib";
@@ -77568,7 +77576,7 @@ function buildReportJson(input) {
77568
77576
  var BLOCK_REASON_LABELS = {
77569
77577
  timeout: "Approval timeout",
77570
77578
  "smart-rule-block": "Smart rule",
77571
- "observe-mode-dlp-would-block": "DLP (observe)",
77579
+ "observe-mode-dlp-would-block": "DLP (audit)",
77572
77580
  "persistent-deny": "Persistent deny",
77573
77581
  "local-decision": "User denied",
77574
77582
  "dlp-block": "DLP block",
@@ -77608,10 +77616,10 @@ function fmtCost2(usd) {
77608
77616
  return "$" + usd.toFixed(2);
77609
77617
  }
77610
77618
  function registerReportCommand(program2) {
77611
- 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) => {
77619
+ 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) => {
77612
77620
  const period = ["today", "7d", "30d", "90d", "month"].includes(
77613
77621
  options.period
77614
- ) ? options.period : "7d";
77622
+ ) ? options.period : "30d";
77615
77623
  const excludeTests = options.tests === false;
77616
77624
  const { data, hasAuditFile, responseDlpEntries } = aggregateReportFromAudit(period, {
77617
77625
  excludeTests
@@ -77809,7 +77817,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
77809
77817
  );
77810
77818
  summaryRow(
77811
77819
  observeDlp > 0 ? chalk20.blue("\u{1F441}") : chalk20.dim("\u{1F441}"),
77812
- "DLP (observe)",
77820
+ "DLP (audit)",
77813
77821
  observeDlp,
77814
77822
  observeDlp > 0 ? "would-block in strict mode" : void 0,
77815
77823
  observeDlp > 0 ? (s) => chalk20.blue(s) : (s) => chalk20.dim(s)
@@ -78182,6 +78190,7 @@ import chalk22 from "chalk";
78182
78190
  import fs88 from "fs";
78183
78191
  import path86 from "path";
78184
78192
  import os73 from "os";
78193
+ init_posture();
78185
78194
  function printAgentSection(label3, hookPairs, wrapped) {
78186
78195
  console.log(chalk22.bold(` ${label3}`));
78187
78196
  for (const { name, present } of hookPairs) {
@@ -78262,6 +78271,13 @@ function registerStatusCommand(program2) {
78262
78271
  console.log("");
78263
78272
  const modeLabel = levelLabel(settings.mode);
78264
78273
  console.log(` Mode: ${modeLabel}`);
78274
+ const posture = await runPosture().catch(() => null);
78275
+ if (posture) {
78276
+ const tier = posture.tier === "good" ? chalk22.green("Good") : posture.tier === "at-risk" ? chalk22.yellow("At risk") : chalk22.red("Critical");
78277
+ console.log(
78278
+ ` Score: ${chalk22.bold(`${posture.score}/100`)} (${tier})` + chalk22.gray(" \xB7 node9 posture for the breakdown")
78279
+ );
78280
+ }
78265
78281
  const wsGoverned = mergedConfig.policySource === "workspace";
78266
78282
  if (wsGoverned) {
78267
78283
  console.log(` Policy: ${chalk22.cyan("Workspace config (app.node9.ai)")}`);
@@ -79317,6 +79333,235 @@ import fs92 from "fs";
79317
79333
  import os78 from "os";
79318
79334
  import path91 from "path";
79319
79335
  import { spawnSync as spawnSync5 } from "child_process";
79336
+
79337
+ // src/cli/commands/egress.ts
79338
+ init_config();
79339
+ init_keyed_guard();
79340
+ init_dist();
79341
+ init_egress_config();
79342
+ init_egress_config();
79343
+ import chalk27 from "chalk";
79344
+ function guard(fn) {
79345
+ try {
79346
+ fn();
79347
+ return true;
79348
+ } catch (err2) {
79349
+ console.error(chalk27.red(`
79350
+ \u2717 ${err2.message}
79351
+ `));
79352
+ process.exitCode = 1;
79353
+ return false;
79354
+ }
79355
+ }
79356
+ function mutate(action, change) {
79357
+ if (!cliGuardPolicyWrite(action)) return false;
79358
+ return guard(() => setEgress(change));
79359
+ }
79360
+ function addHost(list2, host2) {
79361
+ if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
79362
+ return guard(() => addEgressHost(list2, host2));
79363
+ }
79364
+ function exempt2(address) {
79365
+ if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
79366
+ return guard(() => addSsrfExemption(address));
79367
+ }
79368
+ var INTERNAL_FIELDS = {
79369
+ allowed: { ssrfStrict: false, allowPrivate: true },
79370
+ listed: { ssrfStrict: false, allowPrivate: false },
79371
+ blocked: { ssrfStrict: true, allowPrivate: false }
79372
+ };
79373
+ var INTERNAL_SAID = {
79374
+ allowed: "reachable without listing them",
79375
+ listed: "reachable only if they are on your allowlist",
79376
+ blocked: "blocked"
79377
+ };
79378
+ function describeInternalState(state) {
79379
+ return INTERNAL_SAID[state];
79380
+ }
79381
+ function readInternalState(e) {
79382
+ if (e.ssrfStrict === true) return "blocked";
79383
+ return e.allowPrivate === false ? "listed" : "allowed";
79384
+ }
79385
+ function setInternal(value) {
79386
+ const state = value.trim().toLowerCase();
79387
+ if (!(state in INTERNAL_FIELDS)) {
79388
+ console.error(
79389
+ chalk27.red(`
79390
+ \u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk27.gray(
79391
+ "\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"
79392
+ )
79393
+ );
79394
+ process.exitCode = 1;
79395
+ return;
79396
+ }
79397
+ if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
79398
+ _resetConfigCache();
79399
+ const effective = readInternalState(getConfig().policy.egress);
79400
+ if (effective !== state) {
79401
+ console.log(
79402
+ chalk27.yellow(
79403
+ `
79404
+ \u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
79405
+ Change it in the dashboard, Enforcement \u2192 Network.
79406
+ `
79407
+ )
79408
+ );
79409
+ return;
79410
+ }
79411
+ const line = `
79412
+ \u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
79413
+ `;
79414
+ console.log(state === "allowed" ? chalk27.yellow(line) : chalk27.green(line));
79415
+ }
79416
+ function showFloor(e, ssrfStrictSource) {
79417
+ console.log(
79418
+ chalk27.gray("\n Protected addresses") + chalk27.gray(" \u2014 in shell commands and declared URLs")
79419
+ );
79420
+ console.log(
79421
+ chalk27.gray(
79422
+ " always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
79423
+ )
79424
+ );
79425
+ const state = readInternalState(e);
79426
+ const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
79427
+ const STATE_LABEL = {
79428
+ allowed: "allowed",
79429
+ listed: "allowlist only",
79430
+ blocked: "blocked"
79431
+ };
79432
+ console.log(
79433
+ ` 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]}`)
79434
+ );
79435
+ console.log(chalk27.gray(` set by: ${by}`));
79436
+ const exemptions = e.ssrfAllow ?? [];
79437
+ console.log(chalk27.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
79438
+ console.log(
79439
+ chalk27.gray(
79440
+ " 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."
79441
+ )
79442
+ );
79443
+ }
79444
+ function showStatus() {
79445
+ const cfg = getConfig();
79446
+ const e = cfg.policy.egress;
79447
+ 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");
79448
+ console.log(chalk27.cyan.bold("\n\u{1F310} Egress control"));
79449
+ console.log(" State: " + state);
79450
+ if (cfg.policySource === "workspace") {
79451
+ console.log(
79452
+ chalk27.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
79453
+ );
79454
+ }
79455
+ console.log(
79456
+ chalk27.gray(
79457
+ ` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
79458
+ )
79459
+ );
79460
+ showFloor(e, cfg.ssrfStrictSource);
79461
+ if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
79462
+ if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
79463
+ if (!e.enabled) {
79464
+ console.log(chalk27.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
79465
+ console.log(chalk27.gray(" node9 egress lock (hard-block unknown hosts)"));
79466
+ }
79467
+ console.log("");
79468
+ }
79469
+ function registerEgressCommand(program2) {
79470
+ const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
79471
+ egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
79472
+ if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
79473
+ console.log(chalk27.green("\n\u2713 Egress is now watched (review mode)."));
79474
+ console.log(
79475
+ chalk27.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
79476
+ );
79477
+ console.log(
79478
+ chalk27.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
79479
+ );
79480
+ });
79481
+ egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
79482
+ if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
79483
+ console.log(chalk27.green("\n\u2713 Egress is now locked (block mode)."));
79484
+ console.log(chalk27.gray(" Routine hosts are still allowed; unknown hosts are denied."));
79485
+ console.log(chalk27.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
79486
+ });
79487
+ egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
79488
+ if (!addHost("allow", host2)) return;
79489
+ console.log(chalk27.green(`
79490
+ \u2713 Allowed egress to ${host2}.
79491
+ `));
79492
+ });
79493
+ egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
79494
+ if (!addHost("deny", host2)) return;
79495
+ console.log(chalk27.green(`
79496
+ \u2713 Denied egress to ${host2}.
79497
+ `));
79498
+ });
79499
+ egress.command("off").description("Turn egress control off").action(() => {
79500
+ if (!mutate("egress off", { enabled: false })) return;
79501
+ console.log(
79502
+ chalk27.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
79503
+ );
79504
+ });
79505
+ egress.command("internal <allowed|listed|blocked>").description(
79506
+ "How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
79507
+ ).action((value) => setInternal(value));
79508
+ egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
79509
+ const v = value.trim().toLowerCase();
79510
+ if (v !== "on" && v !== "off") {
79511
+ console.error(chalk27.red(`
79512
+ \u2717 Expected "on" or "off", got "${value}".
79513
+ `));
79514
+ process.exitCode = 1;
79515
+ return;
79516
+ }
79517
+ const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
79518
+ console.log(chalk27.gray(`
79519
+ (\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
79520
+ setInternal(state);
79521
+ });
79522
+ egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
79523
+ const a = normalizeEgressHost(address);
79524
+ const slash = a.indexOf("/");
79525
+ const base2 = slash === -1 ? a : a.slice(0, slash);
79526
+ const prefixText = slash === -1 ? null : a.slice(slash + 1);
79527
+ if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
79528
+ console.error(
79529
+ chalk27.red(`
79530
+ \u2717 "${address}" is not an address or a range.`) + chalk27.gray(
79531
+ "\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"
79532
+ )
79533
+ );
79534
+ process.exitCode = 1;
79535
+ return;
79536
+ }
79537
+ const m = classifySsrf(base2);
79538
+ if (m && !m.overridable) {
79539
+ console.error(
79540
+ chalk27.red(`
79541
+ \u2717 ${a} cannot be exempted.`) + chalk27.gray(
79542
+ `
79543
+ ${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
79544
+ `
79545
+ )
79546
+ );
79547
+ process.exitCode = 1;
79548
+ return;
79549
+ }
79550
+ if (!exempt2(a)) return;
79551
+ console.log(chalk27.green(`
79552
+ \u2713 ${a} is exempt from the floor.`));
79553
+ if (!m)
79554
+ console.log(
79555
+ chalk27.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
79556
+ );
79557
+ else console.log("");
79558
+ });
79559
+ egress.command("status").description("Show the current egress state").action(showStatus);
79560
+ egress.action(showStatus);
79561
+ }
79562
+
79563
+ // src/mcp-server/index.ts
79564
+ init_agent_wiring();
79320
79565
  function ok(id, result) {
79321
79566
  return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
79322
79567
  }
@@ -79450,7 +79695,7 @@ var TOOLS = [
79450
79695
  },
79451
79696
  {
79452
79697
  name: "node9_scan",
79453
- 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.",
79698
+ 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.",
79454
79699
  inputSchema: {
79455
79700
  type: "object",
79456
79701
  properties: {
@@ -79470,8 +79715,8 @@ var TOOLS = [
79470
79715
  properties: {
79471
79716
  period: {
79472
79717
  type: "string",
79473
- enum: ["today", "7d", "30d", "month"],
79474
- description: "Time period for the report (default: 7d)."
79718
+ enum: ["today", "7d", "30d", "90d", "month"],
79719
+ description: "Time period for the report (default: 30d)."
79475
79720
  },
79476
79721
  no_tests: {
79477
79722
  type: "boolean",
@@ -79605,7 +79850,7 @@ function handleStatus() {
79605
79850
  const wsGoverned = config.policySource === "workspace";
79606
79851
  const activeShields = wsGoverned ? config.policy.appliedShields ?? [] : readActiveShields();
79607
79852
  const lines = [];
79608
- lines.push(`Mode: ${settings.mode}`);
79853
+ lines.push(`Mode: ${levelOf(settings.mode)}`);
79609
79854
  if (wsGoverned) {
79610
79855
  lines.push(
79611
79856
  "Policy source: workspace config (app.node9.ai) \u2014 local config files are ignored for policy"
@@ -79621,6 +79866,12 @@ function handleStatus() {
79621
79866
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
79622
79867
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
79623
79868
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
79869
+ lines.push("Agent hooks:");
79870
+ for (const a of getAgentWiring()) {
79871
+ const hooked = a.hooks.some((h) => h.wired);
79872
+ const state = !a.installed ? "not installed" : hooked ? "wired" : a.isProtected ? "protected through MCP" : "not wired";
79873
+ lines.push(` ${a.label}: ${state}`);
79874
+ }
79624
79875
  const projectConfig = path91.join(process.cwd(), "node9.config.json");
79625
79876
  const globalConfig = path91.join(os78.homedir(), ".node9", "config.json");
79626
79877
  const presentLabel = wsGoverned ? "present \u2014 ignored (workspace config governs)" : "present";
@@ -79713,6 +79964,11 @@ function handleShieldDisable(args) {
79713
79964
  if (disabledApp) recordUserDecision(disabledApp.slug, "off");
79714
79965
  return `Shield "${name}" disabled.`;
79715
79966
  }
79967
+ function internalAddressLine(e) {
79968
+ const configured = readInternalState(e);
79969
+ const state = !e.enabled && configured === "listed" ? "allowed" : configured;
79970
+ return `Internal addresses: ${state} \u2014 loopback and the private ranges are ${describeInternalState(state)}.`;
79971
+ }
79716
79972
  function handleEgressStatus() {
79717
79973
  const cfgEgress = getConfig();
79718
79974
  const e = cfgEgress.policy.egress;
@@ -79734,7 +79990,7 @@ function handleEgressStatus() {
79734
79990
  // actually covers is measured in egress.integration.test.ts.
79735
79991
  "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.",
79736
79992
  "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.",
79737
- `Internal addresses: ${e.ssrfStrict ? "on" : "off"} \u2014 loopback and the private ranges are ${e.ssrfStrict ? "blocked too" : "reachable"}.`,
79993
+ internalAddressLine(e),
79738
79994
  `Floor exemptions: ${e.ssrfAllow?.length ? e.ssrfAllow.join(", ") : "(none)"}`
79739
79995
  ];
79740
79996
  return lines.join("\n");
@@ -80057,7 +80313,7 @@ function registerMcpServerCommand(program2) {
80057
80313
  init_trusted_hosts();
80058
80314
  init_keyed_guard();
80059
80315
  init_trusted_recipients();
80060
- import chalk27 from "chalk";
80316
+ import chalk28 from "chalk";
80061
80317
  function isValidHost(host2) {
80062
80318
  return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host2);
80063
80319
  }
@@ -80068,17 +80324,17 @@ function registerTrustCommand(program2) {
80068
80324
  const normalized = normalizeHost2(host2.trim());
80069
80325
  if (!isValidHost(normalized)) {
80070
80326
  console.error(
80071
- chalk27.red(`
80327
+ chalk28.red(`
80072
80328
  \u274C Invalid host: "${host2}"
80073
- `) + chalk27.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
80329
+ `) + chalk28.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
80074
80330
  );
80075
80331
  process.exit(1);
80076
80332
  }
80077
80333
  addTrustedHost(normalized);
80078
- console.log(chalk27.green(`
80334
+ console.log(chalk28.green(`
80079
80335
  \u2705 ${normalized} added to trusted hosts.`));
80080
80336
  console.log(
80081
- chalk27.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
80337
+ chalk28.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
80082
80338
  );
80083
80339
  });
80084
80340
  trustCmd.command("remove <host>").description("Remove a trusted host").action((host2) => {
@@ -80086,27 +80342,27 @@ function registerTrustCommand(program2) {
80086
80342
  const normalized = normalizeHost2(host2.trim());
80087
80343
  const removed = removeTrustedHost(normalized);
80088
80344
  if (!removed) {
80089
- console.error(chalk27.yellow(`
80345
+ console.error(chalk28.yellow(`
80090
80346
  \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
80091
80347
  `));
80092
80348
  process.exit(1);
80093
80349
  }
80094
- console.log(chalk27.green(`
80350
+ console.log(chalk28.green(`
80095
80351
  \u2705 ${normalized} removed from trusted hosts.
80096
80352
  `));
80097
80353
  });
80098
80354
  trustCmd.command("list").description("Show all trusted hosts").action(() => {
80099
80355
  const hosts = readTrustedHosts();
80100
80356
  if (hosts.length === 0) {
80101
- console.log(chalk27.gray("\n No trusted hosts configured.\n"));
80102
- console.log(` Add one: ${chalk27.cyan("node9 trust add api.mycompany.com")}
80357
+ console.log(chalk28.gray("\n No trusted hosts configured.\n"));
80358
+ console.log(` Add one: ${chalk28.cyan("node9 trust add api.mycompany.com")}
80103
80359
  `);
80104
80360
  return;
80105
80361
  }
80106
- console.log(chalk27.bold("\n\u{1F513} Trusted Hosts\n"));
80362
+ console.log(chalk28.bold("\n\u{1F513} Trusted Hosts\n"));
80107
80363
  for (const entry of hosts) {
80108
80364
  const date = new Date(entry.addedAt).toLocaleDateString();
80109
- console.log(` ${chalk27.cyan(entry.host.padEnd(40))} ${chalk27.gray(`added ${date}`)}`);
80365
+ console.log(` ${chalk28.cyan(entry.host.padEnd(40))} ${chalk28.gray(`added ${date}`)}`);
80110
80366
  }
80111
80367
  console.log("");
80112
80368
  });
@@ -80118,41 +80374,41 @@ function registerTrustCommand(program2) {
80118
80374
  const stored = addTrustedRecipient(value, "user");
80119
80375
  if (!stored) {
80120
80376
  console.error(
80121
- chalk27.red(`
80377
+ chalk28.red(`
80122
80378
  \u274C Not a destination: "${value}"
80123
- `) + chalk27.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
80379
+ `) + chalk28.gray(" Use an email address, a hostname or URL, or an IBAN.\n")
80124
80380
  );
80125
80381
  process.exit(1);
80126
80382
  }
80127
- console.log(chalk27.green(`
80383
+ console.log(chalk28.green(`
80128
80384
  \u2705 ${stored} added to trusted recipients.
80129
80385
  `));
80130
80386
  });
80131
80387
  rec.command("remove <value>").description("Remove a trusted recipient").action((value) => {
80132
80388
  if (!cliGuardPolicyWrite(`trust recipient remove ${value}`)) return;
80133
80389
  if (!removeTrustedRecipient(value)) {
80134
- console.error(chalk27.yellow(`
80390
+ console.error(chalk28.yellow(`
80135
80391
  \u26A0\uFE0F "${value}" is not in the trusted recipients list.
80136
80392
  `));
80137
80393
  process.exit(1);
80138
80394
  }
80139
- console.log(chalk27.green(`
80395
+ console.log(chalk28.green(`
80140
80396
  \u2705 ${value} removed from trusted recipients.
80141
80397
  `));
80142
80398
  });
80143
80399
  rec.command("list").description("Show all trusted recipients").action(() => {
80144
80400
  const entries = readTrustedRecipients();
80145
80401
  if (entries.length === 0) {
80146
- console.log(chalk27.gray("\n No trusted recipients.\n"));
80147
- console.log(` Add one: ${chalk27.cyan("node9 trust recipient add partner@example.com")}
80402
+ console.log(chalk28.gray("\n No trusted recipients.\n"));
80403
+ console.log(` Add one: ${chalk28.cyan("node9 trust recipient add partner@example.com")}
80148
80404
  `);
80149
80405
  return;
80150
80406
  }
80151
- console.log(chalk27.bold("\n\u{1F513} Trusted Recipients\n"));
80407
+ console.log(chalk28.bold("\n\u{1F513} Trusted Recipients\n"));
80152
80408
  for (const e of entries) {
80153
80409
  const date = new Date(e.addedAt).toLocaleDateString();
80154
80410
  console.log(
80155
- ` ${chalk27.cyan(e.value.padEnd(44))} ${chalk27.gray(`added ${date} (${e.addedBy})`)}`
80411
+ ` ${chalk28.cyan(e.value.padEnd(44))} ${chalk28.gray(`added ${date} (${e.addedBy})`)}`
80156
80412
  );
80157
80413
  }
80158
80414
  console.log("");
@@ -80161,13 +80417,13 @@ function registerTrustCommand(program2) {
80161
80417
 
80162
80418
  // src/cli/commands/mcp-pin.ts
80163
80419
  init_mcp_pin();
80164
- import chalk29 from "chalk";
80420
+ import chalk30 from "chalk";
80165
80421
  import fs93 from "fs";
80166
80422
 
80167
80423
  // src/cli/commands/mcp-gateway-cmd.ts
80168
80424
  init_mcp_wrap();
80169
80425
  init_mcp_status();
80170
- import chalk28 from "chalk";
80426
+ import chalk29 from "chalk";
80171
80427
  var STATE_WORD = {
80172
80428
  gatewayed: "governed",
80173
80429
  "node9-self": "node9",
@@ -80176,10 +80432,10 @@ var STATE_WORD = {
80176
80432
  };
80177
80433
  function stateChip(state) {
80178
80434
  const w = STATE_WORD[state].padEnd(12);
80179
- if (state === "gatewayed") return chalk28.green(w);
80180
- if (state === "node9-self") return chalk28.gray(w);
80181
- if (state === "remote") return chalk28.gray(w);
80182
- return chalk28.yellow(w);
80435
+ if (state === "gatewayed") return chalk29.green(w);
80436
+ if (state === "node9-self") return chalk29.gray(w);
80437
+ if (state === "remote") return chalk29.gray(w);
80438
+ return chalk29.yellow(w);
80183
80439
  }
80184
80440
  function relativeAge(ms, now) {
80185
80441
  const s = Math.max(0, Math.round((now - ms) / 1e3));
@@ -80193,15 +80449,15 @@ function relativeAge(ms, now) {
80193
80449
  function connectionChip(e, now) {
80194
80450
  switch (e.connection) {
80195
80451
  case "connected":
80196
- return chalk28.green(
80452
+ return chalk29.green(
80197
80453
  `\u2713 connected${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`
80198
80454
  );
80199
80455
  case "stale":
80200
- return chalk28.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
80456
+ return chalk29.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
80201
80457
  case "pending-launch":
80202
- return chalk28.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
80458
+ return chalk29.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
80203
80459
  case "unlaunchable":
80204
- return chalk28.red(
80460
+ return chalk29.red(
80205
80461
  `\u2717 can't launch \u2014 ${(e.missingEnv ?? []).map((v) => `$${v}`).join(", ")} not set`
80206
80462
  );
80207
80463
  default:
@@ -80226,7 +80482,7 @@ function registerMcpGatewayCommand2(mcp) {
80226
80482
  const now = Date.now();
80227
80483
  const rows = resolveMcpStatus(void 0, process.env, now);
80228
80484
  if (rows.length === 0) {
80229
- console.error(chalk28.gray("No MCP servers found in any agent config."));
80485
+ console.error(chalk29.gray("No MCP servers found in any agent config."));
80230
80486
  return;
80231
80487
  }
80232
80488
  for (const e of rows) {
@@ -80240,20 +80496,20 @@ function registerMcpGatewayCommand2(mcp) {
80240
80496
  const pending = rows.filter((e) => e.connection === "pending-launch").length;
80241
80497
  if (ungoverned > 0) {
80242
80498
  console.error(
80243
- chalk28.yellow(
80499
+ chalk29.yellow(
80244
80500
  `
80245
- ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all") + " to govern."
80501
+ ${ungoverned} ungoverned \u2014 run ` + chalk29.bold("node9 mcp gateway --all") + " to govern."
80246
80502
  )
80247
80503
  );
80248
80504
  }
80249
80505
  if (pending > 0) {
80250
80506
  console.error(
80251
- chalk28.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
80507
+ chalk29.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
80252
80508
  );
80253
80509
  }
80254
80510
  if (unlaunchable > 0) {
80255
80511
  console.error(
80256
- chalk28.red(
80512
+ chalk29.red(
80257
80513
  `${unlaunchable} can't launch \u2014 set the missing env var(s) where the agent starts (a restart alone won't fix it).`
80258
80514
  )
80259
80515
  );
@@ -80272,27 +80528,27 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
80272
80528
  } else if (name) {
80273
80529
  const matches2 = resolve(name, opts.agent);
80274
80530
  if (matches2.length === 0) {
80275
- console.error(chalk28.red(`No MCP server named "${name}" found.`));
80531
+ console.error(chalk29.red(`No MCP server named "${name}" found.`));
80276
80532
  process.exitCode = 1;
80277
80533
  return;
80278
80534
  }
80279
80535
  if (matches2.length > 1) {
80280
80536
  console.error(
80281
- chalk28.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
80537
+ chalk29.red(`"${name}" exists in ${matches2.length} agents \u2014 pass --agent <id> (`) + matches2.map((m) => m.agent).join(", ") + ")."
80282
80538
  );
80283
80539
  process.exitCode = 1;
80284
80540
  return;
80285
80541
  }
80286
80542
  targets = matches2;
80287
80543
  } else {
80288
- console.error(chalk28.red("Pass a server name or --all. See `node9 mcp status`."));
80544
+ console.error(chalk29.red("Pass a server name or --all. See `node9 mcp status`."));
80289
80545
  process.exitCode = 1;
80290
80546
  return;
80291
80547
  }
80292
80548
  const doable = targets.filter((e) => e.state === targetState);
80293
80549
  if (doable.length === 0) {
80294
80550
  console.error(
80295
- chalk28.gray(
80551
+ chalk29.gray(
80296
80552
  opts.rewrap ? "Nothing to do \u2014 no governed servers to refresh." : "Nothing to do \u2014 already governed (or node9-self)."
80297
80553
  )
80298
80554
  );
@@ -80306,20 +80562,20 @@ ${ungoverned} ungoverned \u2014 run ` + chalk28.bold("node9 mcp gateway --all")
80306
80562
  else wrapEntry(e);
80307
80563
  agents.add(e.agentLabel);
80308
80564
  const verb = opts.rewrap ? "refreshed" : "governed";
80309
- console.error(chalk28.green(`\u2713 ${verb} ${e.name}`) + chalk28.gray(` (${e.agentLabel})`));
80565
+ console.error(chalk29.green(`\u2713 ${verb} ${e.name}`) + chalk29.gray(` (${e.agentLabel})`));
80310
80566
  } catch (err2) {
80311
80567
  failed++;
80312
80568
  const msg = err2 instanceof Error ? err2.message : String(err2);
80313
80569
  console.error(
80314
- chalk28.red(`\u2717 failed ${e.name}`) + chalk28.gray(` (${e.agentLabel}): ${msg}`)
80570
+ chalk29.red(`\u2717 failed ${e.name}`) + chalk29.gray(` (${e.agentLabel}): ${msg}`)
80315
80571
  );
80316
80572
  }
80317
80573
  }
80318
80574
  if (failed > 0) process.exitCode = 1;
80319
80575
  if (agents.size > 0) {
80320
80576
  console.error(
80321
- chalk28.bold(`
80322
- Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any server with ") + chalk28.cyan("node9 mcp ungateway <name>") + chalk28.gray(".")
80577
+ chalk29.bold(`
80578
+ Restart ${[...agents].join(", ")}`) + chalk29.gray(" to activate. Undo any server with ") + chalk29.cyan("node9 mcp ungateway <name>") + chalk29.gray(".")
80323
80579
  );
80324
80580
  }
80325
80581
  }
@@ -80327,19 +80583,19 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
80327
80583
  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) => {
80328
80584
  const matches2 = resolve(name, opts.agent).filter((e2) => e2.state === "gatewayed");
80329
80585
  if (matches2.length === 0) {
80330
- console.error(chalk28.red(`No governed MCP server named "${name}" found.`));
80586
+ console.error(chalk29.red(`No governed MCP server named "${name}" found.`));
80331
80587
  process.exitCode = 1;
80332
80588
  return;
80333
80589
  }
80334
80590
  if (matches2.length > 1) {
80335
- console.error(chalk28.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
80591
+ console.error(chalk29.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
80336
80592
  process.exitCode = 1;
80337
80593
  return;
80338
80594
  }
80339
80595
  const e = matches2[0];
80340
80596
  const orig = fromGateway(e.raw);
80341
80597
  if (!orig) {
80342
- console.error(chalk28.red(`Couldn't parse the original command for "${name}".`));
80598
+ console.error(chalk29.red(`Couldn't parse the original command for "${name}".`));
80343
80599
  process.exitCode = 1;
80344
80600
  return;
80345
80601
  }
@@ -80347,12 +80603,12 @@ Restart ${[...agents].join(", ")}`) + chalk28.gray(" to activate. Undo any serve
80347
80603
  writeMcpEntry(e.mcpFile, e.format, e.name, orig);
80348
80604
  } catch (err2) {
80349
80605
  const msg = err2 instanceof Error ? err2.message : String(err2);
80350
- console.error(chalk28.red(`Failed to restore "${name}": ${msg}`));
80606
+ console.error(chalk29.red(`Failed to restore "${name}": ${msg}`));
80351
80607
  process.exitCode = 1;
80352
80608
  return;
80353
80609
  }
80354
80610
  console.error(
80355
- chalk28.green(`\u2713 restored ${e.name}`) + chalk28.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
80611
+ chalk29.green(`\u2713 restored ${e.name}`) + chalk29.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
80356
80612
  );
80357
80613
  });
80358
80614
  }
@@ -80378,14 +80634,14 @@ function registerMcpPinCommand(program2) {
80378
80634
  }
80379
80635
  }
80380
80636
  if (repoCorrupt) {
80381
- console.error(chalk29.red(`
80637
+ console.error(chalk30.red(`
80382
80638
  \u274C Repo pin file at ${found.path} is corrupt or unreadable.`));
80383
80639
  process.exit(1);
80384
80640
  }
80385
80641
  if (!homeResult.ok && homeResult.reason === "corrupt") {
80386
- console.error(chalk29.red(`
80642
+ console.error(chalk30.red(`
80387
80643
  \u274C Home pin file is corrupt: ${homeResult.detail}`));
80388
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80644
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80389
80645
  process.exit(1);
80390
80646
  }
80391
80647
  const homeEntries = homeResult.ok ? homeResult.pins.servers : {};
@@ -80397,25 +80653,25 @@ function registerMcpPinCommand(program2) {
80397
80653
  merged.set(key, { entry, source: "repo" });
80398
80654
  }
80399
80655
  if (merged.size === 0) {
80400
- console.log(chalk29.gray("\nNo MCP servers are pinned yet."));
80656
+ console.log(chalk30.gray("\nNo MCP servers are pinned yet."));
80401
80657
  console.log(
80402
- chalk29.gray("Pins are created automatically when the MCP gateway first connects.\n")
80658
+ chalk30.gray("Pins are created automatically when the MCP gateway first connects.\n")
80403
80659
  );
80404
80660
  return;
80405
80661
  }
80406
- console.log(chalk29.bold("\n\u{1F512} Pinned MCP Servers\n"));
80662
+ console.log(chalk30.bold("\n\u{1F512} Pinned MCP Servers\n"));
80407
80663
  const showSource = found.source === "repo";
80408
80664
  for (const [key, { entry, source }] of merged) {
80409
- const tag = showSource ? ` ${chalk29.yellow(`[${source}]`)}` : "";
80410
- console.log(` ${chalk29.cyan(key)}${tag} ${chalk29.gray(entry.label)}`);
80411
- console.log(` Tools (${entry.toolCount}): ${chalk29.white(entry.toolNames.join(", "))}`);
80412
- console.log(` Hash: ${chalk29.gray(entry.toolsHash.slice(0, 16))}...`);
80413
- console.log(` Pinned: ${chalk29.gray(entry.pinnedAt)}`);
80665
+ const tag = showSource ? ` ${chalk30.yellow(`[${source}]`)}` : "";
80666
+ console.log(` ${chalk30.cyan(key)}${tag} ${chalk30.gray(entry.label)}`);
80667
+ console.log(` Tools (${entry.toolCount}): ${chalk30.white(entry.toolNames.join(", "))}`);
80668
+ console.log(` Hash: ${chalk30.gray(entry.toolsHash.slice(0, 16))}...`);
80669
+ console.log(` Pinned: ${chalk30.gray(entry.pinnedAt)}`);
80414
80670
  console.log("");
80415
80671
  }
80416
80672
  if (showSource) {
80417
- console.log(chalk29.gray(` [repo] entries come from ${found.path}`));
80418
- console.log(chalk29.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
80673
+ console.log(chalk30.gray(` [repo] entries come from ${found.path}`));
80674
+ console.log(chalk30.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
80419
80675
  }
80420
80676
  });
80421
80677
  pinSubCmd.command("promote <serverKey>").description(
@@ -80425,22 +80681,22 @@ function registerMcpPinCommand(program2) {
80425
80681
  const { repoPath, created } = promotePin(serverKey, process.cwd());
80426
80682
  if (created) {
80427
80683
  console.log(
80428
- chalk29.green(
80684
+ chalk30.green(
80429
80685
  `
80430
- \u2705 Created ${repoPath} with the promoted pin for ${chalk29.cyan(serverKey)}.`
80686
+ \u2705 Created ${repoPath} with the promoted pin for ${chalk30.cyan(serverKey)}.`
80431
80687
  )
80432
80688
  );
80433
80689
  } else {
80434
- console.log(chalk29.green(`
80435
- \u2705 Promoted ${chalk29.cyan(serverKey)} into ${repoPath}.`));
80690
+ console.log(chalk30.green(`
80691
+ \u2705 Promoted ${chalk30.cyan(serverKey)} into ${repoPath}.`));
80436
80692
  }
80437
- console.log(chalk29.gray(" Review the change and commit it:"));
80438
- console.log(chalk29.cyan(` git add ${repoPath}`));
80439
- console.log(chalk29.cyan(` git commit -m "pin ${serverKey} (node9)"`));
80693
+ console.log(chalk30.gray(" Review the change and commit it:"));
80694
+ console.log(chalk30.cyan(` git add ${repoPath}`));
80695
+ console.log(chalk30.cyan(` git commit -m "pin ${serverKey} (node9)"`));
80440
80696
  console.log("");
80441
80697
  } catch (err2) {
80442
80698
  const msg = err2 instanceof Error ? err2.message : String(err2);
80443
- console.error(chalk29.red(`
80699
+ console.error(chalk30.red(`
80444
80700
  \u274C ${msg}
80445
80701
  `));
80446
80702
  process.exit(1);
@@ -80453,36 +80709,36 @@ function registerMcpPinCommand(program2) {
80453
80709
  try {
80454
80710
  pins = readMcpPins();
80455
80711
  } catch {
80456
- console.error(chalk29.red("\n\u274C Pin file is corrupt."));
80457
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80712
+ console.error(chalk30.red("\n\u274C Pin file is corrupt."));
80713
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80458
80714
  process.exit(1);
80459
80715
  }
80460
80716
  if (!pins.servers[serverKey]) {
80461
- console.error(chalk29.red(`
80717
+ console.error(chalk30.red(`
80462
80718
  \u274C No pin found for server key "${serverKey}"
80463
80719
  `));
80464
- console.error(`Run ${chalk29.cyan("node9 mcp pin list")} to see pinned servers.
80720
+ console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
80465
80721
  `);
80466
80722
  process.exit(1);
80467
80723
  }
80468
80724
  const label3 = pins.servers[serverKey].label;
80469
80725
  removePin(serverKey);
80470
- console.log(chalk29.green(`
80471
- \u{1F513} Pin removed for ${chalk29.cyan(serverKey)}`));
80472
- console.log(chalk29.gray(` Server: ${label3}`));
80473
- console.log(chalk29.gray(" Next connection will re-pin with current tool definitions.\n"));
80726
+ console.log(chalk30.green(`
80727
+ \u{1F513} Pin removed for ${chalk30.cyan(serverKey)}`));
80728
+ console.log(chalk30.gray(` Server: ${label3}`));
80729
+ console.log(chalk30.gray(" Next connection will re-pin with current tool definitions.\n"));
80474
80730
  });
80475
80731
  pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
80476
80732
  const result = readMcpPinsSafe();
80477
80733
  if (!result.ok && result.reason === "missing") {
80478
- console.log(chalk29.gray("\nNo pins to clear.\n"));
80734
+ console.log(chalk30.gray("\nNo pins to clear.\n"));
80479
80735
  return;
80480
80736
  }
80481
80737
  const count = result.ok ? Object.keys(result.pins.servers).length : "?";
80482
80738
  clearAllPins();
80483
- console.log(chalk29.green(`
80739
+ console.log(chalk30.green(`
80484
80740
  \u{1F513} Cleared ${count} MCP pin(s).`));
80485
- console.log(chalk29.gray(" Next connection to each server will re-pin.\n"));
80741
+ console.log(chalk30.gray(" Next connection to each server will re-pin.\n"));
80486
80742
  });
80487
80743
  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) => {
80488
80744
  if (opts.stale) {
@@ -80491,7 +80747,7 @@ function registerMcpPinCommand(program2) {
80491
80747
  }
80492
80748
  if (!serverKey) {
80493
80749
  console.error(
80494
- chalk29.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
80750
+ chalk30.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
80495
80751
  );
80496
80752
  process.exit(1);
80497
80753
  }
@@ -80499,15 +80755,15 @@ function registerMcpPinCommand(program2) {
80499
80755
  try {
80500
80756
  pins = readMcpPins();
80501
80757
  } catch {
80502
- console.error(chalk29.red("\n\u274C Pin file is corrupt."));
80503
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80758
+ console.error(chalk30.red("\n\u274C Pin file is corrupt."));
80759
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80504
80760
  process.exit(1);
80505
80761
  }
80506
80762
  if (!pins.servers[serverKey]) {
80507
- console.error(chalk29.red(`
80763
+ console.error(chalk30.red(`
80508
80764
  \u274C No pin found for server key "${serverKey}"
80509
80765
  `));
80510
- console.error(`Run ${chalk29.cyan("node9 mcp pin list")} to see pinned servers.
80766
+ console.error(`Run ${chalk30.cyan("node9 mcp pin list")} to see pinned servers.
80511
80767
  `);
80512
80768
  process.exit(1);
80513
80769
  }
@@ -80515,10 +80771,10 @@ function registerMcpPinCommand(program2) {
80515
80771
  const liveKeys = inventoryServerKeys(inv);
80516
80772
  if (liveKeys.has(serverKey)) {
80517
80773
  const agent = "an agent config";
80518
- console.error(chalk29.red(`
80774
+ console.error(chalk30.red(`
80519
80775
  \u274C Server "${serverKey}" is still configured in ${agent}.`));
80520
80776
  console.error(
80521
- chalk29.yellow(
80777
+ chalk30.yellow(
80522
80778
  ` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
80523
80779
  `
80524
80780
  )
@@ -80527,10 +80783,10 @@ function registerMcpPinCommand(program2) {
80527
80783
  }
80528
80784
  const label3 = pins.servers[serverKey].label;
80529
80785
  removePin(serverKey);
80530
- console.log(chalk29.green(`
80531
- \u2713 Forgot server ${chalk29.cyan(serverKey)}`));
80532
- console.log(chalk29.gray(` Was: ${label3}`));
80533
- console.log(chalk29.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
80786
+ console.log(chalk30.green(`
80787
+ \u2713 Forgot server ${chalk30.cyan(serverKey)}`));
80788
+ console.log(chalk30.gray(` Was: ${label3}`));
80789
+ console.log(chalk30.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
80534
80790
  });
80535
80791
  }
80536
80792
  function forgetAllStale() {
@@ -80538,16 +80794,16 @@ function forgetAllStale() {
80538
80794
  try {
80539
80795
  pins = readMcpPins();
80540
80796
  } catch {
80541
- console.error(chalk29.red("\n\u274C Pin file is corrupt."));
80542
- console.error(chalk29.yellow(" Run: node9 mcp pin reset\n"));
80797
+ console.error(chalk30.red("\n\u274C Pin file is corrupt."));
80798
+ console.error(chalk30.yellow(" Run: node9 mcp pin reset\n"));
80543
80799
  process.exit(1);
80544
80800
  }
80545
80801
  const inv = inventoryMcp();
80546
80802
  const liveKeys = inventoryServerKeys(inv);
80547
80803
  if (liveKeys.size === 0) {
80548
- console.error(chalk29.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
80804
+ console.error(chalk30.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
80549
80805
  console.error(
80550
- chalk29.yellow(
80806
+ chalk30.yellow(
80551
80807
  " 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"
80552
80808
  )
80553
80809
  );
@@ -80555,118 +80811,118 @@ function forgetAllStale() {
80555
80811
  }
80556
80812
  const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
80557
80813
  if (stale.length === 0) {
80558
- console.log(chalk29.gray("\nNo stale servers to remove.\n"));
80814
+ console.log(chalk30.gray("\nNo stale servers to remove.\n"));
80559
80815
  return;
80560
80816
  }
80561
80817
  for (const [sk, pin] of stale) {
80562
80818
  delete pins.servers[sk];
80563
- console.log(chalk29.green(` \u2713 ${chalk29.cyan(sk)} ${chalk29.gray(pin.label)}`));
80819
+ console.log(chalk30.green(` \u2713 ${chalk30.cyan(sk)} ${chalk30.gray(pin.label)}`));
80564
80820
  }
80565
80821
  writeMcpPins(pins);
80566
- console.log(chalk29.green(`
80822
+ console.log(chalk30.green(`
80567
80823
  Removed ${stale.length} stale server(s).
80568
80824
  `));
80569
80825
  }
80570
80826
 
80571
80827
  // src/cli/commands/sync.ts
80572
80828
  init_sync();
80573
- import chalk30 from "chalk";
80829
+ import chalk31 from "chalk";
80574
80830
  function registerSyncCommand(program2) {
80575
80831
  const policy = program2.command("policy").description("Manage cloud policy rules");
80576
80832
  policy.command("push").description("Push this machine's effective policy to the node9 dashboard").action(async () => {
80577
- process.stdout.write(chalk30.cyan("Pushing policy to the dashboard\u2026"));
80833
+ process.stdout.write(chalk31.cyan("Pushing policy to the dashboard\u2026"));
80578
80834
  const result = await runPolicyPush();
80579
80835
  process.stdout.write("\n");
80580
80836
  if (!result.ok) {
80581
- console.error(chalk30.red(`\u2717 ${result.reason}`));
80837
+ console.error(chalk31.red(`\u2717 ${result.reason}`));
80582
80838
  process.exit(1);
80583
80839
  }
80584
- console.log(chalk30.green("\u2713 Policy mirrored to the dashboard"));
80585
- console.log(chalk30.gray(" See it under Security Policy \u2192 Machines"));
80840
+ console.log(chalk31.green("\u2713 Policy mirrored to the dashboard"));
80841
+ console.log(chalk31.gray(" See it under Security Policy \u2192 Machines"));
80586
80842
  });
80587
80843
  policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
80588
- process.stdout.write(chalk30.cyan("Syncing cloud policy rules\u2026"));
80844
+ process.stdout.write(chalk31.cyan("Syncing cloud policy rules\u2026"));
80589
80845
  const result = await runCloudSync();
80590
80846
  process.stdout.write("\n");
80591
80847
  if (!result.ok) {
80592
- console.error(chalk30.red(`\u2717 ${result.reason}`));
80848
+ console.error(chalk31.red(`\u2717 ${result.reason}`));
80593
80849
  process.exit(1);
80594
80850
  }
80595
80851
  if (result.unchanged) {
80596
80852
  console.log(
80597
- chalk30.green(
80853
+ chalk31.green(
80598
80854
  `\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
80599
80855
  )
80600
80856
  );
80601
- console.log(chalk30.gray(` Cached at: ${result.fetchedAt}`));
80602
- console.log(chalk30.gray(` Server returned 304 (no changes since last sync)`));
80857
+ console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
80858
+ console.log(chalk31.gray(` Server returned 304 (no changes since last sync)`));
80603
80859
  } else {
80604
80860
  console.log(
80605
- chalk30.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
80861
+ chalk31.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
80606
80862
  );
80607
- console.log(chalk30.gray(` Cached at: ${result.fetchedAt}`));
80608
- console.log(chalk30.gray(` File: ~/.node9/rules-cache.json`));
80863
+ console.log(chalk31.gray(` Cached at: ${result.fetchedAt}`));
80864
+ console.log(chalk31.gray(` File: ~/.node9/rules-cache.json`));
80609
80865
  }
80610
80866
  });
80611
80867
  policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
80612
80868
  const status = getCloudSyncStatus();
80613
80869
  if (!status.cached) {
80614
- console.log(chalk30.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
80870
+ console.log(chalk31.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
80615
80871
  return;
80616
80872
  }
80617
80873
  const rules = getCloudRules() ?? [];
80618
80874
  const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
80619
80875
  console.log(
80620
- chalk30.bold(`
80621
- Cloud policy rules`) + chalk30.gray(
80876
+ chalk31.bold(`
80877
+ Cloud policy rules`) + chalk31.gray(
80622
80878
  ` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
80623
80879
  `
80624
80880
  )
80625
80881
  );
80626
80882
  if (rules.length === 0) {
80627
- console.log(chalk30.gray(" No rules defined in cloud policy.\n"));
80883
+ console.log(chalk31.gray(" No rules defined in cloud policy.\n"));
80628
80884
  return;
80629
80885
  }
80630
80886
  for (const rule of rules) {
80631
80887
  const r = rule;
80632
- const verdictColor = r.verdict === "block" ? chalk30.red : r.verdict === "allow" ? chalk30.green : chalk30.yellow;
80888
+ const verdictColor = r.verdict === "block" ? chalk31.red : r.verdict === "allow" ? chalk31.green : chalk31.yellow;
80633
80889
  console.log(
80634
80890
  ` ${verdictColor(
80635
80891
  String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
80636
- )} ${chalk30.white(String(r.name ?? "(unnamed)"))}`
80892
+ )} ${chalk31.white(String(r.name ?? "(unnamed)"))}`
80637
80893
  );
80638
- if (r.reason) console.log(chalk30.gray(` ${String(r.reason)}`));
80894
+ if (r.reason) console.log(chalk31.gray(` ${String(r.reason)}`));
80639
80895
  }
80640
80896
  console.log("");
80641
80897
  });
80642
80898
  policy.command("status").description("Show current cloud policy cache status").action(() => {
80643
80899
  const s = getCloudSyncStatus();
80644
80900
  if (!s.cached) {
80645
- console.log(chalk30.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
80901
+ console.log(chalk31.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
80646
80902
  return;
80647
80903
  }
80648
80904
  const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
80649
80905
  console.log(`
80650
- Rules : ${chalk30.green(String(s.rules))} cloud rules loaded`);
80906
+ Rules : ${chalk31.green(String(s.rules))} cloud rules loaded`);
80651
80907
  console.log(
80652
- ` Synced : ${chalk30.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
80908
+ ` Synced : ${chalk31.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
80653
80909
  );
80654
80910
  if (s.workspaceId) {
80655
- console.log(` Workspace: ${chalk30.gray(s.workspaceId)}`);
80911
+ console.log(` Workspace: ${chalk31.gray(s.workspaceId)}`);
80656
80912
  }
80657
80913
  if (s.panicMode) {
80658
80914
  console.log(
80659
- ` ${chalk30.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk30.dim("(every review-verdict becomes block)")
80915
+ ` ${chalk31.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk31.dim("(every review-verdict becomes block)")
80660
80916
  );
80661
80917
  }
80662
80918
  if (s.shadowMode) {
80663
80919
  console.log(
80664
- ` ${chalk30.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk30.dim("(blocks become would-block log entries)")
80920
+ ` ${chalk31.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk31.dim("(blocks become would-block log entries)")
80665
80921
  );
80666
80922
  }
80667
80923
  if (s.syncIntervalHours) {
80668
80924
  console.log(
80669
- chalk30.gray(
80925
+ chalk31.gray(
80670
80926
  ` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
80671
80927
  )
80672
80928
  );
@@ -80677,7 +80933,7 @@ function registerSyncCommand(program2) {
80677
80933
 
80678
80934
  // src/cli/commands/agents.ts
80679
80935
  init_setup();
80680
- import chalk31 from "chalk";
80936
+ import chalk32 from "chalk";
80681
80937
  var SETUP_FN = {
80682
80938
  claude: setupClaude,
80683
80939
  gemini: setupGemini,
@@ -80720,30 +80976,30 @@ function registerAgentsCommand(program2) {
80720
80976
  console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
80721
80977
  console.log(" " + "\u2500".repeat(44));
80722
80978
  for (const s of statuses) {
80723
- const installed = s.installed ? chalk31.green("\u2713") : chalk31.gray("\u2717");
80724
- const wired = !s.installed ? chalk31.gray("\u2014") : s.wired ? chalk31.green("\u2713") : chalk31.yellow("\u2717");
80725
- const mode = s.mode ? chalk31.gray(s.mode) : chalk31.gray("\u2014");
80726
- const hint = s.installed && !s.wired ? chalk31.gray(` \u2190 node9 agents add ${s.name}`) : "";
80979
+ const installed = s.installed ? chalk32.green("\u2713") : chalk32.gray("\u2717");
80980
+ const wired = !s.installed ? chalk32.gray("\u2014") : s.wired ? chalk32.green("\u2713") : chalk32.yellow("\u2717");
80981
+ const mode = s.mode ? chalk32.gray(s.mode) : chalk32.gray("\u2014");
80982
+ const hint = s.installed && !s.wired ? chalk32.gray(` \u2190 node9 agents add ${s.name}`) : "";
80727
80983
  console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
80728
80984
  }
80729
80985
  console.log("");
80730
80986
  if (!anyInstalled) {
80731
80987
  console.log(
80732
- 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")
80988
+ 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")
80733
80989
  );
80734
80990
  return;
80735
80991
  }
80736
80992
  const unwired = statuses.filter((s) => s.installed && !s.wired);
80737
80993
  if (unwired.length > 0) {
80738
80994
  console.log(
80739
- chalk31.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk31.white(`node9 agents add ${unwired[0].name}`) + "\n"
80995
+ chalk32.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk32.white(`node9 agents add ${unwired[0].name}`) + "\n"
80740
80996
  );
80741
80997
  }
80742
80998
  if (statuses.some((s) => s.name === "gemini" && s.installed)) {
80743
80999
  console.log(
80744
- chalk31.yellow(
81000
+ chalk32.yellow(
80745
81001
  " \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
80746
- ) + chalk31.gray(" Migrate to Antigravity: ") + chalk31.white("node9 agents add antigravity") + "\n"
81002
+ ) + chalk32.gray(" Migrate to Antigravity: ") + chalk32.white("node9 agents add antigravity") + "\n"
80747
81003
  );
80748
81004
  }
80749
81005
  });
@@ -80751,7 +81007,7 @@ function registerAgentsCommand(program2) {
80751
81007
  const name = resolveAgentName(agent);
80752
81008
  const fn = SETUP_FN[name];
80753
81009
  if (!fn) {
80754
- console.error(chalk31.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
81010
+ console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
80755
81011
  process.exit(1);
80756
81012
  }
80757
81013
  await fn();
@@ -80760,14 +81016,14 @@ function registerAgentsCommand(program2) {
80760
81016
  const name = resolveAgentName(agent);
80761
81017
  const fn = TEARDOWN_FN[name];
80762
81018
  if (!fn) {
80763
- console.error(chalk31.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
81019
+ console.error(chalk32.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
80764
81020
  process.exit(1);
80765
81021
  }
80766
- console.log(chalk31.cyan(`
81022
+ console.log(chalk32.cyan(`
80767
81023
  \u{1F6E1}\uFE0F Node9: removing from ${name}...
80768
81024
  `));
80769
81025
  fn();
80770
- console.log(chalk31.gray("\n Restart the agent for changes to take effect."));
81026
+ console.log(chalk32.gray("\n Restart the agent for changes to take effect."));
80771
81027
  });
80772
81028
  }
80773
81029
 
@@ -80776,21 +81032,21 @@ init_scan();
80776
81032
 
80777
81033
  // src/cli/commands/posture.ts
80778
81034
  init_posture();
80779
- import chalk33 from "chalk";
81035
+ import chalk34 from "chalk";
80780
81036
 
80781
81037
  // src/posture/render.ts
80782
81038
  init_score();
80783
- import chalk32 from "chalk";
81039
+ import chalk33 from "chalk";
80784
81040
  var ICON = {
80785
- critical: chalk32.red("\u274C"),
80786
- high: chalk32.red("\u274C"),
80787
- medium: chalk32.yellow("\u26A0\uFE0F "),
80788
- advisory: chalk32.gray("\u26A0\uFE0F ")
81041
+ critical: chalk33.red("\u274C"),
81042
+ high: chalk33.red("\u274C"),
81043
+ medium: chalk33.yellow("\u26A0\uFE0F "),
81044
+ advisory: chalk33.gray("\u26A0\uFE0F ")
80789
81045
  };
80790
81046
  var TIER_LABEL = {
80791
- good: chalk32.green("Good"),
80792
- "at-risk": chalk32.yellow("At risk"),
80793
- critical: chalk32.red("Critical")
81047
+ good: chalk33.green("Good"),
81048
+ "at-risk": chalk33.yellow("At risk"),
81049
+ critical: chalk33.red("Critical")
80794
81050
  };
80795
81051
  function wrap(text, width) {
80796
81052
  const out = [];
@@ -80808,7 +81064,7 @@ function wrap(text, width) {
80808
81064
  }
80809
81065
  var LABEL_WIDTH = 14;
80810
81066
  function label2(category) {
80811
- return chalk32.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
81067
+ return chalk33.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
80812
81068
  }
80813
81069
  function guardedWhat(f) {
80814
81070
  if (f.detail.length === 0) return "this";
@@ -80818,31 +81074,31 @@ function guardedWhat(f) {
80818
81074
  }
80819
81075
  function renderFinding(f, showWeight = false, displayLabel = f.category) {
80820
81076
  const lines = [];
80821
- const wt = showWeight && f.scoreWeight ? chalk32.cyan.bold(`+${f.scoreWeight} `) : "";
81077
+ const wt = showWeight && f.scoreWeight ? chalk33.cyan.bold(`+${f.scoreWeight} `) : "";
80822
81078
  lines.push(` ${ICON[f.severity]} ${label2(displayLabel)}${wt}${f.title}`);
80823
81079
  const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
80824
81080
  const width = 80 - indent.length;
80825
81081
  for (const s of [f.what, f.why, f.who]) {
80826
- if (s) for (const l of wrap(s, width)) lines.push(indent + chalk32.gray(l));
81082
+ if (s) for (const l of wrap(s, width)) lines.push(indent + chalk33.gray(l));
80827
81083
  }
80828
- for (const d of f.detail) lines.push(indent + chalk32.gray(d));
81084
+ for (const d of f.detail) lines.push(indent + chalk33.gray(d));
80829
81085
  if (f.fix) {
80830
81086
  let first = true;
80831
81087
  for (const seg of f.fix.split("\n")) {
80832
81088
  for (const l of wrap(seg, width - 2)) {
80833
- lines.push(indent + chalk32.cyan(first ? "\u2192 " + l : " " + l));
81089
+ lines.push(indent + chalk33.cyan(first ? "\u2192 " + l : " " + l));
80834
81090
  first = false;
80835
81091
  }
80836
81092
  }
80837
81093
  }
80838
81094
  const tradeoff = [
80839
- [f.gain, "gain: ", chalk32.green],
80840
- [f.cost, "cost: ", chalk32.yellow]
81095
+ [f.gain, "gain: ", chalk33.green],
81096
+ [f.cost, "cost: ", chalk33.yellow]
80841
81097
  ];
80842
81098
  for (const [text, lbl, color2] of tradeoff) {
80843
81099
  if (!text) continue;
80844
81100
  wrap(text, width - 6).forEach((l, i) => {
80845
- lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk32.gray(l));
81101
+ lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk33.gray(l));
80846
81102
  });
80847
81103
  }
80848
81104
  return lines;
@@ -80852,7 +81108,7 @@ function renderPosture(result) {
80852
81108
  const tier = TIER_LABEL[result.tier];
80853
81109
  lines.push("");
80854
81110
  lines.push(
80855
- chalk32.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk32.gray(` \u2014 ${result.agent}`) + ` ${chalk32.bold(`Score: ${result.score}/100`)} (${tier})`
81111
+ chalk33.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk33.gray(` \u2014 ${result.agent}`) + ` ${chalk33.bold(`Score: ${result.score}/100`)} (${tier})`
80856
81112
  );
80857
81113
  const headroom = openHeadroom(result.findings);
80858
81114
  if (headroom > 0) {
@@ -80860,15 +81116,15 @@ function renderPosture(result) {
80860
81116
  (f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
80861
81117
  ).length;
80862
81118
  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.`;
80863
- for (const l of wrap(note, 76)) lines.push(" " + chalk32.gray(l));
81119
+ for (const l of wrap(note, 76)) lines.push(" " + chalk33.gray(l));
80864
81120
  }
80865
81121
  lines.push("");
80866
81122
  if (result.headline) {
80867
81123
  const indent = " ";
80868
- lines.push(` ${chalk32.red.bold("\u{1F525} Biggest risk")}`);
80869
- for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk32.white(l));
81124
+ lines.push(` ${chalk33.red.bold("\u{1F525} Biggest risk")}`);
81125
+ for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk33.white(l));
80870
81126
  const action = wrap(`Do this first: ${result.headline.action}`, 72);
80871
- action.forEach((l, i) => lines.push(indent + chalk32.cyan(i === 0 ? "\u2192 " + l : " " + l)));
81127
+ action.forEach((l, i) => lines.push(indent + chalk33.cyan(i === 0 ? "\u2192 " + l : " " + l)));
80872
81128
  lines.push("");
80873
81129
  }
80874
81130
  const covered = result.findings.filter((f) => f.coverage?.state === "covered");
@@ -80878,13 +81134,13 @@ function renderPosture(result) {
80878
81134
  );
80879
81135
  const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
80880
81136
  if (covered.length > 0) {
80881
- lines.push(" " + chalk32.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
81137
+ lines.push(" " + chalk33.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
80882
81138
  for (const f of covered) {
80883
81139
  const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
80884
81140
  const via = f.coverage?.via ?? "node9";
80885
81141
  const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
80886
81142
  lines.push(
80887
- ` ${chalk32.green("\u2705")} ${label2(lbl)}${chalk32.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
81143
+ ` ${chalk33.green("\u2705")} ${label2(lbl)}${chalk33.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
80888
81144
  );
80889
81145
  }
80890
81146
  lines.push("");
@@ -80893,24 +81149,24 @@ function renderPosture(result) {
80893
81149
  const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
80894
81150
  const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
80895
81151
  if (node9Open.length > 0) {
80896
- lines.push(" " + chalk32.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
81152
+ lines.push(" " + chalk33.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
80897
81153
  for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
80898
81154
  }
80899
81155
  if (reduceOpen.length > 0) {
80900
81156
  if (node9Open.length > 0) lines.push("");
80901
- lines.push(" " + chalk32.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
81157
+ lines.push(" " + chalk33.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
80902
81158
  for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
80903
81159
  }
80904
81160
  if (osOpen.length > 0) {
80905
81161
  if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
80906
- lines.push(" " + chalk32.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
81162
+ lines.push(" " + chalk33.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
80907
81163
  for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
80908
81164
  }
80909
81165
  for (const cat of result.passedCategories) {
80910
- lines.push(` ${chalk32.green("\u2705")} ${label2(cat)}${chalk32.gray("no issues found")}`);
81166
+ lines.push(` ${chalk33.green("\u2705")} ${label2(cat)}${chalk33.gray("no issues found")}`);
80911
81167
  }
80912
81168
  for (const cat of result.erroredCategories) {
80913
- lines.push(` ${chalk32.gray("\u2022")} ${label2(cat)}${chalk32.gray("could not be checked")}`);
81169
+ lines.push(` ${chalk33.gray("\u2022")} ${label2(cat)}${chalk33.gray("could not be checked")}`);
80914
81170
  }
80915
81171
  lines.push("");
80916
81172
  const crit = open.filter((f) => f.severity === "critical").length;
@@ -80918,16 +81174,16 @@ function renderPosture(result) {
80918
81174
  const med = open.filter((f) => f.severity === "medium").length;
80919
81175
  const adv = open.filter((f) => f.severity === "advisory").length;
80920
81176
  const parts = [];
80921
- if (crit) parts.push(chalk32.red(`${crit} critical`));
80922
- if (high) parts.push(chalk32.red(`${high} high`));
80923
- if (med) parts.push(chalk32.yellow(`${med} medium`));
80924
- if (adv) parts.push(chalk32.gray(`${adv} advisory`));
80925
- const summary = parts.length ? parts.join(" \xB7 ") : chalk32.green("no findings");
81177
+ if (crit) parts.push(chalk33.red(`${crit} critical`));
81178
+ if (high) parts.push(chalk33.red(`${high} high`));
81179
+ if (med) parts.push(chalk33.yellow(`${med} medium`));
81180
+ if (adv) parts.push(chalk33.gray(`${adv} advisory`));
81181
+ const summary = parts.length ? parts.join(" \xB7 ") : chalk33.green("no findings");
80926
81182
  lines.push(` ${summary}`);
80927
81183
  lines.push("");
80928
- lines.push(" " + chalk32.bold("Track this across your fleet & keep it green:"));
81184
+ lines.push(" " + chalk33.bold("Track this across your fleet & keep it green:"));
80929
81185
  lines.push(
80930
- " " + chalk32.dim("\u2192 ") + chalk32.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
81186
+ " " + chalk33.dim("\u2192 ") + chalk33.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
80931
81187
  );
80932
81188
  lines.push("");
80933
81189
  return lines.join("\n");
@@ -80947,11 +81203,11 @@ function registerPostureCommand(program2) {
80947
81203
  if (opts.ship) {
80948
81204
  const creds = readCredentials();
80949
81205
  if (!creds) {
80950
- console.error(chalk33.gray(" Run `node9 login` to ship this to your dashboard."));
81206
+ console.error(chalk34.gray(" Run `node9 login` to ship this to your dashboard."));
80951
81207
  } else {
80952
81208
  const ok2 = await shipPosture(result, creds);
80953
81209
  console.error(
80954
- ok2 ? chalk33.gray(" \u2713 Shipped to your node9 dashboard.") : chalk33.gray(" Could not reach the dashboard \u2014 saved locally only.")
81210
+ ok2 ? chalk34.gray(" \u2713 Shipped to your node9 dashboard.") : chalk34.gray(" Could not reach the dashboard \u2014 saved locally only.")
80955
81211
  );
80956
81212
  }
80957
81213
  }
@@ -80960,7 +81216,7 @@ function registerPostureCommand(program2) {
80960
81216
  }
80961
81217
 
80962
81218
  // src/cli/commands/scan-repo.ts
80963
- import chalk35 from "chalk";
81219
+ import chalk36 from "chalk";
80964
81220
 
80965
81221
  // src/ci-check/fetch.ts
80966
81222
  var import_undici = __toESM(require_undici());
@@ -86472,7 +86728,7 @@ async function scanRepo(input, onProgress) {
86472
86728
  }
86473
86729
 
86474
86730
  // src/ci-check/render.ts
86475
- import chalk34 from "chalk";
86731
+ import chalk35 from "chalk";
86476
86732
  var listed = (fs109) => fs109.filter((f) => f.tier !== "review");
86477
86733
  function excerpt(f) {
86478
86734
  const m = /`([^`]+)`/.exec(f.signals[0] ?? "");
@@ -86517,23 +86773,23 @@ function reviewTerminal(findings) {
86517
86773
  const L = [];
86518
86774
  if (items) {
86519
86775
  L.push(
86520
- 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.")
86776
+ 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.")
86521
86777
  );
86522
86778
  const files = [...byFile.keys()];
86523
86779
  for (const file2 of files.slice(0, REVIEW_FILES)) {
86524
86780
  const fs109 = byFile.get(file2);
86525
- L.push(chalk34.gray(` ${file2}`));
86781
+ L.push(chalk35.gray(` ${file2}`));
86526
86782
  for (const f of fs109.slice(0, REVIEW_LINES_PER_FILE))
86527
86783
  L.push(` \u2022 ${f.line ? `line ${f.line}: ` : ""}${excerpt(f)}`);
86528
86784
  if (fs109.length > REVIEW_LINES_PER_FILE)
86529
- L.push(chalk34.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
86785
+ L.push(chalk35.gray(` and ${fs109.length - REVIEW_LINES_PER_FILE} similar lines`));
86530
86786
  }
86531
86787
  const more = files.length - REVIEW_FILES;
86532
- if (more > 0) L.push(chalk34.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
86788
+ if (more > 0) L.push(chalk35.gray(` and ${more} more file${more === 1 ? "" : "s"}`));
86533
86789
  }
86534
86790
  if (grants)
86535
86791
  L.push(
86536
- chalk34.gray(
86792
+ chalk35.gray(
86537
86793
  ` ${grants} skill${grants === 1 ? " is" : "s are"} granted broad tools (Bash, Write or Edit); usually what the skill needs.`
86538
86794
  )
86539
86795
  );
@@ -86547,45 +86803,45 @@ var ICON2 = {
86547
86803
  advisory: "\u{1F7E2}"
86548
86804
  };
86549
86805
  var COLOR = {
86550
- critical: chalk34.red.bold,
86551
- high: chalk34.red,
86552
- medium: chalk34.yellow,
86553
- advisory: chalk34.gray
86806
+ critical: chalk35.red.bold,
86807
+ high: chalk35.red,
86808
+ medium: chalk35.yellow,
86809
+ advisory: chalk35.gray
86554
86810
  };
86555
86811
  var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security?ref=cli_scan_repo";
86556
86812
  function renderCta(res) {
86557
86813
  const L = [];
86558
- L.push(chalk34.dim(" " + "\u2500".repeat(63)));
86814
+ L.push(chalk35.dim(" " + "\u2500".repeat(63)));
86559
86815
  if (res.worst === "critical" || res.worst === "high") {
86560
86816
  const n = res.findings.filter(
86561
86817
  (f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
86562
86818
  ).length;
86563
86819
  L.push(
86564
- " " + chalk34.red.bold(
86820
+ " " + chalk35.red.bold(
86565
86821
  `\u{1F534} ${n} ${n === 1 ? "issue" : "issues"} to fix, then stop the next at the PR.`
86566
86822
  )
86567
86823
  );
86568
86824
  L.push("");
86569
- L.push(" " + chalk34.bold("Catch this class of issue on every PR, automatically:"));
86825
+ L.push(" " + chalk35.bold("Catch this class of issue on every PR, automatically:"));
86570
86826
  } else if (res.worst) {
86571
- L.push(" " + chalk34.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
86827
+ L.push(" " + chalk35.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
86572
86828
  L.push("");
86573
- L.push(" " + chalk34.bold("Check every PR for agent-CI risk:"));
86829
+ L.push(" " + chalk35.bold("Check every PR for agent-CI risk:"));
86574
86830
  } else if (res.incomplete) {
86575
- L.push(" " + chalk34.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
86831
+ L.push(" " + chalk35.yellow.bold("\u26A0\uFE0F Incomplete: not a clean bill of health."));
86576
86832
  L.push("");
86577
- L.push(" " + chalk34.bold("Get a complete check on every PR (CI reads the tree directly):"));
86833
+ L.push(" " + chalk35.bold("Get a complete check on every PR (CI reads the tree directly):"));
86578
86834
  } else {
86579
86835
  L.push(
86580
- " " + chalk34.green(
86836
+ " " + chalk35.green(
86581
86837
  groupReview(res.findings).byFile.size ? "\u2705 No alerts. Check the items worth a look above." : "\u2705 Agent CI is well-configured: 0 unmitigated issues."
86582
86838
  )
86583
86839
  );
86584
86840
  L.push("");
86585
- L.push(" " + chalk34.bold("Keep it green as you add agent workflows. Check every PR:"));
86841
+ L.push(" " + chalk35.bold("Keep it green as you add agent workflows. Check every PR:"));
86586
86842
  }
86587
- L.push(" " + chalk34.dim("\u2192 ") + chalk34.cyan.underline(ACTION_URL));
86588
- L.push(" " + chalk34.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
86843
+ L.push(" " + chalk35.dim("\u2192 ") + chalk35.cyan.underline(ACTION_URL));
86844
+ L.push(" " + chalk35.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
86589
86845
  return L;
86590
86846
  }
86591
86847
  function ownedHint(source) {
@@ -86625,21 +86881,21 @@ function renderScan(res, diff) {
86625
86881
  const L = [];
86626
86882
  const shown = listed(res.findings);
86627
86883
  const n = shown.length;
86628
- 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");
86629
- L.push(`\u{1F6E1}\uFE0F ${chalk34.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
86884
+ 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");
86885
+ L.push(`\u{1F6E1}\uFE0F ${chalk35.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
86630
86886
  L.push(
86631
- chalk34.gray(
86887
+ chalk35.gray(
86632
86888
  ` inspected ${res.inspected.length} config file(s), ${n} finding(s)` + (res.suppressedCount ? ` \xB7 ${res.suppressedCount} suppressed by .node9-ignore.json` : "")
86633
86889
  )
86634
86890
  );
86635
86891
  if (diff) {
86636
86892
  const introduced = listed(diff.added).length + diff.escalated.length;
86637
86893
  L.push(
86638
- diff.base !== "ok" ? chalk34.yellow.bold(
86894
+ diff.base !== "ok" ? chalk35.yellow.bold(
86639
86895
  ` \u26A0\uFE0F base ${diff.base === "did-not-run" ? "could not be read" : "scan was incomplete"}, so it cannot say what is new; showing everything`
86640
- ) : introduced > 0 ? chalk34.red.bold(
86896
+ ) : introduced > 0 ? chalk35.red.bold(
86641
86897
  ` \u26A0\uFE0F this change introduced ${introduced} finding(s)` + (diff.escalated.length ? ` (${diff.escalated.length} by widening an existing one)` : "")
86642
- ) : chalk34.green(
86898
+ ) : chalk35.green(
86643
86899
  ` \u2705 this change introduced nothing` + (diff.unchanged.length ? ` (${diff.unchanged.length} pre-existing)` : "") + (diff.removed.length ? `, and fixed ${diff.removed.length}` : "")
86644
86900
  )
86645
86901
  );
@@ -86647,7 +86903,7 @@ function renderScan(res, diff) {
86647
86903
  if (res.incomplete) {
86648
86904
  const rateLimited = res.notes.some((nt) => /rate limit/i.test(nt));
86649
86905
  L.push(
86650
- chalk34.yellow.bold(
86906
+ chalk35.yellow.bold(
86651
86907
  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."
86652
86908
  )
86653
86909
  );
@@ -86656,36 +86912,36 @@ function renderScan(res, diff) {
86656
86912
  for (const f of shown) {
86657
86913
  const ex = f.explain;
86658
86914
  L.push(
86659
- `${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk34.bold(ex ? ex.headline : f.title)}`
86915
+ `${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk35.bold(ex ? ex.headline : f.title)}`
86660
86916
  );
86661
- L.push(chalk34.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
86917
+ L.push(chalk35.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
86662
86918
  if (ex) {
86663
86919
  L.push(` ${ex.happens}`);
86664
86920
  for (const s of ex.saw) L.push(` \u2022 ${s}`);
86665
- ex.fix.forEach((x, i) => L.push(chalk34.cyan(` ${i + 1}. ${x}`)));
86921
+ ex.fix.forEach((x, i) => L.push(chalk35.cyan(` ${i + 1}. ${x}`)));
86666
86922
  } else {
86667
86923
  for (const s of f.signals) L.push(` \u2022 ${s}`);
86668
86924
  if (f.mitigations?.length)
86669
- L.push(chalk34.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
86670
- L.push(chalk34.cyan(` \u2192 ${f.fix}`));
86925
+ L.push(chalk35.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
86926
+ L.push(chalk35.cyan(` \u2192 ${f.fix}`));
86671
86927
  }
86672
86928
  L.push("");
86673
86929
  }
86674
86930
  if (n === 0 && !res.incomplete) {
86675
86931
  L.push(
86676
- chalk34.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
86932
+ chalk35.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
86677
86933
  );
86678
86934
  L.push("");
86679
86935
  }
86680
86936
  L.push(...reviewTerminal(res.findings));
86681
- for (const note of res.notes) L.push(chalk34.gray(` note: ${note}`));
86937
+ for (const note of res.notes) L.push(chalk35.gray(` note: ${note}`));
86682
86938
  const hasHigh = shown.some(
86683
86939
  (f) => isAlert(f) && !f.suppressed && (f.severity === "critical" || f.severity === "high")
86684
86940
  );
86685
86941
  if (hasHigh && !ownedHint(res.source)) {
86686
86942
  L.push("");
86687
86943
  L.push(
86688
- chalk34.yellow(
86944
+ chalk35.yellow(
86689
86945
  " \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.)"
86690
86946
  )
86691
86947
  );
@@ -86879,7 +87135,7 @@ function makeProgress(target, quiet) {
86879
87135
  let frame = 0;
86880
87136
  let last = `scanning ${target}\u2026`;
86881
87137
  const timer = setInterval(() => {
86882
- process.stderr.write(`\r${chalk35.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
87138
+ process.stderr.write(`\r${chalk36.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
86883
87139
  }, 80);
86884
87140
  return {
86885
87141
  onProgress: (p) => {
@@ -86942,236 +87198,13 @@ function registerScanRepoCommand(program2) {
86942
87198
  if (!telemetryDisabled()) {
86943
87199
  await sendScanRepoPing(buildScanRepoPing(res, target));
86944
87200
  if (!opts.json && !opts.markdown && process.stderr.isTTY) {
86945
- process.stderr.write(chalk35.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
87201
+ process.stderr.write(chalk36.dim(SCAN_REPO_TELEMETRY_NOTICE) + "\n");
86946
87202
  }
86947
87203
  }
86948
87204
  }
86949
87205
  );
86950
87206
  }
86951
87207
 
86952
- // src/cli/commands/egress.ts
86953
- init_config();
86954
- init_keyed_guard();
86955
- init_dist();
86956
- init_egress_config();
86957
- init_egress_config();
86958
- import chalk36 from "chalk";
86959
- function guard(fn) {
86960
- try {
86961
- fn();
86962
- return true;
86963
- } catch (err2) {
86964
- console.error(chalk36.red(`
86965
- \u2717 ${err2.message}
86966
- `));
86967
- process.exitCode = 1;
86968
- return false;
86969
- }
86970
- }
86971
- function mutate(action, change) {
86972
- if (!cliGuardPolicyWrite(action)) return false;
86973
- return guard(() => setEgress(change));
86974
- }
86975
- function addHost(list2, host2) {
86976
- if (!cliGuardPolicyWrite(`egress ${list2} ${host2}`)) return false;
86977
- return guard(() => addEgressHost(list2, host2));
86978
- }
86979
- function exempt2(address) {
86980
- if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
86981
- return guard(() => addSsrfExemption(address));
86982
- }
86983
- var INTERNAL_FIELDS = {
86984
- allowed: { ssrfStrict: false, allowPrivate: true },
86985
- listed: { ssrfStrict: false, allowPrivate: false },
86986
- blocked: { ssrfStrict: true, allowPrivate: false }
86987
- };
86988
- var INTERNAL_SAID = {
86989
- allowed: "reachable without listing them",
86990
- listed: "reachable only if they are on your allowlist",
86991
- blocked: "blocked"
86992
- };
86993
- function readInternalState(e) {
86994
- if (e.ssrfStrict === true) return "blocked";
86995
- return e.allowPrivate === false ? "listed" : "allowed";
86996
- }
86997
- function setInternal(value) {
86998
- const state = value.trim().toLowerCase();
86999
- if (!(state in INTERNAL_FIELDS)) {
87000
- console.error(
87001
- chalk36.red(`
87002
- \u2717 Expected "allowed", "listed" or "blocked", got "${value}".`) + chalk36.gray(
87003
- "\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"
87004
- )
87005
- );
87006
- process.exitCode = 1;
87007
- return;
87008
- }
87009
- if (!mutate(`egress internal ${state}`, INTERNAL_FIELDS[state])) return;
87010
- _resetConfigCache();
87011
- const effective = readInternalState(getConfig().policy.egress);
87012
- if (effective !== state) {
87013
- console.log(
87014
- chalk36.yellow(
87015
- `
87016
- \u26A0 Saved, but not in effect: your workspace sets internal addresses to ${effective.toUpperCase()} and that governs this machine.
87017
- Change it in the dashboard, Enforcement \u2192 Network.
87018
- `
87019
- )
87020
- );
87021
- return;
87022
- }
87023
- const line = `
87024
- \u2713 Internal addresses: ${state} \u2014 loopback, the private ranges and CGNAT are ${INTERNAL_SAID[state]}.
87025
- `;
87026
- console.log(state === "allowed" ? chalk36.yellow(line) : chalk36.green(line));
87027
- }
87028
- function showFloor(e, ssrfStrictSource) {
87029
- console.log(
87030
- chalk36.gray("\n Protected addresses") + chalk36.gray(" \u2014 in shell commands and declared URLs")
87031
- );
87032
- console.log(
87033
- chalk36.gray(
87034
- " always blocked: cloud metadata, link-local, multicast\n no setting releases these, though `node9 pause` suspends all enforcement"
87035
- )
87036
- );
87037
- const state = readInternalState(e);
87038
- const by = ssrfStrictSource === "workspace" ? "workspace (app.node9.ai)" : ssrfStrictSource === "local" ? "this machine (config.json)" : "the shipped default";
87039
- const STATE_LABEL = {
87040
- allowed: "allowed",
87041
- listed: "allowlist only",
87042
- blocked: "blocked"
87043
- };
87044
- console.log(
87045
- ` 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]}`)
87046
- );
87047
- console.log(chalk36.gray(` set by: ${by}`));
87048
- const exemptions = e.ssrfAllow ?? [];
87049
- console.log(chalk36.gray(` Exemptions: ${exemptions.length ? exemptions.join(", ") : "none"}`));
87050
- console.log(
87051
- chalk36.gray(
87052
- " 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."
87053
- )
87054
- );
87055
- }
87056
- function showStatus() {
87057
- const cfg = getConfig();
87058
- const e = cfg.policy.egress;
87059
- 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");
87060
- console.log(chalk36.cyan.bold("\n\u{1F310} Egress control"));
87061
- console.log(" State: " + state);
87062
- if (cfg.policySource === "workspace") {
87063
- console.log(
87064
- chalk36.gray(" Source: workspace config (app.node9.ai) \u2014 local egress settings are ignored")
87065
- );
87066
- }
87067
- console.log(
87068
- chalk36.gray(
87069
- ` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
87070
- )
87071
- );
87072
- showFloor(e, cfg.ssrfStrictSource);
87073
- if (e.allow.length) console.log("\n Your allow: " + e.allow.join(", "));
87074
- if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
87075
- if (!e.enabled) {
87076
- console.log(chalk36.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
87077
- console.log(chalk36.gray(" node9 egress lock (hard-block unknown hosts)"));
87078
- }
87079
- console.log("");
87080
- }
87081
- function registerEgressCommand(program2) {
87082
- const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
87083
- egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
87084
- if (!mutate("egress watch", { enabled: true, mode: "review" })) return;
87085
- console.log(chalk36.green("\n\u2713 Egress is now watched (review mode)."));
87086
- console.log(
87087
- chalk36.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
87088
- );
87089
- console.log(
87090
- chalk36.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
87091
- );
87092
- });
87093
- egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
87094
- if (!mutate("egress lock", { enabled: true, mode: "block" })) return;
87095
- console.log(chalk36.green("\n\u2713 Egress is now locked (block mode)."));
87096
- console.log(chalk36.gray(" Routine hosts are still allowed; unknown hosts are denied."));
87097
- console.log(chalk36.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
87098
- });
87099
- egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host2) => {
87100
- if (!addHost("allow", host2)) return;
87101
- console.log(chalk36.green(`
87102
- \u2713 Allowed egress to ${host2}.
87103
- `));
87104
- });
87105
- egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host2) => {
87106
- if (!addHost("deny", host2)) return;
87107
- console.log(chalk36.green(`
87108
- \u2713 Denied egress to ${host2}.
87109
- `));
87110
- });
87111
- egress.command("off").description("Turn egress control off").action(() => {
87112
- if (!mutate("egress off", { enabled: false })) return;
87113
- console.log(
87114
- chalk36.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
87115
- );
87116
- });
87117
- egress.command("internal <allowed|listed|blocked>").description(
87118
- "How loopback, private ranges and CGNAT are treated: allowed (default), listed (must be on the allowlist), or blocked"
87119
- ).action((value) => setInternal(value));
87120
- egress.command("strict <on|off>").description("Deprecated alias for `egress internal blocked|allowed`").action((value) => {
87121
- const v = value.trim().toLowerCase();
87122
- if (v !== "on" && v !== "off") {
87123
- console.error(chalk36.red(`
87124
- \u2717 Expected "on" or "off", got "${value}".
87125
- `));
87126
- process.exitCode = 1;
87127
- return;
87128
- }
87129
- const state = v === "on" ? "blocked" : getConfig().policy.egress.allowPrivate === false ? "listed" : "allowed";
87130
- console.log(chalk36.gray(`
87131
- (\`egress strict ${v}\` is now \`egress internal ${state}\`)`));
87132
- setInternal(state);
87133
- });
87134
- egress.command("exempt <address>").description("Let an address or a CIDR range through the floor (e.g. 100.64.0.0/10)").action((address) => {
87135
- const a = normalizeEgressHost(address);
87136
- const slash = a.indexOf("/");
87137
- const base2 = slash === -1 ? a : a.slice(0, slash);
87138
- const prefixText = slash === -1 ? null : a.slice(slash + 1);
87139
- if (!normalizeIpLiteral(base2) || prefixText !== null && !/^\d{1,3}$/.test(prefixText)) {
87140
- console.error(
87141
- chalk36.red(`
87142
- \u2717 "${address}" is not an address or a range.`) + chalk36.gray(
87143
- "\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"
87144
- )
87145
- );
87146
- process.exitCode = 1;
87147
- return;
87148
- }
87149
- const m = classifySsrf(base2);
87150
- if (m && !m.overridable) {
87151
- console.error(
87152
- chalk36.red(`
87153
- \u2717 ${a} cannot be exempted.`) + chalk36.gray(
87154
- `
87155
- ${slash === -1 ? "That address is" : "That range covers only"} protected addresses (${m.tier}), which no setting releases.
87156
- `
87157
- )
87158
- );
87159
- process.exitCode = 1;
87160
- return;
87161
- }
87162
- if (!exempt2(a)) return;
87163
- console.log(chalk36.green(`
87164
- \u2713 ${a} is exempt from the floor.`));
87165
- if (!m)
87166
- console.log(
87167
- chalk36.gray(" Note: this address is not on the floor anyway \u2014 nothing changes.\n")
87168
- );
87169
- else console.log("");
87170
- });
87171
- egress.command("status").description("Show the current egress state").action(showStatus);
87172
- egress.action(showStatus);
87173
- }
87174
-
87175
87208
  // src/cli/commands/checks.ts
87176
87209
  init_dist();
87177
87210
  init_config();
@@ -87212,7 +87245,9 @@ function renderChecks(report) {
87212
87245
  const lines = [];
87213
87246
  const where = report.policySource === "workspace" ? "workspace (app.node9.ai)" : "local (~/.node9/config.json)";
87214
87247
  lines.push("");
87215
- lines.push(`${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${report.mode}`);
87248
+ lines.push(
87249
+ `${chalk37.cyan.bold("node9 checks")} policy: ${where} mode: ${levelOf(report.mode)}`
87250
+ );
87216
87251
  lines.push("");
87217
87252
  const row = (c) => {
87218
87253
  const value = (VALUE_COLOR[c.value] ?? chalk37.white)(c.value.padEnd(7));