@node9/proxy 1.10.2 → 1.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -168,8 +168,8 @@ function sanitizeConfig(raw) {
168
168
  }
169
169
  }
170
170
  const lines = result.error.issues.map((issue) => {
171
- const path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
- return ` \u2022 ${path35}: ${issue.message}`;
171
+ const path39 = issue.path.length > 0 ? issue.path.join(".") : "root";
172
+ return ` \u2022 ${path39}: ${issue.message}`;
173
173
  });
174
174
  return {
175
175
  sanitized,
@@ -254,7 +254,8 @@ var init_config_schema = __esm({
254
254
  enableTrustSessions: import_zod.z.boolean().optional(),
255
255
  allowGlobalPause: import_zod.z.boolean().optional(),
256
256
  auditHashArgs: import_zod.z.boolean().optional(),
257
- agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
257
+ agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional(),
258
+ cloudSyncIntervalHours: import_zod.z.number().positive().optional()
258
259
  }).optional(),
259
260
  policy: import_zod.z.object({
260
261
  sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
@@ -514,11 +515,11 @@ function getGlobalSettings() {
514
515
  };
515
516
  }
516
517
  function getCredentials() {
517
- const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
518
+ const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
518
519
  if (process.env.NODE9_API_KEY) {
519
520
  return {
520
521
  apiKey: process.env.NODE9_API_KEY,
521
- apiUrl: process.env.NODE9_API_URL || DEFAULT_API_URL
522
+ apiUrl: process.env.NODE9_API_URL || DEFAULT_API_URL2
522
523
  };
523
524
  }
524
525
  try {
@@ -530,13 +531,13 @@ function getCredentials() {
530
531
  if (profile?.apiKey) {
531
532
  return {
532
533
  apiKey: profile.apiKey,
533
- apiUrl: profile.apiUrl || DEFAULT_API_URL
534
+ apiUrl: profile.apiUrl || DEFAULT_API_URL2
534
535
  };
535
536
  }
536
537
  if (creds.apiKey) {
537
538
  return {
538
539
  apiKey: creds.apiKey,
539
- apiUrl: creds.apiUrl || DEFAULT_API_URL
540
+ apiUrl: creds.apiUrl || DEFAULT_API_URL2
540
541
  };
541
542
  }
542
543
  }
@@ -587,6 +588,8 @@ function getConfig(cwd) {
587
588
  if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
588
589
  mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
589
590
  if (s.environment !== void 0) mergedSettings.environment = s.environment;
591
+ if (s.cloudSyncIntervalHours !== void 0)
592
+ mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
590
593
  if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
591
594
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
592
595
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
@@ -596,7 +599,12 @@ function getConfig(cwd) {
596
599
  if (p.smartRules) {
597
600
  const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
598
601
  const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
599
- mergedPolicy.smartRules = [...defaultBlocks, ...p.smartRules, ...defaultNonBlocks];
602
+ const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
603
+ const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
604
+ const filteredNonBlocks = defaultNonBlocks.filter(
605
+ (r) => !r.name || !userRuleNames.has(r.name)
606
+ );
607
+ mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
600
608
  }
601
609
  if (p.snapshot) {
602
610
  const s2 = p.snapshot;
@@ -630,6 +638,16 @@ function getConfig(cwd) {
630
638
  };
631
639
  applyLayer(globalConfig);
632
640
  applyLayer(projectConfig);
641
+ {
642
+ const cacheFile = import_path3.default.join(import_os3.default.homedir(), ".node9", "rules-cache.json");
643
+ try {
644
+ const raw = JSON.parse(import_fs3.default.readFileSync(cacheFile, "utf-8"));
645
+ if (Array.isArray(raw.rules) && raw.rules.length > 0) {
646
+ applyLayer({ policy: { smartRules: raw.rules } });
647
+ }
648
+ } catch {
649
+ }
650
+ }
633
651
  const shieldOverrides = readShieldOverrides();
634
652
  for (const shieldName of readActiveShields()) {
635
653
  const shield = getShield(shieldName);
@@ -748,7 +766,8 @@ var init_config = __esm({
748
766
  // 120-second auto-deny timeout
749
767
  flightRecorder: true,
750
768
  auditHashArgs: true,
751
- approvers: { native: true, browser: true, cloud: false, terminal: true }
769
+ approvers: { native: true, browser: true, cloud: false, terminal: true },
770
+ cloudSyncIntervalHours: 5
752
771
  },
753
772
  policy: {
754
773
  sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
@@ -1727,9 +1746,9 @@ function matchesPattern(text, patterns) {
1727
1746
  const withoutDotSlash = text.replace(/^\.\//, "");
1728
1747
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1729
1748
  }
1730
- function getNestedValue(obj, path35) {
1749
+ function getNestedValue(obj, path39) {
1731
1750
  if (!obj || typeof obj !== "object") return null;
1732
- return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
1751
+ return path39.split(".").reduce((prev, curr) => prev?.[curr], obj);
1733
1752
  }
1734
1753
  function shouldSnapshot(toolName, args, config) {
1735
1754
  if (!config.settings.enableUndo) return false;
@@ -2204,8 +2223,8 @@ async function explainPolicy(toolName, args) {
2204
2223
  const flattenedArgs = JSON.stringify(args).toLowerCase();
2205
2224
  const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
2206
2225
  allTokens.push(...extraTokens);
2207
- const preview = extraTokens.slice(0, 8).join(", ") + (extraTokens.length > 8 ? "\u2026" : "");
2208
- detail += ` + deep scan of args: [${preview}]`;
2226
+ const preview2 = extraTokens.slice(0, 8).join(", ") + (extraTokens.length > 8 ? "\u2026" : "");
2227
+ detail += ` + deep scan of args: [${preview2}]`;
2209
2228
  }
2210
2229
  steps.push({ name: "Input parsing", outcome: "checked", detail });
2211
2230
  }
@@ -2469,8 +2488,8 @@ function isDaemonRunning() {
2469
2488
  if (port !== DAEMON_PORT) {
2470
2489
  return false;
2471
2490
  }
2472
- const MAX_PID = 4194304;
2473
- if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
2491
+ const MAX_PID2 = 4194304;
2492
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID2) {
2474
2493
  return false;
2475
2494
  }
2476
2495
  try {
@@ -6266,9 +6285,146 @@ var init_costSync = __esm({
6266
6285
  }
6267
6286
  });
6268
6287
 
6288
+ // src/daemon/sync.ts
6289
+ function readCredentials() {
6290
+ if (process.env.NODE9_API_KEY) {
6291
+ return {
6292
+ apiKey: process.env.NODE9_API_KEY,
6293
+ apiUrl: process.env.NODE9_API_URL ?? DEFAULT_API_URL
6294
+ };
6295
+ }
6296
+ try {
6297
+ const credPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "credentials.json");
6298
+ const creds = JSON.parse(import_fs17.default.readFileSync(credPath, "utf-8"));
6299
+ const profileName = process.env.NODE9_PROFILE ?? "default";
6300
+ const profile = creds[profileName];
6301
+ if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
6302
+ return {
6303
+ apiKey: profile.apiKey,
6304
+ apiUrl: typeof profile.apiUrl === "string" ? profile.apiUrl.replace(/\/intercept$/, "/policy") : DEFAULT_API_URL
6305
+ };
6306
+ }
6307
+ if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
6308
+ return { apiKey: creds.apiKey, apiUrl: DEFAULT_API_URL };
6309
+ }
6310
+ } catch {
6311
+ }
6312
+ return null;
6313
+ }
6314
+ function fetchCloudRules(apiKey, apiUrl) {
6315
+ const parsed = new URL(apiUrl);
6316
+ return new Promise((resolve, reject) => {
6317
+ const req = import_https.default.request(
6318
+ {
6319
+ hostname: parsed.hostname,
6320
+ port: parsed.port ? parseInt(parsed.port, 10) : void 0,
6321
+ path: parsed.pathname + parsed.search,
6322
+ method: "GET",
6323
+ headers: {
6324
+ Authorization: `Bearer ${apiKey}`,
6325
+ "Content-Type": "application/json"
6326
+ },
6327
+ timeout: 1e4
6328
+ },
6329
+ (res) => {
6330
+ const chunks = [];
6331
+ res.on("data", (chunk) => chunks.push(chunk));
6332
+ res.on("end", () => {
6333
+ if (res.statusCode !== 200) {
6334
+ reject(new Error(`API returned ${res.statusCode ?? "unknown"}`));
6335
+ return;
6336
+ }
6337
+ try {
6338
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
6339
+ const rules = Array.isArray(body) ? body : Array.isArray(body.rules) ? body.rules : [];
6340
+ resolve(rules);
6341
+ } catch (e) {
6342
+ reject(e);
6343
+ }
6344
+ });
6345
+ }
6346
+ );
6347
+ req.on("error", reject);
6348
+ req.on("timeout", () => {
6349
+ req.destroy(new Error("Cloud policy fetch timed out"));
6350
+ });
6351
+ req.end();
6352
+ });
6353
+ }
6354
+ async function syncOnce() {
6355
+ const creds = readCredentials();
6356
+ if (!creds) return;
6357
+ try {
6358
+ const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
6359
+ const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
6360
+ const dir = import_path20.default.dirname(rulesCacheFile());
6361
+ if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
6362
+ import_fs17.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
6363
+ } catch {
6364
+ }
6365
+ }
6366
+ async function runCloudSync() {
6367
+ const creds = readCredentials();
6368
+ if (!creds) {
6369
+ return { ok: false, reason: "No API key configured. Add credentials with: node9 login" };
6370
+ }
6371
+ try {
6372
+ const rules = await fetchCloudRules(creds.apiKey, creds.apiUrl);
6373
+ const cache = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), rules };
6374
+ const dir = import_path20.default.dirname(rulesCacheFile());
6375
+ if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
6376
+ import_fs17.default.writeFileSync(rulesCacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
6377
+ return { ok: true, rules: rules.length, fetchedAt: cache.fetchedAt };
6378
+ } catch (err2) {
6379
+ return { ok: false, reason: err2 instanceof Error ? err2.message : String(err2) };
6380
+ }
6381
+ }
6382
+ function getCloudSyncStatus() {
6383
+ try {
6384
+ const raw = JSON.parse(import_fs17.default.readFileSync(rulesCacheFile(), "utf-8"));
6385
+ if (!Array.isArray(raw.rules) || typeof raw.fetchedAt !== "string") return { cached: false };
6386
+ return { cached: true, rules: raw.rules.length, fetchedAt: raw.fetchedAt };
6387
+ } catch {
6388
+ return { cached: false };
6389
+ }
6390
+ }
6391
+ function getCloudRules() {
6392
+ try {
6393
+ const raw = JSON.parse(import_fs17.default.readFileSync(rulesCacheFile(), "utf-8"));
6394
+ return Array.isArray(raw.rules) ? raw.rules : null;
6395
+ } catch {
6396
+ return null;
6397
+ }
6398
+ }
6399
+ function startCloudSync() {
6400
+ const rawHours = getConfig().settings.cloudSyncIntervalHours ?? DEFAULT_INTERVAL_HOURS;
6401
+ const intervalHours = Math.max(rawHours, MIN_INTERVAL_HOURS);
6402
+ const intervalMs = intervalHours * 60 * 60 * 1e3;
6403
+ const initial = setTimeout(() => void syncOnce(), 3e4);
6404
+ initial.unref();
6405
+ const recurring = setInterval(() => void syncOnce(), intervalMs);
6406
+ recurring.unref();
6407
+ }
6408
+ var import_fs17, import_https, import_os15, import_path20, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS;
6409
+ var init_sync = __esm({
6410
+ "src/daemon/sync.ts"() {
6411
+ "use strict";
6412
+ import_fs17 = __toESM(require("fs"));
6413
+ import_https = __toESM(require("https"));
6414
+ import_os15 = __toESM(require("os"));
6415
+ import_path20 = __toESM(require("path"));
6416
+ init_config();
6417
+ rulesCacheFile = () => import_path20.default.join(import_os15.default.homedir(), ".node9", "rules-cache.json");
6418
+ DEFAULT_API_URL = "https://api.node9.ai/api/v1/policy";
6419
+ DEFAULT_INTERVAL_HOURS = 5;
6420
+ MIN_INTERVAL_HOURS = 1;
6421
+ }
6422
+ });
6423
+
6269
6424
  // src/daemon/server.ts
6270
6425
  function startDaemon() {
6271
6426
  startCostSync();
6427
+ startCloudSync();
6272
6428
  loadInsightCounts();
6273
6429
  const csrfToken = (0, import_crypto7.randomUUID)();
6274
6430
  const internalToken = (0, import_crypto7.randomUUID)();
@@ -6284,7 +6440,7 @@ function startDaemon() {
6284
6440
  idleTimer = setTimeout(() => {
6285
6441
  if (autoStarted) {
6286
6442
  try {
6287
- import_fs17.default.unlinkSync(DAEMON_PID_FILE);
6443
+ import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6288
6444
  } catch {
6289
6445
  }
6290
6446
  }
@@ -6447,7 +6603,7 @@ data: ${JSON.stringify(item.data)}
6447
6603
  status: "pending"
6448
6604
  });
6449
6605
  }
6450
- const projectCwd = typeof cwd === "string" && import_path20.default.isAbsolute(cwd) ? cwd : void 0;
6606
+ const projectCwd = typeof cwd === "string" && import_path21.default.isAbsolute(cwd) ? cwd : void 0;
6451
6607
  const projectConfig = getConfig(projectCwd);
6452
6608
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
6453
6609
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -6837,8 +6993,8 @@ data: ${JSON.stringify(item.data)}
6837
6993
  const body = await readBody(req);
6838
6994
  const data = body ? JSON.parse(body) : {};
6839
6995
  const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
6840
- const node9Dir = import_path20.default.dirname(GLOBAL_CONFIG_PATH);
6841
- if (!import_path20.default.resolve(configPath).startsWith(node9Dir + import_path20.default.sep)) {
6996
+ const node9Dir = import_path21.default.dirname(GLOBAL_CONFIG_PATH);
6997
+ if (!import_path21.default.resolve(configPath).startsWith(node9Dir + import_path21.default.sep)) {
6842
6998
  res.writeHead(400, { "Content-Type": "application/json" });
6843
6999
  return res.end(
6844
7000
  JSON.stringify({ error: "configPath must be within the node9 config directory" })
@@ -6949,14 +7105,14 @@ data: ${JSON.stringify(item.data)}
6949
7105
  server.on("error", (e) => {
6950
7106
  if (e.code === "EADDRINUSE") {
6951
7107
  try {
6952
- if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
6953
- const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7108
+ if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
7109
+ const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
6954
7110
  process.kill(pid, 0);
6955
7111
  return process.exit(0);
6956
7112
  }
6957
7113
  } catch {
6958
7114
  try {
6959
- import_fs17.default.unlinkSync(DAEMON_PID_FILE);
7115
+ import_fs18.default.unlinkSync(DAEMON_PID_FILE);
6960
7116
  } catch {
6961
7117
  }
6962
7118
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -7015,13 +7171,13 @@ data: ${JSON.stringify(item.data)}
7015
7171
  }
7016
7172
  startActivitySocket();
7017
7173
  }
7018
- var import_http, import_fs17, import_path20, import_crypto7, import_child_process4, import_chalk2;
7174
+ var import_http, import_fs18, import_path21, import_crypto7, import_child_process4, import_chalk2;
7019
7175
  var init_server = __esm({
7020
7176
  "src/daemon/server.ts"() {
7021
7177
  "use strict";
7022
7178
  import_http = __toESM(require("http"));
7023
- import_fs17 = __toESM(require("fs"));
7024
- import_path20 = __toESM(require("path"));
7179
+ import_fs18 = __toESM(require("fs"));
7180
+ import_path21 = __toESM(require("path"));
7025
7181
  import_crypto7 = require("crypto");
7026
7182
  import_child_process4 = require("child_process");
7027
7183
  import_chalk2 = __toESM(require("chalk"));
@@ -7032,57 +7188,327 @@ var init_server = __esm({
7032
7188
  init_patch();
7033
7189
  init_config_schema();
7034
7190
  init_costSync();
7191
+ init_sync();
7192
+ }
7193
+ });
7194
+
7195
+ // src/daemon/service.ts
7196
+ function resolveNode9Binary() {
7197
+ try {
7198
+ const script = process.argv[1];
7199
+ if (typeof script === "string" && import_path22.default.isAbsolute(script) && import_fs19.default.existsSync(script)) {
7200
+ return import_fs19.default.realpathSync(script);
7201
+ }
7202
+ } catch {
7203
+ }
7204
+ try {
7205
+ const cmd = process.platform === "win32" ? "where" : "which";
7206
+ const r = (0, import_child_process5.spawnSync)(cmd, ["node9"], { encoding: "utf8", timeout: 3e3 });
7207
+ if (r.status === 0 && r.stdout.trim()) {
7208
+ return r.stdout.trim().split("\n")[0].trim();
7209
+ }
7210
+ } catch {
7211
+ }
7212
+ return null;
7213
+ }
7214
+ function xmlEscape(s) {
7215
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
7216
+ }
7217
+ function launchdPlist(binaryPath) {
7218
+ const logDir = import_path22.default.join(import_os16.default.homedir(), ".node9");
7219
+ const nodePath = xmlEscape(process.execPath);
7220
+ const scriptPath = xmlEscape(binaryPath);
7221
+ const outLog = xmlEscape(import_path22.default.join(logDir, "daemon.log"));
7222
+ const errLog = xmlEscape(import_path22.default.join(logDir, "daemon-error.log"));
7223
+ return `<?xml version="1.0" encoding="UTF-8"?>
7224
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7225
+ <plist version="1.0">
7226
+ <dict>
7227
+ <key>Label</key>
7228
+ <string>${LAUNCHD_LABEL}</string>
7229
+ <key>ProgramArguments</key>
7230
+ <array>
7231
+ <string>${nodePath}</string>
7232
+ <string>${scriptPath}</string>
7233
+ <string>daemon</string>
7234
+ </array>
7235
+ <key>RunAtLoad</key>
7236
+ <true/>
7237
+ <key>KeepAlive</key>
7238
+ <true/>
7239
+ <key>ThrottleInterval</key>
7240
+ <integer>10</integer>
7241
+ <key>StandardOutPath</key>
7242
+ <string>${outLog}</string>
7243
+ <key>StandardErrorPath</key>
7244
+ <string>${errLog}</string>
7245
+ <key>EnvironmentVariables</key>
7246
+ <dict>
7247
+ <key>NODE9_AUTO_STARTED</key>
7248
+ <string>1</string>
7249
+ <key>NODE9_BROWSER_OPENED</key>
7250
+ <string>1</string>
7251
+ </dict>
7252
+ </dict>
7253
+ </plist>
7254
+ `;
7255
+ }
7256
+ function installLaunchd(binaryPath) {
7257
+ const dir = import_path22.default.dirname(LAUNCHD_PLIST);
7258
+ if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
7259
+ import_fs19.default.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
7260
+ (0, import_child_process5.spawnSync)("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
7261
+ const r = (0, import_child_process5.spawnSync)("launchctl", ["load", "-w", LAUNCHD_PLIST], {
7262
+ encoding: "utf8",
7263
+ timeout: 5e3
7264
+ });
7265
+ if (r.status !== 0) {
7266
+ throw new Error(`launchctl load failed: ${r.stderr || r.stdout || "unknown error"}`);
7267
+ }
7268
+ }
7269
+ function uninstallLaunchd() {
7270
+ if (import_fs19.default.existsSync(LAUNCHD_PLIST)) {
7271
+ (0, import_child_process5.spawnSync)("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
7272
+ import_fs19.default.unlinkSync(LAUNCHD_PLIST);
7273
+ }
7274
+ }
7275
+ function isLaunchdInstalled() {
7276
+ return import_fs19.default.existsSync(LAUNCHD_PLIST);
7277
+ }
7278
+ function systemdUnit(binaryPath) {
7279
+ return `[Unit]
7280
+ Description=node9 approval daemon
7281
+ After=network.target
7282
+
7283
+ [Service]
7284
+ Type=simple
7285
+ ExecStart=${process.execPath} ${binaryPath} daemon
7286
+ Restart=on-failure
7287
+ RestartSec=10s
7288
+ Environment=NODE9_AUTO_STARTED=1
7289
+ Environment=NODE9_BROWSER_OPENED=1
7290
+
7291
+ [Install]
7292
+ WantedBy=default.target
7293
+ `;
7294
+ }
7295
+ function installSystemd(binaryPath) {
7296
+ if (!import_fs19.default.existsSync(SYSTEMD_UNIT_DIR)) {
7297
+ import_fs19.default.mkdirSync(SYSTEMD_UNIT_DIR, { recursive: true });
7298
+ }
7299
+ import_fs19.default.writeFileSync(SYSTEMD_UNIT, systemdUnit(binaryPath), "utf-8");
7300
+ try {
7301
+ (0, import_child_process5.execFileSync)("loginctl", ["enable-linger", import_os16.default.userInfo().username], { timeout: 3e3 });
7302
+ } catch {
7303
+ }
7304
+ const reload = (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], {
7305
+ encoding: "utf8",
7306
+ timeout: 5e3
7307
+ });
7308
+ if (reload.status !== 0) {
7309
+ throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
7310
+ }
7311
+ (0, import_child_process5.spawnSync)("systemctl", ["--user", "stop", "node9-daemon"], { encoding: "utf8", timeout: 3e3 });
7312
+ const enable = (0, import_child_process5.spawnSync)("systemctl", ["--user", "enable", "--now", "node9-daemon"], {
7313
+ encoding: "utf8",
7314
+ timeout: 5e3
7315
+ });
7316
+ if (enable.status !== 0) {
7317
+ throw new Error(`systemctl enable failed: ${enable.stderr}`);
7318
+ }
7319
+ }
7320
+ function uninstallSystemd() {
7321
+ if (import_fs19.default.existsSync(SYSTEMD_UNIT)) {
7322
+ (0, import_child_process5.spawnSync)("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
7323
+ encoding: "utf8",
7324
+ timeout: 5e3
7325
+ });
7326
+ (0, import_child_process5.spawnSync)("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
7327
+ import_fs19.default.unlinkSync(SYSTEMD_UNIT);
7328
+ }
7329
+ }
7330
+ function isSystemdInstalled() {
7331
+ return import_fs19.default.existsSync(SYSTEMD_UNIT);
7332
+ }
7333
+ function stopRunningDaemon() {
7334
+ const pidFile = import_path22.default.join(import_os16.default.homedir(), ".node9", "daemon.pid");
7335
+ if (!import_fs19.default.existsSync(pidFile)) return;
7336
+ try {
7337
+ const data = JSON.parse(import_fs19.default.readFileSync(pidFile, "utf-8"));
7338
+ const pid = data.pid;
7339
+ const MAX_PID2 = 4194304;
7340
+ if (typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid <= MAX_PID2) {
7341
+ try {
7342
+ process.kill(pid, "SIGTERM");
7343
+ const deadline = Date.now() + 3e3;
7344
+ const pollStop = (0, import_child_process5.spawnSync)(
7345
+ "sh",
7346
+ ["-c", `while kill -0 ${pid} 2>/dev/null; do sleep 0.1; done`],
7347
+ {
7348
+ timeout: 3100
7349
+ }
7350
+ );
7351
+ void pollStop;
7352
+ void deadline;
7353
+ } catch {
7354
+ }
7355
+ }
7356
+ try {
7357
+ import_fs19.default.unlinkSync(pidFile);
7358
+ } catch {
7359
+ }
7360
+ } catch {
7361
+ }
7362
+ }
7363
+ function installDaemonService() {
7364
+ const binary = resolveNode9Binary();
7365
+ if (!binary) {
7366
+ return { ok: false, reason: "Could not locate the node9 binary. Is it in your PATH?" };
7367
+ }
7368
+ stopRunningDaemon();
7369
+ try {
7370
+ if (process.platform === "darwin") {
7371
+ const alreadyInstalled = isLaunchdInstalled();
7372
+ installLaunchd(binary);
7373
+ return { ok: true, platform: "launchd", alreadyInstalled };
7374
+ }
7375
+ if (process.platform === "linux") {
7376
+ const check = (0, import_child_process5.spawnSync)("systemctl", ["--user", "--version"], {
7377
+ encoding: "utf8",
7378
+ timeout: 2e3
7379
+ });
7380
+ if (check.status !== 0) {
7381
+ return {
7382
+ ok: false,
7383
+ reason: "systemd not available. Start the daemon manually with: node9 daemon start"
7384
+ };
7385
+ }
7386
+ const alreadyInstalled = isSystemdInstalled();
7387
+ installSystemd(binary);
7388
+ return { ok: true, platform: "systemd", alreadyInstalled };
7389
+ }
7390
+ return {
7391
+ ok: false,
7392
+ reason: `Automatic service install is not supported on ${process.platform}. Start the daemon manually with: node9 daemon start`
7393
+ };
7394
+ } catch (err2) {
7395
+ return {
7396
+ ok: false,
7397
+ reason: err2 instanceof Error ? err2.message : String(err2)
7398
+ };
7399
+ }
7400
+ }
7401
+ function uninstallDaemonService() {
7402
+ try {
7403
+ if (process.platform === "darwin") {
7404
+ uninstallLaunchd();
7405
+ return { ok: true, platform: "launchd", alreadyInstalled: false };
7406
+ }
7407
+ if (process.platform === "linux") {
7408
+ uninstallSystemd();
7409
+ return { ok: true, platform: "systemd", alreadyInstalled: false };
7410
+ }
7411
+ return {
7412
+ ok: false,
7413
+ reason: `Service management not supported on ${process.platform}.`
7414
+ };
7415
+ } catch (err2) {
7416
+ return {
7417
+ ok: false,
7418
+ reason: err2 instanceof Error ? err2.message : String(err2)
7419
+ };
7420
+ }
7421
+ }
7422
+ function isDaemonServiceInstalled() {
7423
+ if (process.platform === "darwin") return isLaunchdInstalled();
7424
+ if (process.platform === "linux") return isSystemdInstalled();
7425
+ return false;
7426
+ }
7427
+ var import_fs19, import_path22, import_os16, import_child_process5, LAUNCHD_LABEL, LAUNCHD_PLIST, SYSTEMD_UNIT_DIR, SYSTEMD_UNIT;
7428
+ var init_service = __esm({
7429
+ "src/daemon/service.ts"() {
7430
+ "use strict";
7431
+ import_fs19 = __toESM(require("fs"));
7432
+ import_path22 = __toESM(require("path"));
7433
+ import_os16 = __toESM(require("os"));
7434
+ import_child_process5 = require("child_process");
7435
+ LAUNCHD_LABEL = "ai.node9.daemon";
7436
+ LAUNCHD_PLIST = import_path22.default.join(import_os16.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7437
+ SYSTEMD_UNIT_DIR = import_path22.default.join(import_os16.default.homedir(), ".config", "systemd", "user");
7438
+ SYSTEMD_UNIT = import_path22.default.join(SYSTEMD_UNIT_DIR, "node9-daemon.service");
7035
7439
  }
7036
7440
  });
7037
7441
 
7038
7442
  // src/daemon/index.ts
7039
7443
  function stopDaemon() {
7040
- if (!import_fs18.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7444
+ if (!import_fs20.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
7041
7445
  try {
7042
- const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7446
+ const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7447
+ const pid = data.pid;
7448
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
7449
+ console.log(import_chalk3.default.gray("Cleaned up invalid PID file."));
7450
+ return;
7451
+ }
7043
7452
  process.kill(pid, "SIGTERM");
7044
7453
  console.log(import_chalk3.default.green("\u2705 Stopped."));
7045
7454
  } catch {
7046
7455
  console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
7047
7456
  } finally {
7048
7457
  try {
7049
- import_fs18.default.unlinkSync(DAEMON_PID_FILE);
7458
+ import_fs20.default.unlinkSync(DAEMON_PID_FILE);
7050
7459
  } catch {
7051
7460
  }
7052
7461
  }
7053
7462
  }
7054
7463
  function daemonStatus() {
7055
- if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
7464
+ const serviceInstalled = isDaemonServiceInstalled();
7465
+ const serviceLabel = serviceInstalled ? import_chalk3.default.green("installed (starts on login)") : import_chalk3.default.yellow("not installed \u2014 run: node9 daemon install");
7466
+ let processStatus;
7467
+ if (import_fs20.default.existsSync(DAEMON_PID_FILE)) {
7056
7468
  try {
7057
- const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7058
- process.kill(pid, 0);
7059
- console.log(import_chalk3.default.green("Node9 daemon: running"));
7060
- return;
7469
+ const data = JSON.parse(import_fs20.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
7470
+ const pid = data.pid;
7471
+ const port = data.port;
7472
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || pid > MAX_PID) {
7473
+ processStatus = import_chalk3.default.yellow("not running (invalid PID file)");
7474
+ } else {
7475
+ process.kill(pid, 0);
7476
+ processStatus = import_chalk3.default.green(
7477
+ `running (PID ${pid}, port ${typeof port === "number" ? port : DAEMON_PORT})`
7478
+ );
7479
+ }
7061
7480
  } catch {
7062
- console.log(import_chalk3.default.yellow("Node9 daemon: not running (stale PID)"));
7063
- return;
7481
+ processStatus = import_chalk3.default.yellow("not running (stale PID file)");
7064
7482
  }
7065
- }
7066
- const r = (0, import_child_process5.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
7067
- encoding: "utf8",
7068
- timeout: 500
7069
- });
7070
- if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
7071
- console.log(import_chalk3.default.yellow("Node9 daemon: running (no PID file \u2014 orphaned)"));
7072
7483
  } else {
7073
- console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
7484
+ const r = (0, import_child_process6.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
7485
+ encoding: "utf8",
7486
+ timeout: 500
7487
+ });
7488
+ if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
7489
+ processStatus = import_chalk3.default.yellow(`running (orphaned \u2014 no PID file)`);
7490
+ } else {
7491
+ processStatus = import_chalk3.default.yellow("not running");
7492
+ }
7074
7493
  }
7494
+ console.log(`
7495
+ Process : ${processStatus}`);
7496
+ console.log(` Service : ${serviceLabel}
7497
+ `);
7075
7498
  }
7076
- var import_fs18, import_chalk3, import_child_process5;
7499
+ var import_fs20, import_chalk3, import_child_process6, MAX_PID;
7077
7500
  var init_daemon2 = __esm({
7078
7501
  "src/daemon/index.ts"() {
7079
7502
  "use strict";
7080
- import_fs18 = __toESM(require("fs"));
7503
+ import_fs20 = __toESM(require("fs"));
7081
7504
  import_chalk3 = __toESM(require("chalk"));
7082
- import_child_process5 = require("child_process");
7505
+ import_child_process6 = require("child_process");
7083
7506
  init_server();
7084
7507
  init_state2();
7508
+ init_service();
7085
7509
  init_state2();
7510
+ init_service();
7511
+ MAX_PID = 4194304;
7086
7512
  }
7087
7513
  });
7088
7514
 
@@ -7111,22 +7537,22 @@ function formatBase(activity) {
7111
7537
  const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
7112
7538
  const icon = getIcon(activity.tool);
7113
7539
  const toolName = activity.tool.slice(0, 16).padEnd(16);
7114
- const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os25.default.homedir(), "~");
7540
+ const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os29.default.homedir(), "~");
7115
7541
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
7116
- return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
7542
+ return `${import_chalk23.default.gray(time)} ${icon} ${import_chalk23.default.white.bold(toolName)} ${import_chalk23.default.dim(argsPreview)}`;
7117
7543
  }
7118
7544
  function renderResult(activity, result) {
7119
7545
  const base = formatBase(activity);
7120
7546
  let status;
7121
7547
  if (result.status === "allow") {
7122
- status = import_chalk19.default.green("\u2713 ALLOW");
7548
+ status = import_chalk23.default.green("\u2713 ALLOW");
7123
7549
  } else if (result.status === "dlp") {
7124
- status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7550
+ status = import_chalk23.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
7125
7551
  } else {
7126
- status = import_chalk19.default.red("\u2717 BLOCK");
7552
+ status = import_chalk23.default.red("\u2717 BLOCK");
7127
7553
  }
7128
7554
  const cost = result.costEstimate ?? activity.costEstimate;
7129
- const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7555
+ const costSuffix = cost == null ? "" : import_chalk23.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
7130
7556
  if (process.stdout.isTTY) {
7131
7557
  if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
7132
7558
  import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
@@ -7143,19 +7569,19 @@ function renderResult(activity, result) {
7143
7569
  }
7144
7570
  function renderPending(activity) {
7145
7571
  if (!process.stdout.isTTY) return;
7146
- const line = `${formatBase(activity)} ${import_chalk19.default.yellow("\u25CF \u2026")}`;
7572
+ const line = `${formatBase(activity)} ${import_chalk23.default.yellow("\u25CF \u2026")}`;
7147
7573
  pendingShownForId = activity.id;
7148
7574
  pendingWrappedLines = wrappedLineCount(line);
7149
7575
  process.stdout.write(`${line}\r`);
7150
7576
  }
7151
7577
  async function ensureDaemon() {
7152
7578
  let pidPort = null;
7153
- if (import_fs29.default.existsSync(PID_FILE)) {
7579
+ if (import_fs33.default.existsSync(PID_FILE)) {
7154
7580
  try {
7155
- const { port } = JSON.parse(import_fs29.default.readFileSync(PID_FILE, "utf-8"));
7581
+ const { port } = JSON.parse(import_fs33.default.readFileSync(PID_FILE, "utf-8"));
7156
7582
  pidPort = port;
7157
7583
  } catch {
7158
- console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7584
+ console.error(import_chalk23.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
7159
7585
  }
7160
7586
  }
7161
7587
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -7166,8 +7592,8 @@ async function ensureDaemon() {
7166
7592
  if (res.ok) return checkPort;
7167
7593
  } catch {
7168
7594
  }
7169
- console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7170
- const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
7595
+ console.log(import_chalk23.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
7596
+ const child = (0, import_child_process15.spawn)(process.execPath, [process.argv[1], "daemon"], {
7171
7597
  detached: true,
7172
7598
  stdio: "ignore",
7173
7599
  env: { ...process.env, NODE9_AUTO_STARTED: "1" }
@@ -7183,7 +7609,7 @@ async function ensureDaemon() {
7183
7609
  } catch {
7184
7610
  }
7185
7611
  }
7186
- console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7612
+ console.error(import_chalk23.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
7187
7613
  process.exit(1);
7188
7614
  }
7189
7615
  function postDecisionHttp(id, decision, csrfToken, port, opts) {
@@ -7215,27 +7641,56 @@ function postDecisionHttp(id, decision, csrfToken, port, opts) {
7215
7641
  req.end(body);
7216
7642
  });
7217
7643
  }
7644
+ function extractArgsSummary(toolName, args) {
7645
+ const a = args;
7646
+ if (!a) return "";
7647
+ const cmd = a["command"] ?? a["cmd"];
7648
+ if (typeof cmd === "string") return cmd.replace(/\s+/g, " ").trim();
7649
+ const fp = a["file_path"] ?? a["path"] ?? a["filepath"];
7650
+ if (typeof fp === "string") return fp;
7651
+ const q = a["query"] ?? a["sql"];
7652
+ if (typeof q === "string") return q.replace(/\s+/g, " ").trim();
7653
+ const s = JSON.stringify(a).replace(/\s+/g, " ");
7654
+ return s.length > 80 ? s.slice(0, 80) + "\u2026" : s;
7655
+ }
7656
+ function cleanReason(raw) {
7657
+ return raw.replace(/\s*[—–-]+\s*(blocked by|requires human approval)[^)]*(\([^)]*\))?\.?\s*$/i, "").trim();
7658
+ }
7659
+ function cleanBlockedBy(raw) {
7660
+ const shieldMatch = raw.match(/shield:([^:]+):/i);
7661
+ if (shieldMatch) return shieldMatch[1];
7662
+ const smartMatch = raw.match(/^Smart Rule:\s*(.+)$/i);
7663
+ if (smartMatch) return smartMatch[1];
7664
+ return raw;
7665
+ }
7218
7666
  function buildCardLines(req, localCount = 0) {
7219
7667
  if (req.recoveryCommand) {
7220
7668
  return buildRecoveryCardLines(req);
7221
7669
  }
7222
- const argsStr = JSON.stringify(req.args ?? {}).replace(/\s+/g, " ");
7223
- const argsPreview = argsStr.length > 60 ? argsStr.slice(0, 60) + "\u2026" : argsStr;
7224
- const tierLabel = req.riskMetadata?.tier != null ? req.riskMetadata.tier <= 2 ? `${YELLOW}\u26A0 Tier ${req.riskMetadata.tier}` : `${RED}\u{1F6D1} Tier ${req.riskMetadata.tier}` : `${YELLOW}\u26A0 Review`;
7225
- const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
7670
+ const argsSummary = extractArgsSummary(req.toolName, req.args);
7671
+ const argsPreview = argsSummary.length > 72 ? argsSummary.slice(0, 72) + "\u2026" : argsSummary;
7672
+ const rawBlockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
7673
+ const blockedBy = cleanBlockedBy(rawBlockedBy);
7674
+ const isBlock = req.riskMetadata?.tier != null && req.riskMetadata.tier <= 2;
7675
+ const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
7676
+ const rawDesc = req.riskMetadata?.ruleDescription ?? "";
7677
+ const description = rawDesc ? cleanReason(rawDesc) : "";
7226
7678
  const lines = [
7227
7679
  ``,
7228
7680
  `${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
7229
7681
  `${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
7230
- `${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
7682
+ `${CYAN}\u2551${RESET2} Policy: ${severityIcon} ${blockedBy}${RESET2}`
7231
7683
  ];
7232
- if (req.riskMetadata?.ruleDescription) {
7233
- lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
7684
+ if (description) {
7685
+ lines.push(`${CYAN}\u2551${RESET2} Why: ${YELLOW}${description}${RESET2}`);
7234
7686
  }
7235
- if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
7687
+ if (req.riskMetadata?.ruleName && rawBlockedBy.includes("Taint")) {
7236
7688
  lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
7237
7689
  }
7238
- lines.push(`${CYAN}\u2551${RESET2} Args: ${GRAY}${argsPreview}${RESET2}`);
7690
+ if (argsPreview) {
7691
+ const argLabel = req.toolName.toLowerCase().includes("bash") ? "Command" : "Args ";
7692
+ lines.push(`${CYAN}\u2551${RESET2} ${argLabel}: ${GRAY}${argsPreview}${RESET2}`);
7693
+ }
7239
7694
  if (localCount >= 2) {
7240
7695
  lines.push(
7241
7696
  `${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
@@ -7275,9 +7730,9 @@ function buildRecoveryCardLines(req) {
7275
7730
  ];
7276
7731
  }
7277
7732
  function readApproversFromDisk() {
7278
- const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
7733
+ const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
7279
7734
  try {
7280
- const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7735
+ const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
7281
7736
  const settings = raw.settings ?? {};
7282
7737
  return settings.approvers ?? {};
7283
7738
  } catch {
@@ -7288,20 +7743,20 @@ function approverStatusLine() {
7288
7743
  const a = readApproversFromDisk();
7289
7744
  const fmt = (label, key) => {
7290
7745
  const on = a[key] !== false;
7291
- return `[${key[0]}]${label.slice(1)} ${on ? import_chalk19.default.green("\u2713") : import_chalk19.default.dim("\u2717")}`;
7746
+ return `[${key[0]}]${label.slice(1)} ${on ? import_chalk23.default.green("\u2713") : import_chalk23.default.dim("\u2717")}`;
7292
7747
  };
7293
7748
  return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
7294
7749
  }
7295
7750
  function toggleApprover(channel) {
7296
- const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
7751
+ const configPath = import_path36.default.join(import_os29.default.homedir(), ".node9", "config.json");
7297
7752
  try {
7298
- const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
7753
+ const raw = JSON.parse(import_fs33.default.readFileSync(configPath, "utf-8"));
7299
7754
  const settings = raw.settings ?? {};
7300
7755
  const approvers = settings.approvers ?? {};
7301
7756
  approvers[channel] = approvers[channel] === false;
7302
7757
  settings.approvers = approvers;
7303
7758
  raw.settings = settings;
7304
- import_fs29.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7759
+ import_fs33.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
7305
7760
  } catch (err2) {
7306
7761
  process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
7307
7762
  `);
@@ -7333,7 +7788,7 @@ async function startTail(options = {}) {
7333
7788
  req2.end();
7334
7789
  });
7335
7790
  if (result.ok) {
7336
- console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
7791
+ console.log(import_chalk23.default.green("\u2713 Flight Recorder buffer cleared."));
7337
7792
  } else if (result.code === "ECONNREFUSED") {
7338
7793
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
7339
7794
  } else if (result.code === "ETIMEDOUT") {
@@ -7377,7 +7832,7 @@ async function startTail(options = {}) {
7377
7832
  const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
7378
7833
  if (channel) {
7379
7834
  toggleApprover(channel);
7380
- console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
7835
+ console.log(import_chalk23.default.dim(` Approvers: ${approverStatusLine()}`));
7381
7836
  }
7382
7837
  };
7383
7838
  process.stdin.on("keypress", idleKeypressHandler);
@@ -7443,7 +7898,7 @@ async function startTail(options = {}) {
7443
7898
  localAllowCounts.get(req2.toolName) ?? 0
7444
7899
  )
7445
7900
  );
7446
- const decisionStamp = action === "always-allow" ? import_chalk19.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk19.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk19.default.yellow("\u21A9 REDIRECT AI") : import_chalk19.default.red("\u2717 DENIED");
7901
+ const decisionStamp = action === "always-allow" ? import_chalk23.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk23.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk23.default.yellow("\u21A9 REDIRECT AI") : import_chalk23.default.red("\u2717 DENIED");
7447
7902
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
7448
7903
  for (const line of stampedLines) process.stdout.write(line + "\n");
7449
7904
  process.stdout.write(SHOW_CURSOR);
@@ -7471,8 +7926,8 @@ async function startTail(options = {}) {
7471
7926
  }
7472
7927
  postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
7473
7928
  try {
7474
- import_fs29.default.appendFileSync(
7475
- import_path32.default.join(import_os25.default.homedir(), ".node9", "hook-debug.log"),
7929
+ import_fs33.default.appendFileSync(
7930
+ import_path36.default.join(import_os29.default.homedir(), ".node9", "hook-debug.log"),
7476
7931
  `[tail] POST /decision failed: ${String(err2)}
7477
7932
  `
7478
7933
  );
@@ -7494,7 +7949,7 @@ async function startTail(options = {}) {
7494
7949
  );
7495
7950
  const stampedLines = buildCardLines(req2, priorCount);
7496
7951
  if (externalDecision) {
7497
- const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
7952
+ const source = externalDecision === "allow" ? import_chalk23.default.green("\u2713 ALLOWED") : import_chalk23.default.red("\u2717 DENIED");
7498
7953
  stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
7499
7954
  }
7500
7955
  for (const line of stampedLines) process.stdout.write(line + "\n");
@@ -7540,10 +7995,10 @@ async function startTail(options = {}) {
7540
7995
  try {
7541
7996
  const browserEnabled = getConfig().settings.approvers?.browser !== false;
7542
7997
  if (browserEnabled) {
7543
- if (process.platform === "darwin") (0, import_child_process14.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
7998
+ if (process.platform === "darwin") (0, import_child_process15.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
7544
7999
  else if (process.platform === "win32")
7545
- (0, import_child_process14.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
7546
- else (0, import_child_process14.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
8000
+ (0, import_child_process15.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
8001
+ else (0, import_child_process15.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
7547
8002
  const intToken = getInternalToken();
7548
8003
  fetch(`http://127.0.0.1:${port}/browser-opened`, {
7549
8004
  method: "POST",
@@ -7553,16 +8008,16 @@ async function startTail(options = {}) {
7553
8008
  }
7554
8009
  } catch {
7555
8010
  }
7556
- console.log(import_chalk19.default.cyan.bold(`
7557
- \u{1F6F0}\uFE0F Node9 tail `) + import_chalk19.default.dim(`\u2192 ${dashboardUrl}`));
8011
+ console.log(import_chalk23.default.cyan.bold(`
8012
+ \u{1F6F0}\uFE0F Node9 tail `) + import_chalk23.default.dim(`\u2192 ${dashboardUrl}`));
7558
8013
  if (canApprove) {
7559
- console.log(import_chalk19.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
7560
- console.log(import_chalk19.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
8014
+ console.log(import_chalk23.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
8015
+ console.log(import_chalk23.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
7561
8016
  }
7562
8017
  if (options.history) {
7563
- console.log(import_chalk19.default.dim("Showing history + live events.\n"));
8018
+ console.log(import_chalk23.default.dim("Showing history + live events.\n"));
7564
8019
  } else {
7565
- console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
8020
+ console.log(import_chalk23.default.dim("Showing live events only. Use --history to include past.\n"));
7566
8021
  }
7567
8022
  process.on("SIGINT", () => {
7568
8023
  exitIdleMode();
@@ -7572,13 +8027,13 @@ async function startTail(options = {}) {
7572
8027
  import_readline5.default.clearLine(process.stdout, 0);
7573
8028
  import_readline5.default.cursorTo(process.stdout, 0);
7574
8029
  }
7575
- console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
8030
+ console.log(import_chalk23.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
7576
8031
  process.exit(0);
7577
8032
  });
7578
8033
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
7579
8034
  const req = import_http2.default.get(sseUrl, (res) => {
7580
8035
  if (res.statusCode !== 200) {
7581
- console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
8036
+ console.error(import_chalk23.default.red(`Failed to connect: HTTP ${res.statusCode}`));
7582
8037
  process.exit(1);
7583
8038
  }
7584
8039
  if (canApprove) enterIdleMode();
@@ -7609,7 +8064,7 @@ async function startTail(options = {}) {
7609
8064
  import_readline5.default.clearLine(process.stdout, 0);
7610
8065
  import_readline5.default.cursorTo(process.stdout, 0);
7611
8066
  }
7612
- console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
8067
+ console.log(import_chalk23.default.red("\n\u274C Daemon disconnected."));
7613
8068
  process.exit(1);
7614
8069
  });
7615
8070
  });
@@ -7701,9 +8156,9 @@ async function startTail(options = {}) {
7701
8156
  const hash = data.hash ?? "";
7702
8157
  const summary = data.argsSummary ?? data.tool;
7703
8158
  const fileCount = data.fileCount ?? 0;
7704
- const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
8159
+ const files = fileCount > 0 ? import_chalk23.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
7705
8160
  process.stdout.write(
7706
- `${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
8161
+ `${import_chalk23.default.dim(time)} ${import_chalk23.default.cyan("\u{1F4F8} snapshot")} ${import_chalk23.default.dim(hash)} ${summary}${files}
7707
8162
  `
7708
8163
  );
7709
8164
  return;
@@ -7720,26 +8175,26 @@ async function startTail(options = {}) {
7720
8175
  }
7721
8176
  req.on("error", (err2) => {
7722
8177
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
7723
- console.error(import_chalk19.default.red(`
8178
+ console.error(import_chalk23.default.red(`
7724
8179
  \u274C ${msg}`));
7725
8180
  process.exit(1);
7726
8181
  });
7727
8182
  }
7728
- var import_http2, import_chalk19, import_fs29, import_os25, import_path32, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
8183
+ var import_http2, import_chalk23, import_fs33, import_os29, import_path36, import_readline5, import_child_process15, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
7729
8184
  var init_tail = __esm({
7730
8185
  "src/tui/tail.ts"() {
7731
8186
  "use strict";
7732
8187
  import_http2 = __toESM(require("http"));
7733
- import_chalk19 = __toESM(require("chalk"));
7734
- import_fs29 = __toESM(require("fs"));
7735
- import_os25 = __toESM(require("os"));
7736
- import_path32 = __toESM(require("path"));
8188
+ import_chalk23 = __toESM(require("chalk"));
8189
+ import_fs33 = __toESM(require("fs"));
8190
+ import_os29 = __toESM(require("os"));
8191
+ import_path36 = __toESM(require("path"));
7737
8192
  import_readline5 = __toESM(require("readline"));
7738
- import_child_process14 = require("child_process");
8193
+ import_child_process15 = require("child_process");
7739
8194
  init_daemon2();
7740
8195
  init_daemon();
7741
8196
  init_core();
7742
- PID_FILE = import_path32.default.join(import_os25.default.homedir(), ".node9", "daemon.pid");
8197
+ PID_FILE = import_path36.default.join(import_os29.default.homedir(), ".node9", "daemon.pid");
7743
8198
  ICONS = {
7744
8199
  bash: "\u{1F4BB}",
7745
8200
  shell: "\u{1F4BB}",
@@ -7854,9 +8309,9 @@ function formatTimeLeft(resetsAt) {
7854
8309
  return ` (${m}m left)`;
7855
8310
  }
7856
8311
  function safeReadJson(filePath) {
7857
- if (!import_fs30.default.existsSync(filePath)) return null;
8312
+ if (!import_fs34.default.existsSync(filePath)) return null;
7858
8313
  try {
7859
- return JSON.parse(import_fs30.default.readFileSync(filePath, "utf-8"));
8314
+ return JSON.parse(import_fs34.default.readFileSync(filePath, "utf-8"));
7860
8315
  } catch {
7861
8316
  return null;
7862
8317
  }
@@ -7877,12 +8332,12 @@ function countHooksInFile(filePath) {
7877
8332
  return Object.keys(cfg.hooks).length;
7878
8333
  }
7879
8334
  function countRulesInDir(rulesDir) {
7880
- if (!import_fs30.default.existsSync(rulesDir)) return 0;
8335
+ if (!import_fs34.default.existsSync(rulesDir)) return 0;
7881
8336
  let count = 0;
7882
8337
  try {
7883
- for (const entry of import_fs30.default.readdirSync(rulesDir, { withFileTypes: true })) {
8338
+ for (const entry of import_fs34.default.readdirSync(rulesDir, { withFileTypes: true })) {
7884
8339
  if (entry.isDirectory()) {
7885
- count += countRulesInDir(import_path33.default.join(rulesDir, entry.name));
8340
+ count += countRulesInDir(import_path37.default.join(rulesDir, entry.name));
7886
8341
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
7887
8342
  count++;
7888
8343
  }
@@ -7893,46 +8348,46 @@ function countRulesInDir(rulesDir) {
7893
8348
  }
7894
8349
  function isSamePath(a, b) {
7895
8350
  try {
7896
- return import_path33.default.resolve(a) === import_path33.default.resolve(b);
8351
+ return import_path37.default.resolve(a) === import_path37.default.resolve(b);
7897
8352
  } catch {
7898
8353
  return false;
7899
8354
  }
7900
8355
  }
7901
8356
  function countConfigs(cwd) {
7902
- const homeDir2 = import_os26.default.homedir();
7903
- const claudeDir = import_path33.default.join(homeDir2, ".claude");
8357
+ const homeDir2 = import_os30.default.homedir();
8358
+ const claudeDir = import_path37.default.join(homeDir2, ".claude");
7904
8359
  let claudeMdCount = 0;
7905
8360
  let rulesCount = 0;
7906
8361
  let hooksCount = 0;
7907
8362
  const userMcpServers = /* @__PURE__ */ new Set();
7908
8363
  const projectMcpServers = /* @__PURE__ */ new Set();
7909
- if (import_fs30.default.existsSync(import_path33.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
7910
- rulesCount += countRulesInDir(import_path33.default.join(claudeDir, "rules"));
7911
- const userSettings = import_path33.default.join(claudeDir, "settings.json");
8364
+ if (import_fs34.default.existsSync(import_path37.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
8365
+ rulesCount += countRulesInDir(import_path37.default.join(claudeDir, "rules"));
8366
+ const userSettings = import_path37.default.join(claudeDir, "settings.json");
7912
8367
  for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
7913
8368
  hooksCount += countHooksInFile(userSettings);
7914
- const userClaudeJson = import_path33.default.join(homeDir2, ".claude.json");
8369
+ const userClaudeJson = import_path37.default.join(homeDir2, ".claude.json");
7915
8370
  for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
7916
8371
  for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
7917
8372
  userMcpServers.delete(name);
7918
8373
  }
7919
8374
  if (cwd) {
7920
- if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
7921
- if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
7922
- const projectClaudeDir = import_path33.default.join(cwd, ".claude");
8375
+ if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
8376
+ if (import_fs34.default.existsSync(import_path37.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
8377
+ const projectClaudeDir = import_path37.default.join(cwd, ".claude");
7923
8378
  const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
7924
8379
  if (!overlapsUserScope) {
7925
- if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
7926
- rulesCount += countRulesInDir(import_path33.default.join(projectClaudeDir, "rules"));
7927
- const projSettings = import_path33.default.join(projectClaudeDir, "settings.json");
8380
+ if (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
8381
+ rulesCount += countRulesInDir(import_path37.default.join(projectClaudeDir, "rules"));
8382
+ const projSettings = import_path37.default.join(projectClaudeDir, "settings.json");
7928
8383
  for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
7929
8384
  hooksCount += countHooksInFile(projSettings);
7930
8385
  }
7931
- if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
7932
- const localSettings = import_path33.default.join(projectClaudeDir, "settings.local.json");
8386
+ if (import_fs34.default.existsSync(import_path37.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
8387
+ const localSettings = import_path37.default.join(projectClaudeDir, "settings.local.json");
7933
8388
  for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
7934
8389
  hooksCount += countHooksInFile(localSettings);
7935
- const mcpJsonServers = getMcpServerNames(import_path33.default.join(cwd, ".mcp.json"));
8390
+ const mcpJsonServers = getMcpServerNames(import_path37.default.join(cwd, ".mcp.json"));
7936
8391
  const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
7937
8392
  for (const name of disabledMcpJson) mcpJsonServers.delete(name);
7938
8393
  for (const name of mcpJsonServers) projectMcpServers.add(name);
@@ -7965,12 +8420,12 @@ function readActiveShieldsHud() {
7965
8420
  return shieldsCache.value;
7966
8421
  }
7967
8422
  try {
7968
- const shieldsPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "shields.json");
7969
- if (!import_fs30.default.existsSync(shieldsPath)) {
8423
+ const shieldsPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "shields.json");
8424
+ if (!import_fs34.default.existsSync(shieldsPath)) {
7970
8425
  shieldsCache = { value: [], ts: now };
7971
8426
  return [];
7972
8427
  }
7973
- const parsed = JSON.parse(import_fs30.default.readFileSync(shieldsPath, "utf-8"));
8428
+ const parsed = JSON.parse(import_fs34.default.readFileSync(shieldsPath, "utf-8"));
7974
8429
  if (!Array.isArray(parsed.active)) {
7975
8430
  shieldsCache = { value: [], ts: now };
7976
8431
  return [];
@@ -8072,17 +8527,17 @@ function renderContextLine(stdin) {
8072
8527
  async function main() {
8073
8528
  try {
8074
8529
  const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
8075
- if (import_fs30.default.existsSync(import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug"))) {
8530
+ if (import_fs34.default.existsSync(import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug"))) {
8076
8531
  try {
8077
- const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug.log");
8532
+ const logPath = import_path37.default.join(import_os30.default.homedir(), ".node9", "hud-debug.log");
8078
8533
  const MAX_LOG_SIZE = 10 * 1024 * 1024;
8079
8534
  let size = 0;
8080
8535
  try {
8081
- size = import_fs30.default.statSync(logPath).size;
8536
+ size = import_fs34.default.statSync(logPath).size;
8082
8537
  } catch {
8083
8538
  }
8084
8539
  if (size < MAX_LOG_SIZE) {
8085
- import_fs30.default.appendFileSync(
8540
+ import_fs34.default.appendFileSync(
8086
8541
  logPath,
8087
8542
  JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
8088
8543
  );
@@ -8103,11 +8558,11 @@ async function main() {
8103
8558
  try {
8104
8559
  const cwd = stdin.cwd ?? process.cwd();
8105
8560
  for (const configPath of [
8106
- import_path33.default.join(cwd, "node9.config.json"),
8107
- import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json")
8561
+ import_path37.default.join(cwd, "node9.config.json"),
8562
+ import_path37.default.join(import_os30.default.homedir(), ".node9", "config.json")
8108
8563
  ]) {
8109
- if (!import_fs30.default.existsSync(configPath)) continue;
8110
- const cfg = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
8564
+ if (!import_fs34.default.existsSync(configPath)) continue;
8565
+ const cfg = JSON.parse(import_fs34.default.readFileSync(configPath, "utf-8"));
8111
8566
  const hud = cfg.settings?.hud;
8112
8567
  if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
8113
8568
  }
@@ -8125,13 +8580,13 @@ async function main() {
8125
8580
  renderOffline();
8126
8581
  }
8127
8582
  }
8128
- var import_fs30, import_path33, import_os26, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8583
+ var import_fs34, import_path37, import_os30, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
8129
8584
  var init_hud = __esm({
8130
8585
  "src/cli/hud.ts"() {
8131
8586
  "use strict";
8132
- import_fs30 = __toESM(require("fs"));
8133
- import_path33 = __toESM(require("path"));
8134
- import_os26 = __toESM(require("os"));
8587
+ import_fs34 = __toESM(require("fs"));
8588
+ import_path37 = __toESM(require("path"));
8589
+ import_os30 = __toESM(require("os"));
8135
8590
  import_http3 = __toESM(require("http"));
8136
8591
  init_daemon();
8137
8592
  RESET3 = "\x1B[0m";
@@ -8542,7 +8997,9 @@ function detectAgents(homeDir2 = import_os11.default.homedir()) {
8542
8997
  claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
8543
8998
  gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
8544
8999
  cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
8545
- codex: exists(import_path15.default.join(homeDir2, ".codex"))
9000
+ codex: exists(import_path15.default.join(homeDir2, ".codex")),
9001
+ windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
9002
+ vscode: exists(import_path15.default.join(homeDir2, ".vscode"))
8546
9003
  };
8547
9004
  }
8548
9005
  async function setupCursor() {
@@ -8691,6 +9148,38 @@ async function setupCodex() {
8691
9148
  printDaemonTip();
8692
9149
  }
8693
9150
  }
9151
+ function teardownCodex() {
9152
+ const homeDir2 = import_os11.default.homedir();
9153
+ const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
9154
+ const config = readToml(configPath);
9155
+ if (!config?.mcp_servers) {
9156
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
9157
+ return;
9158
+ }
9159
+ let changed = false;
9160
+ if (removeNode9McpServer(config.mcp_servers)) {
9161
+ changed = true;
9162
+ console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.codex/config.toml"));
9163
+ }
9164
+ for (const [name, server] of Object.entries(config.mcp_servers)) {
9165
+ const args = server.args;
9166
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
9167
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
9168
+ config.mcp_servers[name] = {
9169
+ ...server,
9170
+ command: originalCmd,
9171
+ args: originalArgs.length ? originalArgs : void 0
9172
+ };
9173
+ changed = true;
9174
+ }
9175
+ }
9176
+ if (changed) {
9177
+ writeToml(configPath, config);
9178
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codex/config.toml"));
9179
+ } else {
9180
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codex/config.toml"));
9181
+ }
9182
+ }
8694
9183
  function setupHud() {
8695
9184
  const homeDir2 = import_os11.default.homedir();
8696
9185
  const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
@@ -8737,54 +9226,326 @@ function teardownHud() {
8737
9226
  console.log(import_chalk.default.green(" \u2705 node9 HUD removed from ~/.claude/settings.json"));
8738
9227
  console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
8739
9228
  }
8740
-
8741
- // src/cli.ts
8742
- init_daemon2();
8743
- var import_chalk20 = __toESM(require("chalk"));
8744
- var import_fs31 = __toESM(require("fs"));
8745
- var import_path34 = __toESM(require("path"));
8746
- var import_os27 = __toESM(require("os"));
8747
- var import_prompts2 = require("@inquirer/prompts");
8748
-
8749
- // src/utils/duration.ts
8750
- function parseDuration(str) {
8751
- const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
8752
- if (!m) return null;
8753
- const n = parseFloat(m[1]);
8754
- switch ((m[2] ?? "m").toLowerCase()) {
8755
- case "s":
8756
- return Math.round(n * 1e3);
8757
- case "m":
8758
- return Math.round(n * 6e4);
8759
- case "h":
8760
- return Math.round(n * 36e5);
8761
- case "d":
8762
- return Math.round(n * 864e5);
8763
- default:
8764
- return null;
9229
+ async function setupWindsurf() {
9230
+ const homeDir2 = import_os11.default.homedir();
9231
+ const mcpPath = import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
9232
+ const mcpConfig = readJson(mcpPath) ?? {};
9233
+ const servers = mcpConfig.mcpServers ?? {};
9234
+ let anythingChanged = false;
9235
+ if (!hasNode9McpServer(servers)) {
9236
+ servers["node9"] = NODE9_MCP_SERVER_ENTRY;
9237
+ mcpConfig.mcpServers = servers;
9238
+ writeJson(mcpPath, mcpConfig);
9239
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
9240
+ anythingChanged = true;
8765
9241
  }
8766
- }
8767
-
8768
- // src/proxy/index.ts
8769
- var import_readline = __toESM(require("readline"));
8770
- var import_chalk4 = __toESM(require("chalk"));
8771
- var import_child_process6 = require("child_process");
8772
- var import_execa = require("execa");
8773
- var import_execa2 = require("execa");
8774
- init_orchestrator();
8775
-
8776
- // src/policy/negotiation.ts
8777
- function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand) {
8778
- if (isHumanDecision) {
8779
- return `NODE9: The human user rejected this action.
8780
- REASON: ${humanReason || "No specific reason provided."}
8781
- INSTRUCTIONS:
8782
- - Do NOT retry this exact command.
8783
- - Acknowledge the block to the user and ask if there is an alternative approach.
8784
- - If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
9242
+ const serversToWrap = [];
9243
+ for (const [name, server] of Object.entries(servers)) {
9244
+ if (!server.command || server.command === "node9") continue;
9245
+ serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
8785
9246
  }
8786
- const label = blockedByLabel.toLowerCase();
8787
- if (label.includes("dlp") || label.includes("secret detected") || label.includes("credential review")) {
9247
+ if (serversToWrap.length > 0) {
9248
+ console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
9249
+ console.log(import_chalk.default.white(` ${mcpPath}`));
9250
+ for (const { name, upstream } of serversToWrap) {
9251
+ console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
9252
+ }
9253
+ console.log("");
9254
+ const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
9255
+ if (proceed) {
9256
+ for (const { name, upstream } of serversToWrap) {
9257
+ servers[name] = {
9258
+ ...servers[name],
9259
+ command: "node9",
9260
+ args: ["mcp", "--upstream", upstream]
9261
+ };
9262
+ }
9263
+ mcpConfig.mcpServers = servers;
9264
+ writeJson(mcpPath, mcpConfig);
9265
+ console.log(import_chalk.default.green(`
9266
+ \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
9267
+ anythingChanged = true;
9268
+ } else {
9269
+ console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
9270
+ }
9271
+ console.log("");
9272
+ }
9273
+ console.log(
9274
+ import_chalk.default.yellow(
9275
+ " \u26A0\uFE0F Note: Windsurf does not yet support native pre-execution hooks.\n MCP proxy wrapping is the only supported protection mode for Windsurf."
9276
+ )
9277
+ );
9278
+ console.log("");
9279
+ if (!anythingChanged && serversToWrap.length === 0) {
9280
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Windsurf."));
9281
+ printDaemonTip();
9282
+ return;
9283
+ }
9284
+ if (anythingChanged) {
9285
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Windsurf via MCP proxy!"));
9286
+ console.log(import_chalk.default.gray(" Restart Windsurf for changes to take effect."));
9287
+ printDaemonTip();
9288
+ }
9289
+ }
9290
+ function teardownWindsurf() {
9291
+ const homeDir2 = import_os11.default.homedir();
9292
+ const mcpPath = import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json");
9293
+ const mcpConfig = readJson(mcpPath);
9294
+ if (!mcpConfig?.mcpServers) {
9295
+ console.log(
9296
+ import_chalk.default.blue(" \u2139\uFE0F ~/.codeium/windsurf/mcp_config.json not found \u2014 nothing to remove")
9297
+ );
9298
+ return;
9299
+ }
9300
+ let changed = false;
9301
+ if (removeNode9McpServer(mcpConfig.mcpServers)) {
9302
+ changed = true;
9303
+ console.log(
9304
+ import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.codeium/windsurf/mcp_config.json")
9305
+ );
9306
+ }
9307
+ for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
9308
+ const args = server.args;
9309
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
9310
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
9311
+ mcpConfig.mcpServers[name] = {
9312
+ ...server,
9313
+ command: originalCmd,
9314
+ args: originalArgs.length ? originalArgs : void 0
9315
+ };
9316
+ changed = true;
9317
+ }
9318
+ }
9319
+ if (changed) {
9320
+ writeJson(mcpPath, mcpConfig);
9321
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codeium/windsurf/mcp_config.json"));
9322
+ } else {
9323
+ console.log(
9324
+ import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codeium/windsurf/mcp_config.json")
9325
+ );
9326
+ }
9327
+ }
9328
+ function hasNode9McpServerVSCode(servers) {
9329
+ const entry = servers["node9"];
9330
+ return !!entry && entry.command === "node9" && Array.isArray(entry.args) && entry.args[0] === "mcp-server";
9331
+ }
9332
+ async function setupVSCode() {
9333
+ const homeDir2 = import_os11.default.homedir();
9334
+ const mcpPath = import_path15.default.join(homeDir2, ".vscode", "mcp.json");
9335
+ const mcpConfig = readJson(mcpPath) ?? {};
9336
+ const servers = mcpConfig.servers ?? {};
9337
+ let anythingChanged = false;
9338
+ if (!hasNode9McpServerVSCode(servers)) {
9339
+ servers["node9"] = { type: "stdio", command: "node9", args: ["mcp-server"] };
9340
+ mcpConfig.servers = servers;
9341
+ writeJson(mcpPath, mcpConfig);
9342
+ console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
9343
+ anythingChanged = true;
9344
+ }
9345
+ const serversToWrap = [];
9346
+ for (const [name, server] of Object.entries(servers)) {
9347
+ if (!server.command || server.command === "node9") continue;
9348
+ serversToWrap.push({ name, upstream: [server.command, ...server.args ?? []].join(" ") });
9349
+ }
9350
+ if (serversToWrap.length > 0) {
9351
+ console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
9352
+ console.log(import_chalk.default.white(` ${mcpPath}`));
9353
+ for (const { name, upstream } of serversToWrap) {
9354
+ console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
9355
+ }
9356
+ console.log("");
9357
+ const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
9358
+ if (proceed) {
9359
+ for (const { name, upstream } of serversToWrap) {
9360
+ servers[name] = {
9361
+ ...servers[name],
9362
+ type: "stdio",
9363
+ command: "node9",
9364
+ args: ["mcp", "--upstream", upstream]
9365
+ };
9366
+ }
9367
+ mcpConfig.servers = servers;
9368
+ writeJson(mcpPath, mcpConfig);
9369
+ console.log(import_chalk.default.green(`
9370
+ \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
9371
+ anythingChanged = true;
9372
+ } else {
9373
+ console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
9374
+ }
9375
+ console.log("");
9376
+ }
9377
+ console.log(
9378
+ import_chalk.default.yellow(
9379
+ " \u26A0\uFE0F Note: VSCode MCP support requires the GitHub Copilot extension (v1.99+).\n Pre-execution hooks are not supported \u2014 MCP proxy wrapping only."
9380
+ )
9381
+ );
9382
+ console.log("");
9383
+ if (!anythingChanged && serversToWrap.length === 0) {
9384
+ console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for VSCode."));
9385
+ printDaemonTip();
9386
+ return;
9387
+ }
9388
+ if (anythingChanged) {
9389
+ console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting VSCode via MCP proxy!"));
9390
+ console.log(import_chalk.default.gray(" Restart VSCode for changes to take effect."));
9391
+ printDaemonTip();
9392
+ }
9393
+ }
9394
+ function teardownVSCode() {
9395
+ const homeDir2 = import_os11.default.homedir();
9396
+ const mcpPath = import_path15.default.join(homeDir2, ".vscode", "mcp.json");
9397
+ const mcpConfig = readJson(mcpPath);
9398
+ if (!mcpConfig?.servers) {
9399
+ console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.vscode/mcp.json not found \u2014 nothing to remove"));
9400
+ return;
9401
+ }
9402
+ let changed = false;
9403
+ if (hasNode9McpServerVSCode(mcpConfig.servers)) {
9404
+ delete mcpConfig.servers["node9"];
9405
+ changed = true;
9406
+ console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.vscode/mcp.json"));
9407
+ }
9408
+ for (const [name, server] of Object.entries(mcpConfig.servers)) {
9409
+ const args = server.args;
9410
+ if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
9411
+ const [originalCmd, ...originalArgs] = args[2].split(" ");
9412
+ mcpConfig.servers[name] = {
9413
+ ...server,
9414
+ type: "stdio",
9415
+ command: originalCmd,
9416
+ args: originalArgs.length ? originalArgs : void 0
9417
+ };
9418
+ changed = true;
9419
+ }
9420
+ }
9421
+ if (changed) {
9422
+ writeJson(mcpPath, mcpConfig);
9423
+ console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.vscode/mcp.json"));
9424
+ } else {
9425
+ console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.vscode/mcp.json"));
9426
+ }
9427
+ }
9428
+ function getAgentsStatus(homeDir2 = import_os11.default.homedir()) {
9429
+ const detected = detectAgents(homeDir2);
9430
+ const claudeWired = (() => {
9431
+ const settings = readJson(import_path15.default.join(homeDir2, ".claude", "settings.json"));
9432
+ return !!settings?.hooks?.PreToolUse?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
9433
+ })();
9434
+ const geminiWired = (() => {
9435
+ const settings = readJson(import_path15.default.join(homeDir2, ".gemini", "settings.json"));
9436
+ return !!settings?.hooks?.BeforeTool?.some((m) => m.hooks.some((h) => isNode9Hook(h.command)));
9437
+ })();
9438
+ const cursorWired = (() => {
9439
+ const cfg = readJson(import_path15.default.join(homeDir2, ".cursor", "mcp.json"));
9440
+ return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
9441
+ })();
9442
+ const codexWired = (() => {
9443
+ const cfg = readToml(import_path15.default.join(homeDir2, ".codex", "config.toml"));
9444
+ return !!(cfg?.mcp_servers && hasNode9McpServer(cfg.mcp_servers));
9445
+ })();
9446
+ const windsurfWired = (() => {
9447
+ const cfg = readJson(
9448
+ import_path15.default.join(homeDir2, ".codeium", "windsurf", "mcp_config.json")
9449
+ );
9450
+ return !!(cfg?.mcpServers && hasNode9McpServer(cfg.mcpServers));
9451
+ })();
9452
+ const vscodeWired = (() => {
9453
+ const cfg = readJson(import_path15.default.join(homeDir2, ".vscode", "mcp.json"));
9454
+ return !!(cfg?.servers && hasNode9McpServerVSCode(cfg.servers));
9455
+ })();
9456
+ return [
9457
+ {
9458
+ name: "claude",
9459
+ label: "Claude Code",
9460
+ installed: detected.claude,
9461
+ wired: claudeWired,
9462
+ mode: detected.claude ? "hooks" : null
9463
+ },
9464
+ {
9465
+ name: "gemini",
9466
+ label: "Gemini CLI",
9467
+ installed: detected.gemini,
9468
+ wired: geminiWired,
9469
+ mode: detected.gemini ? "hooks" : null
9470
+ },
9471
+ {
9472
+ name: "cursor",
9473
+ label: "Cursor",
9474
+ installed: detected.cursor,
9475
+ wired: cursorWired,
9476
+ mode: detected.cursor ? "mcp" : null
9477
+ },
9478
+ {
9479
+ name: "windsurf",
9480
+ label: "Windsurf",
9481
+ installed: detected.windsurf,
9482
+ wired: windsurfWired,
9483
+ mode: detected.windsurf ? "mcp" : null
9484
+ },
9485
+ {
9486
+ name: "vscode",
9487
+ label: "VSCode",
9488
+ installed: detected.vscode,
9489
+ wired: vscodeWired,
9490
+ mode: detected.vscode ? "mcp" : null
9491
+ },
9492
+ {
9493
+ name: "codex",
9494
+ label: "Codex",
9495
+ installed: detected.codex,
9496
+ wired: codexWired,
9497
+ mode: detected.codex ? "mcp" : null
9498
+ }
9499
+ ];
9500
+ }
9501
+
9502
+ // src/cli.ts
9503
+ init_daemon2();
9504
+ var import_chalk24 = __toESM(require("chalk"));
9505
+ var import_fs35 = __toESM(require("fs"));
9506
+ var import_path38 = __toESM(require("path"));
9507
+ var import_os31 = __toESM(require("os"));
9508
+ var import_prompts2 = require("@inquirer/prompts");
9509
+
9510
+ // src/utils/duration.ts
9511
+ function parseDuration(str) {
9512
+ const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
9513
+ if (!m) return null;
9514
+ const n = parseFloat(m[1]);
9515
+ switch ((m[2] ?? "m").toLowerCase()) {
9516
+ case "s":
9517
+ return Math.round(n * 1e3);
9518
+ case "m":
9519
+ return Math.round(n * 6e4);
9520
+ case "h":
9521
+ return Math.round(n * 36e5);
9522
+ case "d":
9523
+ return Math.round(n * 864e5);
9524
+ default:
9525
+ return null;
9526
+ }
9527
+ }
9528
+
9529
+ // src/proxy/index.ts
9530
+ var import_readline = __toESM(require("readline"));
9531
+ var import_chalk4 = __toESM(require("chalk"));
9532
+ var import_child_process7 = require("child_process");
9533
+ var import_execa = require("execa");
9534
+ var import_execa2 = require("execa");
9535
+ init_orchestrator();
9536
+
9537
+ // src/policy/negotiation.ts
9538
+ function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand) {
9539
+ if (isHumanDecision) {
9540
+ return `NODE9: The human user rejected this action.
9541
+ REASON: ${humanReason || "No specific reason provided."}
9542
+ INSTRUCTIONS:
9543
+ - Do NOT retry this exact command.
9544
+ - Acknowledge the block to the user and ask if there is an alternative approach.
9545
+ - If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
9546
+ }
9547
+ const label = blockedByLabel.toLowerCase();
9548
+ if (label.includes("dlp") || label.includes("secret detected") || label.includes("credential review")) {
8788
9549
  return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
8789
9550
  CRITICAL INSTRUCTION: Do NOT retry this action.
8790
9551
  REQUIRED ACTIONS:
@@ -8856,11 +9617,11 @@ async function runProxy(targetCommand) {
8856
9617
  }
8857
9618
  console.error(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
8858
9619
  const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
8859
- const child = useShell ? (0, import_child_process6.spawn)("/bin/bash", ["-c", targetCommand], {
9620
+ const child = useShell ? (0, import_child_process7.spawn)("/bin/bash", ["-c", targetCommand], {
8860
9621
  stdio: ["pipe", "pipe", "inherit"],
8861
9622
  shell: false,
8862
9623
  env: spawnEnv
8863
- }) : (0, import_child_process6.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
9624
+ }) : (0, import_child_process7.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
8864
9625
  const agentIn = import_readline.default.createInterface({ input: process.stdin, terminal: false });
8865
9626
  agentIn.on("line", async (line) => {
8866
9627
  let message;
@@ -8924,22 +9685,22 @@ async function runProxy(targetCommand) {
8924
9685
  }
8925
9686
 
8926
9687
  // src/cli/daemon-starter.ts
8927
- var import_child_process7 = require("child_process");
9688
+ var import_child_process8 = require("child_process");
8928
9689
  init_daemon();
8929
9690
  function openBrowserLocal() {
8930
9691
  const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
8931
9692
  try {
8932
9693
  const opts = { stdio: "ignore" };
8933
- if (process.platform === "darwin") (0, import_child_process7.execSync)(`open "${url}"`, opts);
8934
- else if (process.platform === "win32") (0, import_child_process7.execSync)(`cmd /c start "" "${url}"`, opts);
8935
- else (0, import_child_process7.execSync)(`xdg-open "${url}"`, opts);
9694
+ if (process.platform === "darwin") (0, import_child_process8.execSync)(`open "${url}"`, opts);
9695
+ else if (process.platform === "win32") (0, import_child_process8.execSync)(`cmd /c start "" "${url}"`, opts);
9696
+ else (0, import_child_process8.execSync)(`xdg-open "${url}"`, opts);
8936
9697
  } catch {
8937
9698
  }
8938
9699
  }
8939
9700
  async function autoStartDaemonAndWait() {
8940
9701
  if (process.env.NODE9_TESTING === "1") return false;
8941
9702
  try {
8942
- const child = (0, import_child_process7.spawn)(process.execPath, [process.argv[1], "daemon"], {
9703
+ const child = (0, import_child_process8.spawn)(process.execPath, [process.argv[1], "daemon"], {
8943
9704
  detached: true,
8944
9705
  stdio: "ignore",
8945
9706
  // NODE9_BROWSER_OPENED=1 tells the daemon we will open the browser ourselves
@@ -8968,23 +9729,23 @@ async function autoStartDaemonAndWait() {
8968
9729
 
8969
9730
  // src/cli/commands/check.ts
8970
9731
  var import_chalk5 = __toESM(require("chalk"));
8971
- var import_fs20 = __toESM(require("fs"));
8972
- var import_child_process9 = require("child_process");
8973
- var import_path22 = __toESM(require("path"));
8974
- var import_os16 = __toESM(require("os"));
9732
+ var import_fs22 = __toESM(require("fs"));
9733
+ var import_child_process10 = require("child_process");
9734
+ var import_path24 = __toESM(require("path"));
9735
+ var import_os18 = __toESM(require("os"));
8975
9736
  init_orchestrator();
8976
9737
  init_daemon();
8977
9738
  init_config();
8978
9739
  init_policy();
8979
9740
 
8980
9741
  // src/undo.ts
8981
- var import_child_process8 = require("child_process");
9742
+ var import_child_process9 = require("child_process");
8982
9743
  var import_crypto8 = __toESM(require("crypto"));
8983
- var import_fs19 = __toESM(require("fs"));
9744
+ var import_fs21 = __toESM(require("fs"));
8984
9745
  var import_net3 = __toESM(require("net"));
8985
- var import_path21 = __toESM(require("path"));
8986
- var import_os15 = __toESM(require("os"));
8987
- var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path21.default.join(import_os15.default.tmpdir(), "node9-activity.sock");
9746
+ var import_path23 = __toESM(require("path"));
9747
+ var import_os17 = __toESM(require("os"));
9748
+ var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path23.default.join(import_os17.default.tmpdir(), "node9-activity.sock");
8988
9749
  function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
8989
9750
  try {
8990
9751
  const payload = JSON.stringify({
@@ -9004,22 +9765,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
9004
9765
  } catch {
9005
9766
  }
9006
9767
  }
9007
- var SNAPSHOT_STACK_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots.json");
9008
- var UNDO_LATEST_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "undo_latest.txt");
9768
+ var SNAPSHOT_STACK_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots.json");
9769
+ var UNDO_LATEST_PATH = import_path23.default.join(import_os17.default.homedir(), ".node9", "undo_latest.txt");
9009
9770
  var MAX_SNAPSHOTS = 10;
9010
9771
  var GIT_TIMEOUT = 15e3;
9011
9772
  function readStack() {
9012
9773
  try {
9013
- if (import_fs19.default.existsSync(SNAPSHOT_STACK_PATH))
9014
- return JSON.parse(import_fs19.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
9774
+ if (import_fs21.default.existsSync(SNAPSHOT_STACK_PATH))
9775
+ return JSON.parse(import_fs21.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
9015
9776
  } catch {
9016
9777
  }
9017
9778
  return [];
9018
9779
  }
9019
9780
  function writeStack(stack) {
9020
- const dir = import_path21.default.dirname(SNAPSHOT_STACK_PATH);
9021
- if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
9022
- import_fs19.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
9781
+ const dir = import_path23.default.dirname(SNAPSHOT_STACK_PATH);
9782
+ if (!import_fs21.default.existsSync(dir)) import_fs21.default.mkdirSync(dir, { recursive: true });
9783
+ import_fs21.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
9023
9784
  }
9024
9785
  function extractFilePath(args) {
9025
9786
  if (!args || typeof args !== "object") return null;
@@ -9039,12 +9800,12 @@ function buildArgsSummary(tool, args) {
9039
9800
  return "";
9040
9801
  }
9041
9802
  function findProjectRoot(filePath) {
9042
- let dir = import_path21.default.dirname(filePath);
9803
+ let dir = import_path23.default.dirname(filePath);
9043
9804
  while (true) {
9044
- if (import_fs19.default.existsSync(import_path21.default.join(dir, ".git")) || import_fs19.default.existsSync(import_path21.default.join(dir, "package.json"))) {
9805
+ if (import_fs21.default.existsSync(import_path23.default.join(dir, ".git")) || import_fs21.default.existsSync(import_path23.default.join(dir, "package.json"))) {
9045
9806
  return dir;
9046
9807
  }
9047
- const parent = import_path21.default.dirname(dir);
9808
+ const parent = import_path23.default.dirname(dir);
9048
9809
  if (parent === dir) return process.cwd();
9049
9810
  dir = parent;
9050
9811
  }
@@ -9052,7 +9813,7 @@ function findProjectRoot(filePath) {
9052
9813
  function normalizeCwdForHash(cwd) {
9053
9814
  let normalized;
9054
9815
  try {
9055
- normalized = import_fs19.default.realpathSync(cwd);
9816
+ normalized = import_fs21.default.realpathSync(cwd);
9056
9817
  } catch {
9057
9818
  normalized = cwd;
9058
9819
  }
@@ -9062,16 +9823,16 @@ function normalizeCwdForHash(cwd) {
9062
9823
  }
9063
9824
  function getShadowRepoDir(cwd) {
9064
9825
  const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
9065
- return import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots", hash);
9826
+ return import_path23.default.join(import_os17.default.homedir(), ".node9", "snapshots", hash);
9066
9827
  }
9067
9828
  function cleanOrphanedIndexFiles(shadowDir) {
9068
9829
  try {
9069
9830
  const cutoff = Date.now() - 6e4;
9070
- for (const f of import_fs19.default.readdirSync(shadowDir)) {
9831
+ for (const f of import_fs21.default.readdirSync(shadowDir)) {
9071
9832
  if (f.startsWith("index_")) {
9072
- const fp = import_path21.default.join(shadowDir, f);
9833
+ const fp = import_path23.default.join(shadowDir, f);
9073
9834
  try {
9074
- if (import_fs19.default.statSync(fp).mtimeMs < cutoff) import_fs19.default.unlinkSync(fp);
9835
+ if (import_fs21.default.statSync(fp).mtimeMs < cutoff) import_fs21.default.unlinkSync(fp);
9075
9836
  } catch {
9076
9837
  }
9077
9838
  }
@@ -9083,7 +9844,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
9083
9844
  const hardcoded = [".git", ".node9"];
9084
9845
  const lines = [...hardcoded, ...ignorePaths].join("\n");
9085
9846
  try {
9086
- import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9847
+ import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
9087
9848
  } catch {
9088
9849
  }
9089
9850
  }
@@ -9091,54 +9852,54 @@ function ensureShadowRepo(shadowDir, cwd) {
9091
9852
  cleanOrphanedIndexFiles(shadowDir);
9092
9853
  const normalizedCwd = normalizeCwdForHash(cwd);
9093
9854
  const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
9094
- const check = (0, import_child_process8.spawnSync)("git", ["rev-parse", "--git-dir"], {
9855
+ const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
9095
9856
  env: shadowEnvBase,
9096
9857
  timeout: 3e3
9097
9858
  });
9098
9859
  if (check.status === 0) {
9099
- const ptPath = import_path21.default.join(shadowDir, "project-path.txt");
9860
+ const ptPath = import_path23.default.join(shadowDir, "project-path.txt");
9100
9861
  try {
9101
- const stored = import_fs19.default.readFileSync(ptPath, "utf8").trim();
9862
+ const stored = import_fs21.default.readFileSync(ptPath, "utf8").trim();
9102
9863
  if (stored === normalizedCwd) return true;
9103
9864
  if (process.env.NODE9_DEBUG === "1")
9104
9865
  console.error(
9105
9866
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
9106
9867
  );
9107
- import_fs19.default.rmSync(shadowDir, { recursive: true, force: true });
9868
+ import_fs21.default.rmSync(shadowDir, { recursive: true, force: true });
9108
9869
  } catch {
9109
9870
  try {
9110
- import_fs19.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9871
+ import_fs21.default.writeFileSync(ptPath, normalizedCwd, "utf8");
9111
9872
  } catch {
9112
9873
  }
9113
9874
  return true;
9114
9875
  }
9115
9876
  }
9116
9877
  try {
9117
- import_fs19.default.mkdirSync(shadowDir, { recursive: true });
9878
+ import_fs21.default.mkdirSync(shadowDir, { recursive: true });
9118
9879
  } catch {
9119
9880
  }
9120
- const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
9881
+ const init = (0, import_child_process9.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
9121
9882
  if (init.status !== 0 || init.error) {
9122
9883
  const reason = init.error ? init.error.message : init.stderr?.toString();
9123
9884
  if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
9124
9885
  return false;
9125
9886
  }
9126
- const configFile = import_path21.default.join(shadowDir, "config");
9127
- (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
9887
+ const configFile = import_path23.default.join(shadowDir, "config");
9888
+ (0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
9128
9889
  timeout: 3e3
9129
9890
  });
9130
- (0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
9891
+ (0, import_child_process9.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
9131
9892
  timeout: 3e3
9132
9893
  });
9133
9894
  try {
9134
- import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9895
+ import_fs21.default.writeFileSync(import_path23.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
9135
9896
  } catch {
9136
9897
  }
9137
9898
  return true;
9138
9899
  }
9139
9900
  function buildGitEnv(cwd) {
9140
9901
  const shadowDir = getShadowRepoDir(cwd);
9141
- const check = (0, import_child_process8.spawnSync)("git", ["rev-parse", "--git-dir"], {
9902
+ const check = (0, import_child_process9.spawnSync)("git", ["rev-parse", "--git-dir"], {
9142
9903
  env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
9143
9904
  timeout: 2e3
9144
9905
  });
@@ -9151,23 +9912,23 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9151
9912
  let indexFile = null;
9152
9913
  try {
9153
9914
  const rawFilePath = extractFilePath(args);
9154
- const absFilePath = rawFilePath && import_path21.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9915
+ const absFilePath = rawFilePath && import_path23.default.isAbsolute(rawFilePath) ? rawFilePath : null;
9155
9916
  const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
9156
9917
  const shadowDir = getShadowRepoDir(cwd);
9157
9918
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
9158
9919
  writeShadowExcludes(shadowDir, ignorePaths);
9159
- indexFile = import_path21.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9920
+ indexFile = import_path23.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
9160
9921
  const shadowEnv = {
9161
9922
  ...process.env,
9162
9923
  GIT_DIR: shadowDir,
9163
9924
  GIT_WORK_TREE: cwd,
9164
9925
  GIT_INDEX_FILE: indexFile
9165
9926
  };
9166
- (0, import_child_process8.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9167
- const treeRes = (0, import_child_process8.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9927
+ (0, import_child_process9.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9928
+ const treeRes = (0, import_child_process9.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
9168
9929
  const treeHash = treeRes.stdout?.toString().trim();
9169
9930
  if (!treeHash || treeRes.status !== 0) return null;
9170
- const commitRes = (0, import_child_process8.spawnSync)(
9931
+ const commitRes = (0, import_child_process9.spawnSync)(
9171
9932
  "git",
9172
9933
  ["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
9173
9934
  { env: shadowEnv, timeout: GIT_TIMEOUT }
@@ -9179,7 +9940,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9179
9940
  let capturedFiles = [];
9180
9941
  let capturedDiff = null;
9181
9942
  if (prevEntry) {
9182
- const filesRes = (0, import_child_process8.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
9943
+ const filesRes = (0, import_child_process9.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
9183
9944
  env: shadowEnv,
9184
9945
  timeout: GIT_TIMEOUT
9185
9946
  });
@@ -9189,7 +9950,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9189
9950
  if (capturedFiles.length === 0) {
9190
9951
  return prevEntry.hash;
9191
9952
  }
9192
- const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
9953
+ const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
9193
9954
  env: shadowEnv,
9194
9955
  timeout: GIT_TIMEOUT
9195
9956
  });
@@ -9197,7 +9958,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9197
9958
  capturedDiff = diffRes.stdout?.toString() || null;
9198
9959
  }
9199
9960
  } else {
9200
- const filesRes = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
9961
+ const filesRes = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
9201
9962
  env: shadowEnv,
9202
9963
  timeout: GIT_TIMEOUT
9203
9964
  });
@@ -9228,9 +9989,9 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9228
9989
  writeStack(stack);
9229
9990
  const entry = stack[stack.length - 1];
9230
9991
  notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
9231
- import_fs19.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9992
+ import_fs21.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
9232
9993
  if (shouldGc) {
9233
- (0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
9994
+ (0, import_child_process9.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
9234
9995
  }
9235
9996
  return commitHash;
9236
9997
  } catch (err2) {
@@ -9239,7 +10000,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
9239
10000
  } finally {
9240
10001
  if (indexFile) {
9241
10002
  try {
9242
- import_fs19.default.unlinkSync(indexFile);
10003
+ import_fs21.default.unlinkSync(indexFile);
9243
10004
  } catch {
9244
10005
  }
9245
10006
  }
@@ -9251,14 +10012,14 @@ function getSnapshotHistory() {
9251
10012
  function computeUndoDiff(hash, cwd) {
9252
10013
  try {
9253
10014
  const env = buildGitEnv(cwd);
9254
- const statRes = (0, import_child_process8.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
10015
+ const statRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
9255
10016
  cwd,
9256
10017
  env,
9257
10018
  timeout: GIT_TIMEOUT
9258
10019
  });
9259
10020
  const stat = statRes.stdout?.toString().trim();
9260
10021
  if (!stat || statRes.status !== 0) return null;
9261
- const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", hash, "--", "."], {
10022
+ const diffRes = (0, import_child_process9.spawnSync)("git", ["diff", hash, "--", "."], {
9262
10023
  cwd,
9263
10024
  env,
9264
10025
  timeout: GIT_TIMEOUT
@@ -9277,7 +10038,7 @@ function applyUndo(hash, cwd) {
9277
10038
  try {
9278
10039
  const dir = cwd ?? process.cwd();
9279
10040
  const env = buildGitEnv(dir);
9280
- const restore = (0, import_child_process8.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
10041
+ const restore = (0, import_child_process9.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
9281
10042
  cwd: dir,
9282
10043
  env,
9283
10044
  timeout: GIT_TIMEOUT
@@ -9289,7 +10050,7 @@ function applyUndo(hash, cwd) {
9289
10050
  }
9290
10051
  return false;
9291
10052
  }
9292
- const lsTree = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
10053
+ const lsTree = (0, import_child_process9.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
9293
10054
  cwd: dir,
9294
10055
  env,
9295
10056
  timeout: GIT_TIMEOUT
@@ -9308,16 +10069,16 @@ function applyUndo(hash, cwd) {
9308
10069
  `);
9309
10070
  return false;
9310
10071
  }
9311
- const tracked = (0, import_child_process8.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9312
- const untracked = (0, import_child_process8.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
10072
+ const tracked = (0, import_child_process9.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
10073
+ const untracked = (0, import_child_process9.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
9313
10074
  cwd: dir,
9314
10075
  env,
9315
10076
  timeout: GIT_TIMEOUT
9316
10077
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
9317
10078
  for (const file of [...tracked, ...untracked]) {
9318
- const fullPath = import_path21.default.join(dir, file);
9319
- if (!snapshotFiles.has(file) && import_fs19.default.existsSync(fullPath)) {
9320
- import_fs19.default.unlinkSync(fullPath);
10079
+ const fullPath = import_path23.default.join(dir, file);
10080
+ if (!snapshotFiles.has(file) && import_fs21.default.existsSync(fullPath)) {
10081
+ import_fs21.default.unlinkSync(fullPath);
9321
10082
  }
9322
10083
  }
9323
10084
  return true;
@@ -9341,9 +10102,9 @@ function registerCheckCommand(program2) {
9341
10102
  } catch (err2) {
9342
10103
  const tempConfig = getConfig();
9343
10104
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
9344
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
10105
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
9345
10106
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9346
- import_fs20.default.appendFileSync(
10107
+ import_fs22.default.appendFileSync(
9347
10108
  logPath,
9348
10109
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
9349
10110
  RAW: ${raw}
@@ -9356,11 +10117,11 @@ RAW: ${raw}
9356
10117
  if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
9357
10118
  try {
9358
10119
  const scriptPath = process.argv[1];
9359
- if (typeof scriptPath !== "string" || !import_path22.default.isAbsolute(scriptPath))
10120
+ if (typeof scriptPath !== "string" || !import_path24.default.isAbsolute(scriptPath))
9360
10121
  throw new Error("node9: argv[1] is not an absolute path");
9361
- const resolvedScript = import_fs20.default.realpathSync(scriptPath);
9362
- const packageDist = import_fs20.default.realpathSync(import_path22.default.resolve(__dirname, "../.."));
9363
- if (!resolvedScript.startsWith(packageDist + import_path22.default.sep) && resolvedScript !== packageDist)
10122
+ const resolvedScript = import_fs22.default.realpathSync(scriptPath);
10123
+ const packageDist = import_fs22.default.realpathSync(import_path24.default.resolve(__dirname, "../.."));
10124
+ if (!resolvedScript.startsWith(packageDist + import_path24.default.sep) && resolvedScript !== packageDist)
9364
10125
  throw new Error(
9365
10126
  `node9: daemon spawn aborted \u2014 argv[1] (${resolvedScript}) is outside package dist (${packageDist})`
9366
10127
  );
@@ -9375,17 +10136,17 @@ RAW: ${raw}
9375
10136
  ]) {
9376
10137
  delete safeEnv[key];
9377
10138
  }
9378
- const d = (0, import_child_process9.spawn)(process.execPath, [scriptPath, "daemon"], {
10139
+ const d = (0, import_child_process10.spawn)(process.execPath, [scriptPath, "daemon"], {
9379
10140
  detached: true,
9380
10141
  stdio: "ignore",
9381
10142
  env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
9382
10143
  });
9383
10144
  d.unref();
9384
10145
  } catch (spawnErr) {
9385
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
10146
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
9386
10147
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
9387
10148
  try {
9388
- import_fs20.default.appendFileSync(
10149
+ import_fs22.default.appendFileSync(
9389
10150
  logPath,
9390
10151
  `[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-failed: ${msg}
9391
10152
  `
@@ -9395,10 +10156,10 @@ RAW: ${raw}
9395
10156
  }
9396
10157
  }
9397
10158
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
9398
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
9399
- if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
9400
- import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
9401
- import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
10159
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
10160
+ if (!import_fs22.default.existsSync(import_path24.default.dirname(logPath)))
10161
+ import_fs22.default.mkdirSync(import_path24.default.dirname(logPath), { recursive: true });
10162
+ import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
9402
10163
  `);
9403
10164
  }
9404
10165
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -9411,8 +10172,8 @@ RAW: ${raw}
9411
10172
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
9412
10173
  let ttyFd = null;
9413
10174
  try {
9414
- ttyFd = import_fs20.default.openSync("/dev/tty", "w");
9415
- const writeTty = (line) => import_fs20.default.writeSync(ttyFd, line + "\n");
10175
+ ttyFd = import_fs22.default.openSync("/dev/tty", "w");
10176
+ const writeTty = (line) => import_fs22.default.writeSync(ttyFd, line + "\n");
9416
10177
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
9417
10178
  writeTty(import_chalk5.default.bgRed.white.bold(`
9418
10179
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -9431,7 +10192,7 @@ RAW: ${raw}
9431
10192
  } finally {
9432
10193
  if (ttyFd !== null)
9433
10194
  try {
9434
- import_fs20.default.closeSync(ttyFd);
10195
+ import_fs22.default.closeSync(ttyFd);
9435
10196
  } catch {
9436
10197
  }
9437
10198
  }
@@ -9463,7 +10224,7 @@ RAW: ${raw}
9463
10224
  if (shouldSnapshot(toolName, toolInput, config)) {
9464
10225
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
9465
10226
  }
9466
- const safeCwdForAuth = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10227
+ const safeCwdForAuth = typeof payload.cwd === "string" && import_path24.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9467
10228
  const result = await authorizeHeadless(toolName, toolInput, meta, {
9468
10229
  cwd: safeCwdForAuth
9469
10230
  });
@@ -9475,12 +10236,12 @@ RAW: ${raw}
9475
10236
  }
9476
10237
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
9477
10238
  try {
9478
- const tty = import_fs20.default.openSync("/dev/tty", "w");
9479
- import_fs20.default.writeSync(
10239
+ const tty = import_fs22.default.openSync("/dev/tty", "w");
10240
+ import_fs22.default.writeSync(
9480
10241
  tty,
9481
10242
  import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
9482
10243
  );
9483
- import_fs20.default.closeSync(tty);
10244
+ import_fs22.default.closeSync(tty);
9484
10245
  } catch {
9485
10246
  }
9486
10247
  const daemonReady = await autoStartDaemonAndWait();
@@ -9507,9 +10268,9 @@ RAW: ${raw}
9507
10268
  });
9508
10269
  } catch (err2) {
9509
10270
  if (process.env.NODE9_DEBUG === "1") {
9510
- const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
10271
+ const logPath = import_path24.default.join(import_os18.default.homedir(), ".node9", "hook-debug.log");
9511
10272
  const errMsg = err2 instanceof Error ? err2.message : String(err2);
9512
- import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
10273
+ import_fs22.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
9513
10274
  `);
9514
10275
  }
9515
10276
  process.exit(0);
@@ -9543,9 +10304,9 @@ RAW: ${raw}
9543
10304
  }
9544
10305
 
9545
10306
  // src/cli/commands/log.ts
9546
- var import_fs21 = __toESM(require("fs"));
9547
- var import_path23 = __toESM(require("path"));
9548
- var import_os17 = __toESM(require("os"));
10307
+ var import_fs23 = __toESM(require("fs"));
10308
+ var import_path25 = __toESM(require("path"));
10309
+ var import_os19 = __toESM(require("os"));
9549
10310
  init_audit();
9550
10311
  init_config();
9551
10312
  init_policy();
@@ -9621,10 +10382,10 @@ function registerLogCommand(program2) {
9621
10382
  decision: "allowed",
9622
10383
  source: "post-hook"
9623
10384
  };
9624
- const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
9625
- if (!import_fs21.default.existsSync(import_path23.default.dirname(logPath)))
9626
- import_fs21.default.mkdirSync(import_path23.default.dirname(logPath), { recursive: true });
9627
- import_fs21.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
10385
+ const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10386
+ if (!import_fs23.default.existsSync(import_path25.default.dirname(logPath)))
10387
+ import_fs23.default.mkdirSync(import_path25.default.dirname(logPath), { recursive: true });
10388
+ import_fs23.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
9628
10389
  if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
9629
10390
  const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
9630
10391
  if (command) {
@@ -9657,7 +10418,7 @@ function registerLogCommand(program2) {
9657
10418
  }
9658
10419
  }
9659
10420
  }
9660
- const safeCwd = typeof payload.cwd === "string" && import_path23.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
10421
+ const safeCwd = typeof payload.cwd === "string" && import_path25.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
9661
10422
  const config = getConfig(safeCwd);
9662
10423
  if (shouldSnapshot(tool, {}, config)) {
9663
10424
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -9666,9 +10427,9 @@ function registerLogCommand(program2) {
9666
10427
  const msg = err2 instanceof Error ? err2.message : String(err2);
9667
10428
  process.stderr.write(`[Node9] audit log error: ${msg}
9668
10429
  `);
9669
- const debugPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "hook-debug.log");
10430
+ const debugPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "hook-debug.log");
9670
10431
  try {
9671
- import_fs21.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
10432
+ import_fs23.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
9672
10433
  `);
9673
10434
  } catch {
9674
10435
  }
@@ -9698,10 +10459,10 @@ init_audit();
9698
10459
  init_config();
9699
10460
 
9700
10461
  // src/utils/https-fetch.ts
9701
- var import_https = __toESM(require("https"));
10462
+ var import_https2 = __toESM(require("https"));
9702
10463
  function httpsFetch(url) {
9703
10464
  return new Promise((resolve, reject) => {
9704
- import_https.default.get(url, (res) => {
10465
+ import_https2.default.get(url, (res) => {
9705
10466
  if (res.statusCode !== 200) {
9706
10467
  reject(new Error(`HTTP ${String(res.statusCode)} for ${url}`));
9707
10468
  res.resume();
@@ -10068,14 +10829,14 @@ function registerConfigShowCommand(program2) {
10068
10829
 
10069
10830
  // src/cli/commands/doctor.ts
10070
10831
  var import_chalk7 = __toESM(require("chalk"));
10071
- var import_fs22 = __toESM(require("fs"));
10072
- var import_path24 = __toESM(require("path"));
10073
- var import_os18 = __toESM(require("os"));
10074
- var import_child_process10 = require("child_process");
10832
+ var import_fs24 = __toESM(require("fs"));
10833
+ var import_path26 = __toESM(require("path"));
10834
+ var import_os20 = __toESM(require("os"));
10835
+ var import_child_process11 = require("child_process");
10075
10836
  init_daemon();
10076
10837
  function registerDoctorCommand(program2, version2) {
10077
10838
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
10078
- const homeDir2 = import_os18.default.homedir();
10839
+ const homeDir2 = import_os20.default.homedir();
10079
10840
  let failures = 0;
10080
10841
  function pass(msg) {
10081
10842
  console.log(import_chalk7.default.green(" \u2705 ") + msg);
@@ -10097,7 +10858,7 @@ function registerDoctorCommand(program2, version2) {
10097
10858
  `));
10098
10859
  section("Binary");
10099
10860
  try {
10100
- const which = (0, import_child_process10.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
10861
+ const which = (0, import_child_process11.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
10101
10862
  pass(`node9 found at ${which}`);
10102
10863
  } catch {
10103
10864
  warn(
@@ -10115,7 +10876,7 @@ function registerDoctorCommand(program2, version2) {
10115
10876
  );
10116
10877
  }
10117
10878
  try {
10118
- const gitVersion = (0, import_child_process10.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
10879
+ const gitVersion = (0, import_child_process11.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
10119
10880
  pass(gitVersion);
10120
10881
  } catch {
10121
10882
  warn(
@@ -10124,10 +10885,10 @@ function registerDoctorCommand(program2, version2) {
10124
10885
  );
10125
10886
  }
10126
10887
  section("Configuration");
10127
- const globalConfigPath = import_path24.default.join(homeDir2, ".node9", "config.json");
10128
- if (import_fs22.default.existsSync(globalConfigPath)) {
10888
+ const globalConfigPath = import_path26.default.join(homeDir2, ".node9", "config.json");
10889
+ if (import_fs24.default.existsSync(globalConfigPath)) {
10129
10890
  try {
10130
- JSON.parse(import_fs22.default.readFileSync(globalConfigPath, "utf-8"));
10891
+ JSON.parse(import_fs24.default.readFileSync(globalConfigPath, "utf-8"));
10131
10892
  pass("~/.node9/config.json found and valid");
10132
10893
  } catch {
10133
10894
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -10135,10 +10896,10 @@ function registerDoctorCommand(program2, version2) {
10135
10896
  } else {
10136
10897
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
10137
10898
  }
10138
- const projectConfigPath = import_path24.default.join(process.cwd(), "node9.config.json");
10139
- if (import_fs22.default.existsSync(projectConfigPath)) {
10899
+ const projectConfigPath = import_path26.default.join(process.cwd(), "node9.config.json");
10900
+ if (import_fs24.default.existsSync(projectConfigPath)) {
10140
10901
  try {
10141
- JSON.parse(import_fs22.default.readFileSync(projectConfigPath, "utf-8"));
10902
+ JSON.parse(import_fs24.default.readFileSync(projectConfigPath, "utf-8"));
10142
10903
  pass("node9.config.json found and valid (project)");
10143
10904
  } catch {
10144
10905
  fail(
@@ -10147,8 +10908,8 @@ function registerDoctorCommand(program2, version2) {
10147
10908
  );
10148
10909
  }
10149
10910
  }
10150
- const credsPath = import_path24.default.join(homeDir2, ".node9", "credentials.json");
10151
- if (import_fs22.default.existsSync(credsPath)) {
10911
+ const credsPath = import_path26.default.join(homeDir2, ".node9", "credentials.json");
10912
+ if (import_fs24.default.existsSync(credsPath)) {
10152
10913
  pass("Cloud credentials found (~/.node9/credentials.json)");
10153
10914
  } else {
10154
10915
  warn(
@@ -10157,10 +10918,10 @@ function registerDoctorCommand(program2, version2) {
10157
10918
  );
10158
10919
  }
10159
10920
  section("Agent Hooks");
10160
- const claudeSettingsPath = import_path24.default.join(homeDir2, ".claude", "settings.json");
10161
- if (import_fs22.default.existsSync(claudeSettingsPath)) {
10921
+ const claudeSettingsPath = import_path26.default.join(homeDir2, ".claude", "settings.json");
10922
+ if (import_fs24.default.existsSync(claudeSettingsPath)) {
10162
10923
  try {
10163
- const cs = JSON.parse(import_fs22.default.readFileSync(claudeSettingsPath, "utf-8"));
10924
+ const cs = JSON.parse(import_fs24.default.readFileSync(claudeSettingsPath, "utf-8"));
10164
10925
  const hasHook = cs.hooks?.PreToolUse?.some(
10165
10926
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
10166
10927
  );
@@ -10176,10 +10937,10 @@ function registerDoctorCommand(program2, version2) {
10176
10937
  } else {
10177
10938
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
10178
10939
  }
10179
- const geminiSettingsPath = import_path24.default.join(homeDir2, ".gemini", "settings.json");
10180
- if (import_fs22.default.existsSync(geminiSettingsPath)) {
10940
+ const geminiSettingsPath = import_path26.default.join(homeDir2, ".gemini", "settings.json");
10941
+ if (import_fs24.default.existsSync(geminiSettingsPath)) {
10181
10942
  try {
10182
- const gs = JSON.parse(import_fs22.default.readFileSync(geminiSettingsPath, "utf-8"));
10943
+ const gs = JSON.parse(import_fs24.default.readFileSync(geminiSettingsPath, "utf-8"));
10183
10944
  const hasHook = gs.hooks?.BeforeTool?.some(
10184
10945
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
10185
10946
  );
@@ -10195,10 +10956,10 @@ function registerDoctorCommand(program2, version2) {
10195
10956
  } else {
10196
10957
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
10197
10958
  }
10198
- const cursorHooksPath = import_path24.default.join(homeDir2, ".cursor", "hooks.json");
10199
- if (import_fs22.default.existsSync(cursorHooksPath)) {
10959
+ const cursorHooksPath = import_path26.default.join(homeDir2, ".cursor", "hooks.json");
10960
+ if (import_fs24.default.existsSync(cursorHooksPath)) {
10200
10961
  try {
10201
- const cur = JSON.parse(import_fs22.default.readFileSync(cursorHooksPath, "utf-8"));
10962
+ const cur = JSON.parse(import_fs24.default.readFileSync(cursorHooksPath, "utf-8"));
10202
10963
  const hasHook = cur.hooks?.preToolUse?.some(
10203
10964
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
10204
10965
  );
@@ -10236,9 +10997,9 @@ function registerDoctorCommand(program2, version2) {
10236
10997
 
10237
10998
  // src/cli/commands/audit.ts
10238
10999
  var import_chalk8 = __toESM(require("chalk"));
10239
- var import_fs23 = __toESM(require("fs"));
10240
- var import_path25 = __toESM(require("path"));
10241
- var import_os19 = __toESM(require("os"));
11000
+ var import_fs25 = __toESM(require("fs"));
11001
+ var import_path27 = __toESM(require("path"));
11002
+ var import_os21 = __toESM(require("os"));
10242
11003
  function formatRelativeTime(timestamp) {
10243
11004
  const diff = Date.now() - new Date(timestamp).getTime();
10244
11005
  const sec = Math.floor(diff / 1e3);
@@ -10251,14 +11012,14 @@ function formatRelativeTime(timestamp) {
10251
11012
  }
10252
11013
  function registerAuditCommand(program2) {
10253
11014
  program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
10254
- const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
10255
- if (!import_fs23.default.existsSync(logPath)) {
11015
+ const logPath = import_path27.default.join(import_os21.default.homedir(), ".node9", "audit.log");
11016
+ if (!import_fs25.default.existsSync(logPath)) {
10256
11017
  console.log(
10257
11018
  import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
10258
11019
  );
10259
11020
  return;
10260
11021
  }
10261
- const raw = import_fs23.default.readFileSync(logPath, "utf-8");
11022
+ const raw = import_fs25.default.readFileSync(logPath, "utf-8");
10262
11023
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
10263
11024
  let entries = lines.flatMap((line) => {
10264
11025
  try {
@@ -10312,9 +11073,9 @@ function registerAuditCommand(program2) {
10312
11073
 
10313
11074
  // src/cli/commands/report.ts
10314
11075
  var import_chalk9 = __toESM(require("chalk"));
10315
- var import_fs24 = __toESM(require("fs"));
10316
- var import_path26 = __toESM(require("path"));
10317
- var import_os20 = __toESM(require("os"));
11076
+ var import_fs26 = __toESM(require("fs"));
11077
+ var import_path28 = __toESM(require("path"));
11078
+ var import_os22 = __toESM(require("os"));
10318
11079
  var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
10319
11080
  function buildTestTimestamps(allEntries) {
10320
11081
  const testTs = /* @__PURE__ */ new Set();
@@ -10361,8 +11122,8 @@ function getDateRange(period) {
10361
11122
  }
10362
11123
  }
10363
11124
  function parseAuditLog(logPath) {
10364
- if (!import_fs24.default.existsSync(logPath)) return [];
10365
- const raw = import_fs24.default.readFileSync(logPath, "utf-8");
11125
+ if (!import_fs26.default.existsSync(logPath)) return [];
11126
+ const raw = import_fs26.default.readFileSync(logPath, "utf-8");
10366
11127
  return raw.split("\n").flatMap((line) => {
10367
11128
  if (!line.trim()) return [];
10368
11129
  try {
@@ -10388,9 +11149,9 @@ function colorBar(value, max, width) {
10388
11149
  const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
10389
11150
  return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
10390
11151
  }
10391
- function pct(num2, total) {
11152
+ function pct(num3, total) {
10392
11153
  if (total === 0) return "\u2013";
10393
- return Math.round(num2 / total * 100) + "%";
11154
+ return Math.round(num3 / total * 100) + "%";
10394
11155
  }
10395
11156
  function fmtDate(d) {
10396
11157
  const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
@@ -10431,11 +11192,11 @@ function loadClaudeCost(start, end) {
10431
11192
  inputTokens: 0,
10432
11193
  cacheReadTokens: 0
10433
11194
  };
10434
- const projectsDir = import_path26.default.join(import_os20.default.homedir(), ".claude", "projects");
10435
- if (!import_fs24.default.existsSync(projectsDir)) return empty;
11195
+ const projectsDir = import_path28.default.join(import_os22.default.homedir(), ".claude", "projects");
11196
+ if (!import_fs26.default.existsSync(projectsDir)) return empty;
10436
11197
  let dirs;
10437
11198
  try {
10438
- dirs = import_fs24.default.readdirSync(projectsDir);
11199
+ dirs = import_fs26.default.readdirSync(projectsDir);
10439
11200
  } catch {
10440
11201
  return empty;
10441
11202
  }
@@ -10445,18 +11206,18 @@ function loadClaudeCost(start, end) {
10445
11206
  const byDay = /* @__PURE__ */ new Map();
10446
11207
  const byModel = /* @__PURE__ */ new Map();
10447
11208
  for (const proj of dirs) {
10448
- const projPath = import_path26.default.join(projectsDir, proj);
11209
+ const projPath = import_path28.default.join(projectsDir, proj);
10449
11210
  let files;
10450
11211
  try {
10451
- const stat = import_fs24.default.statSync(projPath);
11212
+ const stat = import_fs26.default.statSync(projPath);
10452
11213
  if (!stat.isDirectory()) continue;
10453
- files = import_fs24.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
11214
+ files = import_fs26.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
10454
11215
  } catch {
10455
11216
  continue;
10456
11217
  }
10457
11218
  for (const file of files) {
10458
11219
  try {
10459
- const raw = import_fs24.default.readFileSync(import_path26.default.join(projPath, file), "utf-8");
11220
+ const raw = import_fs26.default.readFileSync(import_path28.default.join(projPath, file), "utf-8");
10460
11221
  for (const line of raw.split("\n")) {
10461
11222
  if (!line.trim()) continue;
10462
11223
  let entry;
@@ -10499,7 +11260,7 @@ function registerReportCommand(program2) {
10499
11260
  const period = ["today", "7d", "30d", "month"].includes(
10500
11261
  options.period
10501
11262
  ) ? options.period : "7d";
10502
- const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
11263
+ const logPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "audit.log");
10503
11264
  const allEntries = parseAuditLog(logPath);
10504
11265
  if (allEntries.length === 0) {
10505
11266
  console.log(
@@ -10747,26 +11508,67 @@ function registerReportCommand(program2) {
10747
11508
 
10748
11509
  // src/cli/commands/daemon-cmd.ts
10749
11510
  var import_chalk10 = __toESM(require("chalk"));
10750
- var import_child_process11 = require("child_process");
11511
+ var import_child_process12 = require("child_process");
10751
11512
  init_daemon2();
10752
11513
  init_daemon();
11514
+ var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
10753
11515
  function registerDaemonCommand(program2) {
10754
- program2.command("daemon").description("Run the local approval server").argument("[action]", "start | stop | status (default: start)").option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
11516
+ program2.command("daemon").description("Manage the local approval daemon").argument("[action]", `${VALID_ACTIONS} (default: start)`).option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
10755
11517
  "-w, --watch",
10756
11518
  "Start daemon + open browser, stay alive permanently (Flight Recorder mode)"
10757
11519
  ).action(
10758
11520
  async (action, options) => {
10759
11521
  const cmd = (action ?? "start").toLowerCase();
10760
- if (cmd === "stop") return stopDaemon();
10761
- if (cmd === "status") return daemonStatus();
10762
- if (cmd !== "start" && action !== void 0) {
10763
- console.error(
10764
- import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
10765
- );
10766
- process.exit(1);
11522
+ if (cmd === "install") {
11523
+ const result = installDaemonService();
11524
+ if (!result.ok) {
11525
+ console.error(import_chalk10.default.red(`\u2717 ${result.reason}`));
11526
+ process.exit(1);
11527
+ }
11528
+ if (result.alreadyInstalled) {
11529
+ console.log(import_chalk10.default.green(`\u2713 Daemon service reinstalled (${result.platform})`));
11530
+ } else {
11531
+ console.log(import_chalk10.default.green(`\u2713 Daemon installed as login service (${result.platform})`));
11532
+ console.log(import_chalk10.default.gray(" The daemon will now start automatically on login."));
11533
+ }
11534
+ process.exit(0);
10767
11535
  }
10768
- if (options.watch) {
10769
- process.env.NODE9_WATCH_MODE = "1";
11536
+ if (cmd === "uninstall") {
11537
+ const result = uninstallDaemonService();
11538
+ if (!result.ok) {
11539
+ console.error(import_chalk10.default.red(`\u2717 ${result.reason}`));
11540
+ process.exit(1);
11541
+ }
11542
+ console.log(import_chalk10.default.green(`\u2713 Daemon service removed (${result.platform})`));
11543
+ console.log(import_chalk10.default.gray(" The daemon will no longer start automatically on login."));
11544
+ console.log(import_chalk10.default.gray(" To stop the running daemon: node9 daemon stop"));
11545
+ process.exit(0);
11546
+ }
11547
+ if (cmd === "stop") return stopDaemon();
11548
+ if (cmd === "restart") {
11549
+ stopDaemon();
11550
+ await new Promise((r) => setTimeout(r, 500));
11551
+ const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
11552
+ detached: true,
11553
+ stdio: "ignore",
11554
+ env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
11555
+ });
11556
+ child.unref();
11557
+ if (child.pid) {
11558
+ console.log(import_chalk10.default.green(`\u2713 Daemon restarted (PID ${child.pid})`));
11559
+ } else {
11560
+ console.error(import_chalk10.default.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
11561
+ process.exit(1);
11562
+ }
11563
+ process.exit(0);
11564
+ }
11565
+ if (cmd === "status") return daemonStatus();
11566
+ if (cmd !== "start" && action !== void 0) {
11567
+ console.error(import_chalk10.default.red(`Unknown daemon action: "${action}". Use: ${VALID_ACTIONS}`));
11568
+ process.exit(1);
11569
+ }
11570
+ if (options.watch) {
11571
+ process.env.NODE9_WATCH_MODE = "1";
10770
11572
  setTimeout(() => {
10771
11573
  openBrowserLocal();
10772
11574
  console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
@@ -10780,7 +11582,7 @@ function registerDaemonCommand(program2) {
10780
11582
  console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
10781
11583
  process.exit(0);
10782
11584
  }
10783
- const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
11585
+ const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10784
11586
  detached: true,
10785
11587
  stdio: "ignore"
10786
11588
  });
@@ -10795,7 +11597,7 @@ function registerDaemonCommand(program2) {
10795
11597
  process.exit(0);
10796
11598
  }
10797
11599
  if (options.background) {
10798
- const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
11600
+ const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
10799
11601
  detached: true,
10800
11602
  stdio: "ignore"
10801
11603
  });
@@ -10811,14 +11613,14 @@ function registerDaemonCommand(program2) {
10811
11613
 
10812
11614
  // src/cli/commands/status.ts
10813
11615
  var import_chalk11 = __toESM(require("chalk"));
10814
- var import_fs25 = __toESM(require("fs"));
10815
- var import_path27 = __toESM(require("path"));
10816
- var import_os21 = __toESM(require("os"));
11616
+ var import_fs27 = __toESM(require("fs"));
11617
+ var import_path29 = __toESM(require("path"));
11618
+ var import_os23 = __toESM(require("os"));
10817
11619
  init_core();
10818
11620
  init_daemon();
10819
11621
  function readJson2(filePath) {
10820
11622
  try {
10821
- if (import_fs25.default.existsSync(filePath)) return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
11623
+ if (import_fs27.default.existsSync(filePath)) return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
10822
11624
  } catch {
10823
11625
  }
10824
11626
  return null;
@@ -10883,28 +11685,28 @@ function registerStatusCommand(program2) {
10883
11685
  console.log("");
10884
11686
  const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
10885
11687
  console.log(` Mode: ${modeLabel}`);
10886
- const projectConfig = import_path27.default.join(process.cwd(), "node9.config.json");
10887
- const globalConfig = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
11688
+ const projectConfig = import_path29.default.join(process.cwd(), "node9.config.json");
11689
+ const globalConfig = import_path29.default.join(import_os23.default.homedir(), ".node9", "config.json");
10888
11690
  console.log(
10889
- ` Local: ${import_fs25.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
11691
+ ` Local: ${import_fs27.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
10890
11692
  );
10891
11693
  console.log(
10892
- ` Global: ${import_fs25.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
11694
+ ` Global: ${import_fs27.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
10893
11695
  );
10894
11696
  if (mergedConfig.policy.sandboxPaths.length > 0) {
10895
11697
  console.log(
10896
11698
  ` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
10897
11699
  );
10898
11700
  }
10899
- const homeDir2 = import_os21.default.homedir();
11701
+ const homeDir2 = import_os23.default.homedir();
10900
11702
  const claudeSettings = readJson2(
10901
- import_path27.default.join(homeDir2, ".claude", "settings.json")
11703
+ import_path29.default.join(homeDir2, ".claude", "settings.json")
10902
11704
  );
10903
- const claudeConfig = readJson2(import_path27.default.join(homeDir2, ".claude.json"));
11705
+ const claudeConfig = readJson2(import_path29.default.join(homeDir2, ".claude.json"));
10904
11706
  const geminiSettings = readJson2(
10905
- import_path27.default.join(homeDir2, ".gemini", "settings.json")
11707
+ import_path29.default.join(homeDir2, ".gemini", "settings.json")
10906
11708
  );
10907
- const cursorConfig = readJson2(import_path27.default.join(homeDir2, ".cursor", "mcp.json"));
11709
+ const cursorConfig = readJson2(import_path29.default.join(homeDir2, ".cursor", "mcp.json"));
10908
11710
  const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
10909
11711
  if (agentFound) {
10910
11712
  console.log("");
@@ -10963,12 +11765,13 @@ function registerStatusCommand(program2) {
10963
11765
 
10964
11766
  // src/cli/commands/init.ts
10965
11767
  var import_chalk12 = __toESM(require("chalk"));
10966
- var import_fs26 = __toESM(require("fs"));
10967
- var import_path28 = __toESM(require("path"));
10968
- var import_os22 = __toESM(require("os"));
10969
- var import_https2 = __toESM(require("https"));
11768
+ var import_fs28 = __toESM(require("fs"));
11769
+ var import_path30 = __toESM(require("path"));
11770
+ var import_os24 = __toESM(require("os"));
11771
+ var import_https3 = __toESM(require("https"));
10970
11772
  init_core();
10971
11773
  init_shields();
11774
+ init_service();
10972
11775
  var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
10973
11776
  function fireTelemetryPing(agents) {
10974
11777
  try {
@@ -10978,7 +11781,7 @@ function fireTelemetryPing(agents) {
10978
11781
  os: process.platform,
10979
11782
  node9_version: process.env.npm_package_version ?? "unknown"
10980
11783
  });
10981
- const req = import_https2.default.request(
11784
+ const req = import_https3.default.request(
10982
11785
  {
10983
11786
  hostname: "api.node9.ai",
10984
11787
  path: "/api/v1/telemetry",
@@ -11025,15 +11828,15 @@ function registerInitCommand(program2) {
11025
11828
  }
11026
11829
  console.log("");
11027
11830
  }
11028
- const configPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "config.json");
11029
- if (import_fs26.default.existsSync(configPath) && !options.force) {
11831
+ const configPath = import_path30.default.join(import_os24.default.homedir(), ".node9", "config.json");
11832
+ if (import_fs28.default.existsSync(configPath) && !options.force) {
11030
11833
  try {
11031
- const existing = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
11834
+ const existing = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
11032
11835
  const settings = existing.settings ?? {};
11033
11836
  if (settings.mode !== chosenMode) {
11034
11837
  settings.mode = chosenMode;
11035
11838
  existing.settings = settings;
11036
- import_fs26.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11839
+ import_fs28.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
11037
11840
  console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
11038
11841
  } else {
11039
11842
  console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
@@ -11046,9 +11849,9 @@ function registerInitCommand(program2) {
11046
11849
  ...DEFAULT_CONFIG,
11047
11850
  settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
11048
11851
  };
11049
- const dir = import_path28.default.dirname(configPath);
11050
- if (!import_fs26.default.existsSync(dir)) import_fs26.default.mkdirSync(dir, { recursive: true });
11051
- import_fs26.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11852
+ const dir = import_path30.default.dirname(configPath);
11853
+ if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
11854
+ import_fs28.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
11052
11855
  console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
11053
11856
  console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
11054
11857
  }
@@ -11060,9 +11863,13 @@ function registerInitCommand(program2) {
11060
11863
  );
11061
11864
  if (found.length === 0) {
11062
11865
  console.log(
11063
- import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
11866
+ import_chalk12.default.gray(
11867
+ "No AI agents detected. Install Claude Code, Gemini CLI, Cursor, Windsurf, VSCode, or Codex"
11868
+ )
11869
+ );
11870
+ console.log(
11871
+ import_chalk12.default.gray("then run: node9 agents add <claude|gemini|cursor|windsurf|vscode|codex>")
11064
11872
  );
11065
- console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
11066
11873
  return;
11067
11874
  }
11068
11875
  console.log(import_chalk12.default.bold("Detected agents:"));
@@ -11076,6 +11883,32 @@ function registerInitCommand(program2) {
11076
11883
  else if (agent === "gemini") await setupGemini();
11077
11884
  else if (agent === "cursor") await setupCursor();
11078
11885
  else if (agent === "codex") await setupCodex();
11886
+ else if (agent === "windsurf") await setupWindsurf();
11887
+ else if (agent === "vscode") await setupVSCode();
11888
+ console.log("");
11889
+ }
11890
+ if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
11891
+ const alreadyInstalled = isDaemonServiceInstalled();
11892
+ if (!alreadyInstalled) {
11893
+ const { confirm: confirm3 } = await import("@inquirer/prompts");
11894
+ const installService = await confirm3({
11895
+ message: "Install daemon as a login service? (starts automatically on login)",
11896
+ default: true
11897
+ });
11898
+ if (installService) {
11899
+ const result = installDaemonService();
11900
+ if (result.ok) {
11901
+ console.log(
11902
+ import_chalk12.default.green(` \u2713 Daemon installed as login service (${result.platform})`)
11903
+ );
11904
+ } else {
11905
+ console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not install service: ${result.reason}`));
11906
+ console.log(import_chalk12.default.gray(" You can try again later with: node9 daemon install"));
11907
+ }
11908
+ }
11909
+ } else {
11910
+ console.log(import_chalk12.default.green(" \u2713 Daemon login service already installed"));
11911
+ }
11079
11912
  console.log("");
11080
11913
  }
11081
11914
  {
@@ -11102,7 +11935,7 @@ function registerInitCommand(program2) {
11102
11935
  }
11103
11936
 
11104
11937
  // src/cli/commands/undo.ts
11105
- var import_path29 = __toESM(require("path"));
11938
+ var import_path31 = __toESM(require("path"));
11106
11939
  var import_chalk14 = __toESM(require("chalk"));
11107
11940
 
11108
11941
  // src/tui/undo-navigator.ts
@@ -11261,7 +12094,7 @@ function findMatchingCwd(startDir, history) {
11261
12094
  let dir = startDir;
11262
12095
  while (true) {
11263
12096
  if (cwds.has(dir)) return dir;
11264
- const parent = import_path29.default.dirname(dir);
12097
+ const parent = import_path31.default.dirname(dir);
11265
12098
  if (parent === dir) return null;
11266
12099
  dir = parent;
11267
12100
  }
@@ -11389,7 +12222,7 @@ function registerUndoCommand(program2) {
11389
12222
 
11390
12223
  // src/cli/commands/watch.ts
11391
12224
  var import_chalk15 = __toESM(require("chalk"));
11392
- var import_child_process12 = require("child_process");
12225
+ var import_child_process13 = require("child_process");
11393
12226
  init_daemon();
11394
12227
  function registerWatchCommand(program2) {
11395
12228
  program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
@@ -11406,7 +12239,7 @@ function registerWatchCommand(program2) {
11406
12239
  }
11407
12240
  } catch {
11408
12241
  console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
11409
- const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
12242
+ const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
11410
12243
  detached: true,
11411
12244
  stdio: "ignore",
11412
12245
  env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
@@ -11436,7 +12269,7 @@ function registerWatchCommand(program2) {
11436
12269
  "\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
11437
12270
  )
11438
12271
  );
11439
- const result = (0, import_child_process12.spawnSync)(cmd, args, {
12272
+ const result = (0, import_child_process13.spawnSync)(cmd, args, {
11440
12273
  stdio: "inherit",
11441
12274
  env: { ...process.env, NODE9_WATCH_MODE: "1" }
11442
12275
  });
@@ -11451,18 +12284,18 @@ function registerWatchCommand(program2) {
11451
12284
  // src/mcp-gateway/index.ts
11452
12285
  var import_readline3 = __toESM(require("readline"));
11453
12286
  var import_chalk16 = __toESM(require("chalk"));
11454
- var import_child_process13 = require("child_process");
12287
+ var import_child_process14 = require("child_process");
11455
12288
  var import_execa3 = require("execa");
11456
12289
  init_orchestrator();
11457
12290
  init_provenance();
11458
12291
 
11459
12292
  // src/mcp-pin.ts
11460
- var import_fs27 = __toESM(require("fs"));
11461
- var import_path30 = __toESM(require("path"));
11462
- var import_os23 = __toESM(require("os"));
12293
+ var import_fs29 = __toESM(require("fs"));
12294
+ var import_path32 = __toESM(require("path"));
12295
+ var import_os25 = __toESM(require("os"));
11463
12296
  var import_crypto9 = __toESM(require("crypto"));
11464
12297
  function getPinsFilePath() {
11465
- return import_path30.default.join(import_os23.default.homedir(), ".node9", "mcp-pins.json");
12298
+ return import_path32.default.join(import_os25.default.homedir(), ".node9", "mcp-pins.json");
11466
12299
  }
11467
12300
  function hashToolDefinitions(tools) {
11468
12301
  const sorted = [...tools].sort((a, b) => {
@@ -11479,7 +12312,7 @@ function getServerKey(upstreamCommand) {
11479
12312
  function readMcpPinsSafe() {
11480
12313
  const filePath = getPinsFilePath();
11481
12314
  try {
11482
- const raw = import_fs27.default.readFileSync(filePath, "utf-8");
12315
+ const raw = import_fs29.default.readFileSync(filePath, "utf-8");
11483
12316
  if (!raw.trim()) {
11484
12317
  return { ok: false, reason: "corrupt", detail: "empty file" };
11485
12318
  }
@@ -11503,10 +12336,10 @@ function readMcpPins() {
11503
12336
  }
11504
12337
  function writeMcpPins(data) {
11505
12338
  const filePath = getPinsFilePath();
11506
- import_fs27.default.mkdirSync(import_path30.default.dirname(filePath), { recursive: true });
12339
+ import_fs29.default.mkdirSync(import_path32.default.dirname(filePath), { recursive: true });
11507
12340
  const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
11508
- import_fs27.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
11509
- import_fs27.default.renameSync(tmp, filePath);
12341
+ import_fs29.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
12342
+ import_fs29.default.renameSync(tmp, filePath);
11510
12343
  }
11511
12344
  function checkPin(serverKey, currentHash) {
11512
12345
  const result = readMcpPinsSafe();
@@ -11623,7 +12456,7 @@ async function runMcpGateway(upstreamCommand) {
11623
12456
  const safeEnv = Object.fromEntries(
11624
12457
  Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
11625
12458
  );
11626
- const child = (0, import_child_process13.spawn)(executable, cmdArgs, {
12459
+ const child = (0, import_child_process14.spawn)(executable, cmdArgs, {
11627
12460
  stdio: ["pipe", "pipe", "inherit"],
11628
12461
  // control stdin/stdout; inherit stderr
11629
12462
  shell: false,
@@ -11878,9 +12711,9 @@ function registerMcpGatewayCommand(program2) {
11878
12711
 
11879
12712
  // src/mcp-server/index.ts
11880
12713
  var import_readline4 = __toESM(require("readline"));
11881
- var import_fs28 = __toESM(require("fs"));
11882
- var import_os24 = __toESM(require("os"));
11883
- var import_path31 = __toESM(require("path"));
12714
+ var import_fs30 = __toESM(require("fs"));
12715
+ var import_os26 = __toESM(require("os"));
12716
+ var import_path33 = __toESM(require("path"));
11884
12717
  init_core();
11885
12718
  init_daemon();
11886
12719
  init_shields();
@@ -12055,13 +12888,13 @@ function handleStatus() {
12055
12888
  lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
12056
12889
  lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
12057
12890
  lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
12058
- const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
12059
- const globalConfig = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
12891
+ const projectConfig = import_path33.default.join(process.cwd(), "node9.config.json");
12892
+ const globalConfig = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
12060
12893
  lines.push(
12061
- `Project config (node9.config.json): ${import_fs28.default.existsSync(projectConfig) ? "present" : "not found"}`
12894
+ `Project config (node9.config.json): ${import_fs30.default.existsSync(projectConfig) ? "present" : "not found"}`
12062
12895
  );
12063
12896
  lines.push(
12064
- `Global config (~/.node9/config.json): ${import_fs28.default.existsSync(globalConfig) ? "present" : "not found"}`
12897
+ `Global config (~/.node9/config.json): ${import_fs30.default.existsSync(globalConfig) ? "present" : "not found"}`
12065
12898
  );
12066
12899
  return lines.join("\n");
12067
12900
  }
@@ -12135,21 +12968,21 @@ function handleShieldDisable(args) {
12135
12968
  writeActiveShields(active.filter((s) => s !== name));
12136
12969
  return `Shield "${name}" disabled.`;
12137
12970
  }
12138
- var GLOBAL_CONFIG_PATH2 = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
12971
+ var GLOBAL_CONFIG_PATH2 = import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json");
12139
12972
  var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
12140
12973
  function readGlobalConfigRaw() {
12141
12974
  try {
12142
- if (import_fs28.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12143
- return JSON.parse(import_fs28.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12975
+ if (import_fs30.default.existsSync(GLOBAL_CONFIG_PATH2)) {
12976
+ return JSON.parse(import_fs30.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
12144
12977
  }
12145
12978
  } catch {
12146
12979
  }
12147
12980
  return {};
12148
12981
  }
12149
12982
  function writeGlobalConfigRaw(data) {
12150
- const dir = import_path31.default.dirname(GLOBAL_CONFIG_PATH2);
12151
- if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
12152
- import_fs28.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12983
+ const dir = import_path33.default.dirname(GLOBAL_CONFIG_PATH2);
12984
+ if (!import_fs30.default.existsSync(dir)) import_fs30.default.mkdirSync(dir, { recursive: true });
12985
+ import_fs30.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
12153
12986
  }
12154
12987
  function handleApproverList() {
12155
12988
  const config = getConfig();
@@ -12192,9 +13025,9 @@ function handleApproverSet(args) {
12192
13025
  }
12193
13026
  function handleAuditGet(args) {
12194
13027
  const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
12195
- const auditPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "audit.log");
12196
- if (!import_fs28.default.existsSync(auditPath)) return "No audit log found.";
12197
- const lines = import_fs28.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
13028
+ const auditPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "audit.log");
13029
+ if (!import_fs30.default.existsSync(auditPath)) return "No audit log found.";
13030
+ const lines = import_fs30.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
12198
13031
  const recent = lines.slice(-limit);
12199
13032
  const entries = recent.map((line) => {
12200
13033
  try {
@@ -12512,25 +13345,950 @@ function registerMcpPinCommand(program2) {
12512
13345
  });
12513
13346
  }
12514
13347
 
13348
+ // src/cli/commands/sync.ts
13349
+ var import_chalk19 = __toESM(require("chalk"));
13350
+ init_sync();
13351
+ function registerSyncCommand(program2) {
13352
+ const policy = program2.command("policy").description("Manage cloud policy rules");
13353
+ policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
13354
+ process.stdout.write(import_chalk19.default.cyan("Syncing cloud policy rules\u2026"));
13355
+ const result = await runCloudSync();
13356
+ process.stdout.write("\n");
13357
+ if (!result.ok) {
13358
+ console.error(import_chalk19.default.red(`\u2717 ${result.reason}`));
13359
+ process.exit(1);
13360
+ }
13361
+ console.log(
13362
+ import_chalk19.default.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
13363
+ );
13364
+ console.log(import_chalk19.default.gray(` Cached at: ${result.fetchedAt}`));
13365
+ console.log(import_chalk19.default.gray(` File: ~/.node9/rules-cache.json`));
13366
+ });
13367
+ policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
13368
+ const status = getCloudSyncStatus();
13369
+ if (!status.cached) {
13370
+ console.log(import_chalk19.default.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
13371
+ return;
13372
+ }
13373
+ const rules = getCloudRules() ?? [];
13374
+ const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
13375
+ console.log(
13376
+ import_chalk19.default.bold(`
13377
+ Cloud policy rules`) + import_chalk19.default.gray(
13378
+ ` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
13379
+ `
13380
+ )
13381
+ );
13382
+ if (rules.length === 0) {
13383
+ console.log(import_chalk19.default.gray(" No rules defined in cloud policy.\n"));
13384
+ return;
13385
+ }
13386
+ for (const rule of rules) {
13387
+ const r = rule;
13388
+ const verdictColor = r.verdict === "block" ? import_chalk19.default.red : r.verdict === "allow" ? import_chalk19.default.green : import_chalk19.default.yellow;
13389
+ console.log(
13390
+ ` ${verdictColor(
13391
+ String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
13392
+ )} ${import_chalk19.default.white(String(r.name ?? "(unnamed)"))}`
13393
+ );
13394
+ if (r.reason) console.log(import_chalk19.default.gray(` ${String(r.reason)}`));
13395
+ }
13396
+ console.log("");
13397
+ });
13398
+ policy.command("status").description("Show current cloud policy cache status").action(() => {
13399
+ const s = getCloudSyncStatus();
13400
+ if (!s.cached) {
13401
+ console.log(import_chalk19.default.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
13402
+ } else {
13403
+ const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
13404
+ console.log(`
13405
+ Rules : ${import_chalk19.default.green(String(s.rules))} cloud rules loaded`);
13406
+ console.log(
13407
+ ` Synced : ${import_chalk19.default.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})
13408
+ `
13409
+ );
13410
+ }
13411
+ });
13412
+ }
13413
+
13414
+ // src/cli/commands/agents.ts
13415
+ var import_chalk20 = __toESM(require("chalk"));
13416
+ var SETUP_FN = {
13417
+ claude: setupClaude,
13418
+ gemini: setupGemini,
13419
+ cursor: setupCursor,
13420
+ codex: setupCodex,
13421
+ windsurf: setupWindsurf,
13422
+ vscode: setupVSCode
13423
+ };
13424
+ var TEARDOWN_FN = {
13425
+ claude: teardownClaude,
13426
+ gemini: teardownGemini,
13427
+ cursor: teardownCursor,
13428
+ codex: teardownCodex,
13429
+ windsurf: teardownWindsurf,
13430
+ vscode: teardownVSCode
13431
+ };
13432
+ var AGENT_NAMES = Object.keys(SETUP_FN);
13433
+ function registerAgentsCommand(program2) {
13434
+ const agents = program2.command("agents").description("List and manage AI agent integrations");
13435
+ agents.command("list").description("Show all supported agents and their Node9 status").action(() => {
13436
+ const statuses = getAgentsStatus();
13437
+ const anyInstalled = statuses.some((s) => s.installed);
13438
+ console.log("");
13439
+ console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
13440
+ console.log(" " + "\u2500".repeat(44));
13441
+ for (const s of statuses) {
13442
+ const installed = s.installed ? import_chalk20.default.green("\u2713") : import_chalk20.default.gray("\u2717");
13443
+ const wired = !s.installed ? import_chalk20.default.gray("\u2014") : s.wired ? import_chalk20.default.green("\u2713") : import_chalk20.default.yellow("\u2717");
13444
+ const mode = s.mode ? import_chalk20.default.gray(s.mode) : import_chalk20.default.gray("\u2014");
13445
+ const hint = s.installed && !s.wired ? import_chalk20.default.gray(` \u2190 node9 agents add ${s.name}`) : "";
13446
+ console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
13447
+ }
13448
+ console.log("");
13449
+ if (!anyInstalled) {
13450
+ console.log(
13451
+ import_chalk20.default.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + import_chalk20.default.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
13452
+ );
13453
+ return;
13454
+ }
13455
+ const unwired = statuses.filter((s) => s.installed && !s.wired);
13456
+ if (unwired.length > 0) {
13457
+ console.log(
13458
+ import_chalk20.default.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + import_chalk20.default.white(`node9 agents add ${unwired[0].name}`) + "\n"
13459
+ );
13460
+ }
13461
+ });
13462
+ agents.command("add").description("Wire Node9 into an agent").argument("<agent>", `Agent to wire: ${AGENT_NAMES.join(" | ")}`).action(async (agent) => {
13463
+ const name = agent.toLowerCase();
13464
+ const fn = SETUP_FN[name];
13465
+ if (!fn) {
13466
+ console.error(import_chalk20.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
13467
+ process.exit(1);
13468
+ }
13469
+ await fn();
13470
+ });
13471
+ agents.command("remove").description("Remove Node9 from an agent").argument("<agent>", `Agent to unwire: ${AGENT_NAMES.join(" | ")}`).action((agent) => {
13472
+ const name = agent.toLowerCase();
13473
+ const fn = TEARDOWN_FN[name];
13474
+ if (!fn) {
13475
+ console.error(import_chalk20.default.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
13476
+ process.exit(1);
13477
+ }
13478
+ console.log(import_chalk20.default.cyan(`
13479
+ \u{1F6E1}\uFE0F Node9: removing from ${name}...
13480
+ `));
13481
+ fn();
13482
+ console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
13483
+ });
13484
+ }
13485
+
13486
+ // src/cli/commands/scan.ts
13487
+ var import_chalk21 = __toESM(require("chalk"));
13488
+ var import_fs31 = __toESM(require("fs"));
13489
+ var import_path34 = __toESM(require("path"));
13490
+ var import_os27 = __toESM(require("os"));
13491
+ init_shields();
13492
+ init_config();
13493
+ init_policy();
13494
+ init_dlp();
13495
+ var CLAUDE_PRICING2 = {
13496
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13497
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13498
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
13499
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13500
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13501
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13502
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13503
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13504
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
13505
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
13506
+ };
13507
+ function claudeModelPrice2(model) {
13508
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
13509
+ for (const [key, p] of Object.entries(CLAUDE_PRICING2)) {
13510
+ if (base === key || base.startsWith(key)) return p;
13511
+ }
13512
+ return null;
13513
+ }
13514
+ function num2(n) {
13515
+ return n.toLocaleString();
13516
+ }
13517
+ function fmtCost2(usd) {
13518
+ if (usd < 1e-3) return "< $0.001";
13519
+ if (usd < 1) return "$" + usd.toFixed(4);
13520
+ return "$" + usd.toFixed(2);
13521
+ }
13522
+ function fmtTs(ts) {
13523
+ try {
13524
+ return new Date(ts).toLocaleDateString("en-US", {
13525
+ month: "short",
13526
+ day: "numeric",
13527
+ year: "numeric"
13528
+ });
13529
+ } catch {
13530
+ return ts.slice(0, 10);
13531
+ }
13532
+ }
13533
+ function preview(input, max) {
13534
+ const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
13535
+ const s = String(cmd).replace(/\s+/g, " ").trim();
13536
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
13537
+ }
13538
+ function buildRuleSources() {
13539
+ const sources = [];
13540
+ for (const [shieldName, shield] of Object.entries(SHIELDS)) {
13541
+ for (const rule of shield.smartRules) {
13542
+ sources.push({ shieldName, shieldLabel: shieldName, rule });
13543
+ }
13544
+ }
13545
+ try {
13546
+ const config = getConfig();
13547
+ for (const rule of config.policy.smartRules) {
13548
+ if (!rule.name) continue;
13549
+ if (rule.name.startsWith("shield:")) continue;
13550
+ const isCloud = rule.name.startsWith("cloud:");
13551
+ sources.push({
13552
+ shieldName: isCloud ? "cloud" : "custom",
13553
+ shieldLabel: isCloud ? "Cloud Policy" : "Your Rules",
13554
+ rule
13555
+ });
13556
+ }
13557
+ } catch {
13558
+ }
13559
+ return sources;
13560
+ }
13561
+ function scanClaudeHistory(startDate) {
13562
+ const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13563
+ const result = {
13564
+ filesScanned: 0,
13565
+ sessions: 0,
13566
+ totalToolCalls: 0,
13567
+ bashCalls: 0,
13568
+ findings: [],
13569
+ dlpFindings: [],
13570
+ totalCostUSD: 0,
13571
+ firstDate: null,
13572
+ lastDate: null
13573
+ };
13574
+ if (!import_fs31.default.existsSync(projectsDir)) return result;
13575
+ let projDirs;
13576
+ try {
13577
+ projDirs = import_fs31.default.readdirSync(projectsDir);
13578
+ } catch {
13579
+ return result;
13580
+ }
13581
+ const ruleSources = buildRuleSources();
13582
+ for (const proj of projDirs) {
13583
+ const projPath = import_path34.default.join(projectsDir, proj);
13584
+ try {
13585
+ if (!import_fs31.default.statSync(projPath).isDirectory()) continue;
13586
+ } catch {
13587
+ continue;
13588
+ }
13589
+ const projLabel = decodeURIComponent(proj).replace(import_os27.default.homedir(), "~").slice(0, 40);
13590
+ let files;
13591
+ try {
13592
+ files = import_fs31.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
13593
+ } catch {
13594
+ continue;
13595
+ }
13596
+ for (const file of files) {
13597
+ result.filesScanned++;
13598
+ result.sessions++;
13599
+ let raw;
13600
+ try {
13601
+ raw = import_fs31.default.readFileSync(import_path34.default.join(projPath, file), "utf-8");
13602
+ } catch {
13603
+ continue;
13604
+ }
13605
+ for (const line of raw.split("\n")) {
13606
+ if (!line.trim()) continue;
13607
+ let entry;
13608
+ try {
13609
+ entry = JSON.parse(line);
13610
+ } catch {
13611
+ continue;
13612
+ }
13613
+ if (entry.type !== "assistant") continue;
13614
+ if (startDate && entry.timestamp) {
13615
+ if (new Date(entry.timestamp) < startDate) continue;
13616
+ }
13617
+ if (entry.timestamp) {
13618
+ if (!result.firstDate || entry.timestamp < result.firstDate)
13619
+ result.firstDate = entry.timestamp;
13620
+ if (!result.lastDate || entry.timestamp > result.lastDate)
13621
+ result.lastDate = entry.timestamp;
13622
+ }
13623
+ const usage = entry.message?.usage;
13624
+ const model = entry.message?.model;
13625
+ if (usage && model) {
13626
+ const p = claudeModelPrice2(model);
13627
+ if (p) {
13628
+ result.totalCostUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
13629
+ }
13630
+ }
13631
+ const content = entry.message?.content;
13632
+ if (!Array.isArray(content)) continue;
13633
+ for (const block of content) {
13634
+ if (block.type !== "tool_use") continue;
13635
+ result.totalToolCalls++;
13636
+ const toolName = block.name ?? "";
13637
+ const toolNameLower = toolName.toLowerCase();
13638
+ const input = block.input ?? {};
13639
+ if (toolNameLower === "bash" || toolNameLower === "execute_bash") {
13640
+ result.bashCalls++;
13641
+ }
13642
+ const dlpMatch = scanArgs(input);
13643
+ if (dlpMatch) {
13644
+ const isDupe = result.dlpFindings.some(
13645
+ (f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
13646
+ );
13647
+ if (!isDupe) {
13648
+ result.dlpFindings.push({
13649
+ patternName: dlpMatch.patternName,
13650
+ redactedSample: dlpMatch.redactedSample,
13651
+ toolName,
13652
+ timestamp: entry.timestamp ?? "",
13653
+ project: projLabel
13654
+ });
13655
+ }
13656
+ }
13657
+ for (const source of ruleSources) {
13658
+ const { rule } = source;
13659
+ if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
13660
+ if (!evaluateSmartConditions(input, rule)) continue;
13661
+ const inputPreview = preview(input, 120);
13662
+ const isDupe = result.findings.some(
13663
+ (f) => f.source.rule.name === rule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
13664
+ );
13665
+ if (!isDupe) {
13666
+ result.findings.push({
13667
+ source,
13668
+ toolName,
13669
+ input,
13670
+ timestamp: entry.timestamp ?? "",
13671
+ project: projLabel
13672
+ });
13673
+ }
13674
+ break;
13675
+ }
13676
+ }
13677
+ }
13678
+ }
13679
+ }
13680
+ return result;
13681
+ }
13682
+ function registerScanCommand(program2) {
13683
+ program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per shield", "5").action((options) => {
13684
+ const topN = Math.max(1, parseInt(options.top, 10) || 5);
13685
+ const startDate = options.all ? null : (() => {
13686
+ const d = /* @__PURE__ */ new Date();
13687
+ d.setDate(d.getDate() - (parseInt(options.days, 10) || 90));
13688
+ d.setHours(0, 0, 0, 0);
13689
+ return d;
13690
+ })();
13691
+ console.log("");
13692
+ console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
13693
+ console.log("");
13694
+ const projectsDir = import_path34.default.join(import_os27.default.homedir(), ".claude", "projects");
13695
+ if (!import_fs31.default.existsSync(projectsDir)) {
13696
+ console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
13697
+ console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
13698
+ return;
13699
+ }
13700
+ process.stdout.write(import_chalk21.default.dim(" Scanning\u2026"));
13701
+ const scan = scanClaudeHistory(startDate);
13702
+ process.stdout.write("\r" + " ".repeat(20) + "\r");
13703
+ if (scan.filesScanned === 0) {
13704
+ console.log(import_chalk21.default.yellow(" No JSONL session files found.\n"));
13705
+ return;
13706
+ }
13707
+ const rangeLabel = options.all ? import_chalk21.default.dim("all time") : import_chalk21.default.dim(`last ${options.days ?? 90} days`);
13708
+ const dateRange = scan.firstDate && scan.lastDate ? import_chalk21.default.dim(` ${fmtTs(scan.firstDate)} \u2013 ${fmtTs(scan.lastDate)}`) : "";
13709
+ console.log(
13710
+ " " + import_chalk21.default.white(num2(scan.sessions)) + import_chalk21.default.dim(" sessions ") + import_chalk21.default.white(num2(scan.totalToolCalls)) + import_chalk21.default.dim(" tool calls ") + import_chalk21.default.white(num2(scan.bashCalls)) + import_chalk21.default.dim(" bash commands ") + rangeLabel + dateRange
13711
+ );
13712
+ console.log("");
13713
+ const byShield = /* @__PURE__ */ new Map();
13714
+ for (const f of scan.findings) {
13715
+ const key = f.source.shieldName;
13716
+ const entry = byShield.get(key) ?? { label: f.source.shieldLabel, findings: [] };
13717
+ entry.findings.push(f);
13718
+ byShield.set(key, entry);
13719
+ }
13720
+ const totalFindings = scan.findings.length;
13721
+ if (totalFindings === 0 && scan.dlpFindings.length === 0) {
13722
+ console.log(import_chalk21.default.green(" \u2705 No findings across all shields and rules."));
13723
+ console.log(import_chalk21.default.dim(" node9 is still worth running \u2014 it monitors in real time.\n"));
13724
+ } else {
13725
+ if (totalFindings > 0) {
13726
+ console.log(
13727
+ " " + import_chalk21.default.bold("If node9 had been installed:") + " " + import_chalk21.default.yellow.bold(
13728
+ `${num2(totalFindings)} command${totalFindings !== 1 ? "s" : ""} flagged for review`
13729
+ )
13730
+ );
13731
+ console.log("");
13732
+ const sorted = [...byShield.entries()].sort(
13733
+ (a, b) => b[1].findings.length - a[1].findings.length
13734
+ );
13735
+ for (const [shieldName, { label, findings }] of sorted) {
13736
+ const count = findings.length;
13737
+ const isUserRule = shieldName === "custom" || shieldName === "cloud";
13738
+ const shieldBadge = isUserRule ? import_chalk21.default.magenta(label) : import_chalk21.default.cyan(label);
13739
+ console.log(" " + import_chalk21.default.dim("\u2500".repeat(70)));
13740
+ console.log(
13741
+ " " + shieldBadge + import_chalk21.default.dim(" \xB7 ") + import_chalk21.default.yellow(`${num2(count)} finding${count !== 1 ? "s" : ""}`) + (isUserRule ? "" : import_chalk21.default.dim(` \u2192 node9 shield enable ${shieldName}`))
13742
+ );
13743
+ const byRule = /* @__PURE__ */ new Map();
13744
+ for (const f of findings) {
13745
+ const ruleKey = f.source.rule.name ?? "unnamed";
13746
+ const arr = byRule.get(ruleKey) ?? [];
13747
+ arr.push(f);
13748
+ byRule.set(ruleKey, arr);
13749
+ }
13750
+ for (const [, ruleFindings] of byRule) {
13751
+ const rule = ruleFindings[0].source.rule;
13752
+ const ruleCount = ruleFindings.length;
13753
+ const countBadge = ruleCount > 1 ? import_chalk21.default.white(` \xD7${ruleCount}`) : "";
13754
+ const shortName = (rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
13755
+ console.log(
13756
+ " " + import_chalk21.default.white(shortName) + countBadge + (rule.reason ? import_chalk21.default.dim(` \u2014 ${rule.reason}`) : "")
13757
+ );
13758
+ const shown = ruleFindings.slice(0, topN);
13759
+ for (const f of shown) {
13760
+ const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
13761
+ const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
13762
+ const cmd = import_chalk21.default.gray(preview(f.input, 55));
13763
+ console.log(` ${ts}${proj}${cmd}`);
13764
+ }
13765
+ if (ruleFindings.length > topN) {
13766
+ console.log(
13767
+ import_chalk21.default.dim(
13768
+ ` \u2026 and ${ruleFindings.length - topN} more (--top ${ruleFindings.length})`
13769
+ )
13770
+ );
13771
+ }
13772
+ }
13773
+ console.log("");
13774
+ }
13775
+ }
13776
+ if (scan.dlpFindings.length > 0) {
13777
+ console.log(" " + import_chalk21.default.dim("\u2500".repeat(70)));
13778
+ console.log(
13779
+ " " + import_chalk21.default.red.bold("Secrets / DLP") + import_chalk21.default.dim(" \xB7 ") + import_chalk21.default.red(
13780
+ `${num2(scan.dlpFindings.length)} potential secret leak${scan.dlpFindings.length !== 1 ? "s" : ""}`
13781
+ )
13782
+ );
13783
+ const shownDlp = scan.dlpFindings.slice(0, topN);
13784
+ for (const f of shownDlp) {
13785
+ const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
13786
+ const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
13787
+ console.log(
13788
+ ` ${ts}${proj}` + import_chalk21.default.yellow(f.patternName) + import_chalk21.default.dim(" ") + import_chalk21.default.gray(f.redactedSample)
13789
+ );
13790
+ }
13791
+ if (scan.dlpFindings.length > topN) {
13792
+ console.log(
13793
+ import_chalk21.default.dim(
13794
+ ` \u2026 and ${scan.dlpFindings.length - topN} more (--top ${scan.dlpFindings.length})`
13795
+ )
13796
+ );
13797
+ }
13798
+ console.log("");
13799
+ }
13800
+ }
13801
+ if (scan.totalCostUSD > 0) {
13802
+ console.log(
13803
+ " " + import_chalk21.default.bold("Claude spend:") + " " + import_chalk21.default.yellow(fmtCost2(scan.totalCostUSD)) + import_chalk21.default.dim(" (for per-period breakdown: node9 report)")
13804
+ );
13805
+ console.log("");
13806
+ }
13807
+ const auditLog = import_path34.default.join(import_os27.default.homedir(), ".node9", "audit.log");
13808
+ if (import_fs31.default.existsSync(auditLog)) {
13809
+ console.log(import_chalk21.default.green(" \u2705 node9 is active \u2014 future sessions are protected."));
13810
+ console.log(
13811
+ import_chalk21.default.dim(" Run ") + import_chalk21.default.cyan("node9 report") + import_chalk21.default.dim(" to see live stats.")
13812
+ );
13813
+ } else {
13814
+ console.log(import_chalk21.default.yellow.bold(" \u26A1 node9 was not running during these sessions."));
13815
+ console.log(
13816
+ " " + import_chalk21.default.white("Run ") + import_chalk21.default.cyan("node9 init") + import_chalk21.default.white(" to start protecting your AI agents.")
13817
+ );
13818
+ }
13819
+ console.log("");
13820
+ });
13821
+ }
13822
+
13823
+ // src/cli/commands/sessions.ts
13824
+ var import_chalk22 = __toESM(require("chalk"));
13825
+ var import_fs32 = __toESM(require("fs"));
13826
+ var import_path35 = __toESM(require("path"));
13827
+ var import_os28 = __toESM(require("os"));
13828
+ var CLAUDE_PRICING3 = {
13829
+ "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13830
+ "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
13831
+ "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
13832
+ "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13833
+ "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13834
+ "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13835
+ "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13836
+ "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
13837
+ "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
13838
+ "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
13839
+ };
13840
+ function modelPrice(model) {
13841
+ const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
13842
+ for (const [key, p] of Object.entries(CLAUDE_PRICING3)) {
13843
+ if (base === key || base.startsWith(key)) return p;
13844
+ }
13845
+ return null;
13846
+ }
13847
+ function encodeProjectPath(projectPath) {
13848
+ return projectPath.replace(/\//g, "-");
13849
+ }
13850
+ function sessionJsonlPath(projectPath, sessionId) {
13851
+ const encoded = encodeProjectPath(projectPath);
13852
+ return import_path35.default.join(import_os28.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
13853
+ }
13854
+ function projectLabel(projectPath) {
13855
+ return projectPath.replace(import_os28.default.homedir(), "~");
13856
+ }
13857
+ function parseHistoryLines(lines) {
13858
+ const entries = [];
13859
+ for (const line of lines) {
13860
+ if (!line.trim()) continue;
13861
+ try {
13862
+ const obj = JSON.parse(line);
13863
+ if (typeof obj["display"] === "string" && (typeof obj["timestamp"] === "string" || typeof obj["timestamp"] === "number") && typeof obj["project"] === "string" && typeof obj["sessionId"] === "string") {
13864
+ const ts = typeof obj["timestamp"] === "number" ? new Date(obj["timestamp"]).toISOString() : obj["timestamp"];
13865
+ entries.push({
13866
+ display: obj["display"],
13867
+ timestamp: ts,
13868
+ project: obj["project"],
13869
+ sessionId: obj["sessionId"]
13870
+ });
13871
+ }
13872
+ } catch {
13873
+ }
13874
+ }
13875
+ return entries;
13876
+ }
13877
+ function parseSessionLines(lines) {
13878
+ const toolCalls = [];
13879
+ let costUSD = 0;
13880
+ let hasSnapshot = false;
13881
+ const modifiedFiles = [];
13882
+ const seenFiles = /* @__PURE__ */ new Set();
13883
+ for (const line of lines) {
13884
+ if (!line.trim()) continue;
13885
+ let entry;
13886
+ try {
13887
+ entry = JSON.parse(line);
13888
+ } catch {
13889
+ continue;
13890
+ }
13891
+ if (entry.type === "file-history-snapshot") {
13892
+ hasSnapshot = true;
13893
+ continue;
13894
+ }
13895
+ if (entry.type !== "assistant") continue;
13896
+ const usage = entry.message?.usage;
13897
+ const model = entry.message?.model;
13898
+ if (usage && model) {
13899
+ const p = modelPrice(model);
13900
+ if (p) {
13901
+ costUSD += (usage.input_tokens ?? 0) * p.i + (usage.output_tokens ?? 0) * p.o + (usage.cache_creation_input_tokens ?? 0) * p.cw + (usage.cache_read_input_tokens ?? 0) * p.cr;
13902
+ }
13903
+ }
13904
+ const content = entry.message?.content;
13905
+ if (!Array.isArray(content)) continue;
13906
+ for (const block of content) {
13907
+ if (block.type !== "tool_use") continue;
13908
+ const tool = block.name ?? "";
13909
+ const input = block.input ?? {};
13910
+ toolCalls.push({ tool, input, timestamp: entry.timestamp ?? "" });
13911
+ const toolLower = tool.toLowerCase();
13912
+ if (toolLower === "write" || toolLower === "edit" || toolLower === "notebookedit") {
13913
+ const fp = input.file_path ?? input.path;
13914
+ if (typeof fp === "string" && !seenFiles.has(fp)) {
13915
+ seenFiles.add(fp);
13916
+ modifiedFiles.push(fp);
13917
+ }
13918
+ }
13919
+ }
13920
+ }
13921
+ return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
13922
+ }
13923
+ function loadAuditEntries(auditPath) {
13924
+ const aPath = auditPath ?? import_path35.default.join(import_os28.default.homedir(), ".node9", "audit.log");
13925
+ let raw;
13926
+ try {
13927
+ raw = import_fs32.default.readFileSync(aPath, "utf-8");
13928
+ } catch {
13929
+ return [];
13930
+ }
13931
+ const entries = [];
13932
+ for (const line of raw.split("\n")) {
13933
+ if (!line.trim()) continue;
13934
+ try {
13935
+ const e = JSON.parse(line);
13936
+ if (!e.ts || !e.tool || !e.decision) continue;
13937
+ if (e.decision === "allow" || e.decision === "allowed") continue;
13938
+ entries.push(e);
13939
+ } catch {
13940
+ }
13941
+ }
13942
+ return entries;
13943
+ }
13944
+ function auditEntriesInWindow(entries, windowStart, windowEnd) {
13945
+ const start = new Date(windowStart).getTime();
13946
+ const end = new Date(windowEnd).getTime();
13947
+ const result = [];
13948
+ for (const e of entries) {
13949
+ const t = new Date(e.ts).getTime();
13950
+ if (t < start || t > end) continue;
13951
+ result.push({
13952
+ tool: e.tool,
13953
+ args: e.args,
13954
+ argsHash: e.argsHash,
13955
+ timestamp: e.ts,
13956
+ decision: e.decision,
13957
+ checkedBy: e.checkedBy
13958
+ });
13959
+ }
13960
+ return result;
13961
+ }
13962
+ function buildSessions(days, historyPath) {
13963
+ const hPath = historyPath ?? import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
13964
+ let historyRaw;
13965
+ try {
13966
+ historyRaw = import_fs32.default.readFileSync(hPath, "utf-8");
13967
+ } catch {
13968
+ return [];
13969
+ }
13970
+ const cutoff = days !== null ? (() => {
13971
+ const d = /* @__PURE__ */ new Date();
13972
+ d.setDate(d.getDate() - days);
13973
+ d.setHours(0, 0, 0, 0);
13974
+ return d;
13975
+ })() : null;
13976
+ const entries = parseHistoryLines(historyRaw.split("\n"));
13977
+ const bySession = /* @__PURE__ */ new Map();
13978
+ for (const e of entries) {
13979
+ if (cutoff && new Date(e.timestamp) < cutoff) continue;
13980
+ const existing = bySession.get(e.sessionId);
13981
+ if (!existing || e.timestamp < existing.timestamp) {
13982
+ bySession.set(e.sessionId, e);
13983
+ }
13984
+ }
13985
+ const allAuditEntries = loadAuditEntries();
13986
+ const summaries = [];
13987
+ for (const entry of bySession.values()) {
13988
+ const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
13989
+ let sessionLines = [];
13990
+ try {
13991
+ sessionLines = import_fs32.default.readFileSync(jsonlFile, "utf-8").split("\n");
13992
+ } catch {
13993
+ }
13994
+ const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
13995
+ const windowStart = entry.timestamp;
13996
+ const lastToolTs = toolCalls.length > 0 ? toolCalls[toolCalls.length - 1].timestamp : "";
13997
+ const windowEnd = new Date(
13998
+ Math.max(new Date(windowStart).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
13999
+ // 5 min buffer
14000
+ ).toISOString();
14001
+ const blockedCalls = auditEntriesInWindow(allAuditEntries, windowStart, windowEnd);
14002
+ summaries.push({
14003
+ sessionId: entry.sessionId,
14004
+ project: entry.project,
14005
+ projectLabel: projectLabel(entry.project),
14006
+ firstPrompt: entry.display,
14007
+ startTime: entry.timestamp,
14008
+ toolCalls,
14009
+ blockedCalls,
14010
+ costUSD,
14011
+ hasSnapshot,
14012
+ modifiedFiles
14013
+ });
14014
+ }
14015
+ summaries.sort((a, b) => a.startTime > b.startTime ? -1 : 1);
14016
+ return summaries;
14017
+ }
14018
+ function fmtCost3(usd) {
14019
+ if (usd === 0) return "";
14020
+ if (usd < 1e-3) return "< $0.001";
14021
+ if (usd < 1) return "$" + usd.toFixed(3);
14022
+ return "$" + usd.toFixed(2);
14023
+ }
14024
+ function fmtDate2(iso) {
14025
+ try {
14026
+ return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric" });
14027
+ } catch {
14028
+ return iso.slice(0, 10);
14029
+ }
14030
+ }
14031
+ function fmtTime(iso) {
14032
+ try {
14033
+ return new Date(iso).toLocaleTimeString("en-US", {
14034
+ hour: "2-digit",
14035
+ minute: "2-digit",
14036
+ hour12: false
14037
+ });
14038
+ } catch {
14039
+ return iso.slice(11, 16);
14040
+ }
14041
+ }
14042
+ function fmtDateTime(iso) {
14043
+ try {
14044
+ return new Date(iso).toLocaleString("en-US", {
14045
+ month: "short",
14046
+ day: "numeric",
14047
+ year: "numeric",
14048
+ hour: "2-digit",
14049
+ minute: "2-digit",
14050
+ hour12: false
14051
+ });
14052
+ } catch {
14053
+ return iso;
14054
+ }
14055
+ }
14056
+ function truncate(s, max) {
14057
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
14058
+ }
14059
+ function toolInputSummary(tool, input) {
14060
+ const fp = input.file_path ?? input.path;
14061
+ if (typeof fp === "string") return fp;
14062
+ const cmd = input.command;
14063
+ if (typeof cmd === "string") return truncate(cmd.replace(/\s+/g, " "), 60);
14064
+ const q = input.query ?? input.url;
14065
+ if (typeof q === "string") return truncate(String(q), 60);
14066
+ return "";
14067
+ }
14068
+ function toolColor(tool) {
14069
+ const t = tool.toLowerCase();
14070
+ if (t === "bash" || t === "execute_bash") return import_chalk22.default.red;
14071
+ if (t === "write") return import_chalk22.default.green;
14072
+ if (t === "edit" || t === "notebookedit") return import_chalk22.default.yellow;
14073
+ if (t === "read") return import_chalk22.default.cyan;
14074
+ return import_chalk22.default.gray;
14075
+ }
14076
+ function barStr2(value, max, width) {
14077
+ if (max === 0 || width <= 0) return "\u2591".repeat(width);
14078
+ const filled = Math.max(1, Math.round(value / max * width));
14079
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
14080
+ }
14081
+ function colorBar2(value, max, width) {
14082
+ const s = barStr2(value, max, width);
14083
+ const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
14084
+ return import_chalk22.default.cyan(s.slice(0, filled)) + import_chalk22.default.dim(s.slice(filled));
14085
+ }
14086
+ function renderSummary(summaries) {
14087
+ const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
14088
+ const totalCost = summaries.reduce((n, s) => n + s.costUSD, 0);
14089
+ const totalFiles = summaries.reduce((n, s) => n + s.modifiedFiles.length, 0);
14090
+ const totalBlocked = summaries.reduce((n, s) => n + s.blockedCalls.length, 0);
14091
+ const snapshots = summaries.filter((s) => s.hasSnapshot).length;
14092
+ const avgCost = summaries.length > 0 ? totalCost / summaries.length : 0;
14093
+ const toolCounts = /* @__PURE__ */ new Map();
14094
+ for (const s of summaries) {
14095
+ for (const tc of s.toolCalls) {
14096
+ const key = tc.tool.toLowerCase();
14097
+ toolCounts.set(key, (toolCounts.get(key) ?? 0) + 1);
14098
+ }
14099
+ }
14100
+ const groups = { Bash: 0, Read: 0, Write: 0, Edit: 0, Other: 0 };
14101
+ for (const [tool, count] of toolCounts) {
14102
+ if (tool === "bash" || tool === "execute_bash") groups["Bash"] += count;
14103
+ else if (tool === "read") groups["Read"] += count;
14104
+ else if (tool === "write") groups["Write"] += count;
14105
+ else if (tool === "edit" || tool === "notebookedit") groups["Edit"] += count;
14106
+ else groups["Other"] += count;
14107
+ }
14108
+ const projCosts = /* @__PURE__ */ new Map();
14109
+ for (const s of summaries) {
14110
+ projCosts.set(s.projectLabel, (projCosts.get(s.projectLabel) ?? 0) + s.costUSD);
14111
+ }
14112
+ const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
14113
+ const W = 20;
14114
+ console.log(import_chalk22.default.dim(" " + "\u2500".repeat(70)));
14115
+ console.log(
14116
+ " " + import_chalk22.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk22.default.dim("sessions ") + import_chalk22.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk22.default.dim("total ") + import_chalk22.default.bold.white(String(totalTools).padEnd(6)) + import_chalk22.default.dim("tool calls ") + import_chalk22.default.bold.white(String(totalFiles)) + import_chalk22.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk22.default.dim(" ") + import_chalk22.default.red.bold(String(totalBlocked)) + import_chalk22.default.dim(" blocked by node9") : "")
14117
+ );
14118
+ console.log(
14119
+ " " + import_chalk22.default.dim("avg ") + import_chalk22.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk22.default.dim("/session ") + import_chalk22.default.green(String(snapshots)) + import_chalk22.default.dim(` of ${summaries.length} sessions had snapshots`)
14120
+ );
14121
+ console.log("");
14122
+ console.log(" " + import_chalk22.default.dim("Tool breakdown:"));
14123
+ const maxGroup = Math.max(...Object.values(groups));
14124
+ for (const [label, count] of Object.entries(groups)) {
14125
+ if (count === 0) continue;
14126
+ const pct2 = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
14127
+ console.log(
14128
+ " " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk22.default.white(String(count).padStart(4)) + import_chalk22.default.dim(` (${String(pct2)}%)`)
14129
+ );
14130
+ }
14131
+ console.log("");
14132
+ if (topProjects.length > 1) {
14133
+ console.log(" " + import_chalk22.default.dim("Cost by project:"));
14134
+ const maxProjCost = topProjects[0][1];
14135
+ for (const [proj, cost] of topProjects) {
14136
+ console.log(
14137
+ " " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk22.default.yellow(fmtCost3(cost))
14138
+ );
14139
+ }
14140
+ console.log("");
14141
+ }
14142
+ console.log(import_chalk22.default.dim(" " + "\u2500".repeat(70)));
14143
+ console.log("");
14144
+ }
14145
+ function renderList(summaries, totalCost) {
14146
+ if (summaries.length === 0) {
14147
+ console.log(import_chalk22.default.yellow(" No sessions found in the requested range.\n"));
14148
+ return;
14149
+ }
14150
+ const totalLabel = totalCost > 0 ? import_chalk22.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
14151
+ console.log(
14152
+ " " + import_chalk22.default.white(String(summaries.length)) + import_chalk22.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
14153
+ );
14154
+ console.log("");
14155
+ let lastGroup = "";
14156
+ for (const s of summaries) {
14157
+ const group = fmtDate2(s.startTime) + " " + s.projectLabel;
14158
+ if (group !== lastGroup) {
14159
+ console.log(
14160
+ import_chalk22.default.dim(" \u2500\u2500\u2500 ") + import_chalk22.default.bold(fmtDate2(s.startTime)) + import_chalk22.default.dim(" " + s.projectLabel)
14161
+ );
14162
+ lastGroup = group;
14163
+ }
14164
+ const timeStr = import_chalk22.default.dim(fmtTime(s.startTime));
14165
+ const prompt = import_chalk22.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
14166
+ const tools = s.toolCalls.length > 0 ? import_chalk22.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk22.default.dim(" 0 tools");
14167
+ const cost = s.costUSD > 0 ? import_chalk22.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
14168
+ const blocked = s.blockedCalls.length > 0 ? import_chalk22.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
14169
+ const snap = s.hasSnapshot ? import_chalk22.default.green(" \u{1F4F8}") : "";
14170
+ const sid = import_chalk22.default.dim(" " + s.sessionId.slice(0, 8));
14171
+ console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${sid}`);
14172
+ }
14173
+ console.log("");
14174
+ console.log(
14175
+ import_chalk22.default.dim(" Run") + " " + import_chalk22.default.cyan("node9 sessions --detail <session-id>") + import_chalk22.default.dim(" for full tool trace.")
14176
+ );
14177
+ console.log("");
14178
+ }
14179
+ function renderDetail(s) {
14180
+ console.log("");
14181
+ console.log(import_chalk22.default.bold(" Session ") + import_chalk22.default.dim(s.sessionId));
14182
+ console.log(
14183
+ import_chalk22.default.bold(" Prompt ") + import_chalk22.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
14184
+ );
14185
+ console.log(import_chalk22.default.bold(" Project ") + import_chalk22.default.white(s.projectLabel));
14186
+ console.log(import_chalk22.default.bold(" When ") + import_chalk22.default.white(fmtDateTime(s.startTime)));
14187
+ if (s.costUSD > 0)
14188
+ console.log(import_chalk22.default.bold(" Cost ") + import_chalk22.default.yellow("~" + fmtCost3(s.costUSD)));
14189
+ console.log(
14190
+ import_chalk22.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk22.default.green("\u2713 taken") : import_chalk22.default.dim("none"))
14191
+ );
14192
+ console.log("");
14193
+ if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
14194
+ console.log(import_chalk22.default.dim(" No tool calls recorded.\n"));
14195
+ return;
14196
+ }
14197
+ const timeline = [
14198
+ ...s.toolCalls.map((tc) => ({ kind: "tool", tc })),
14199
+ ...s.blockedCalls.map((bc) => ({ kind: "blocked", bc }))
14200
+ ].sort((a, b) => {
14201
+ const ta = a.kind === "tool" ? a.tc.timestamp : a.bc.timestamp;
14202
+ const tb = b.kind === "tool" ? b.tc.timestamp : b.bc.timestamp;
14203
+ return ta < tb ? -1 : ta > tb ? 1 : 0;
14204
+ });
14205
+ const headerParts = [`Tool calls (${s.toolCalls.length})`];
14206
+ if (s.blockedCalls.length > 0)
14207
+ headerParts.push(import_chalk22.default.red(`${s.blockedCalls.length} blocked by node9`));
14208
+ console.log(import_chalk22.default.bold(" " + headerParts.join(" \xB7 ")));
14209
+ console.log("");
14210
+ for (const entry of timeline) {
14211
+ if (entry.kind === "tool") {
14212
+ const tc = entry.tc;
14213
+ const colorFn = toolColor(tc.tool);
14214
+ const toolPad = colorFn(tc.tool.padEnd(16));
14215
+ const detail = import_chalk22.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
14216
+ const ts = tc.timestamp ? import_chalk22.default.dim(fmtTime(tc.timestamp) + " ") : " ";
14217
+ console.log(` ${ts}${toolPad} ${detail}`);
14218
+ } else {
14219
+ const bc = entry.bc;
14220
+ const ts = bc.timestamp ? import_chalk22.default.dim(fmtTime(bc.timestamp) + " ") : " ";
14221
+ const label = import_chalk22.default.red("\u{1F6D1} BLOCKED".padEnd(16));
14222
+ const toolName = import_chalk22.default.red(bc.tool.padEnd(10));
14223
+ const argsSummary = bc.args ? import_chalk22.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk22.default.dim("[args not logged]");
14224
+ const reason = bc.checkedBy ? import_chalk22.default.dim(" \u2190 " + bc.checkedBy) : "";
14225
+ console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
14226
+ }
14227
+ }
14228
+ console.log("");
14229
+ if (s.modifiedFiles.length > 0) {
14230
+ console.log(import_chalk22.default.bold(` Files modified (${s.modifiedFiles.length}):`));
14231
+ for (const f of s.modifiedFiles) {
14232
+ console.log(" " + import_chalk22.default.yellow(f));
14233
+ }
14234
+ console.log("");
14235
+ }
14236
+ }
14237
+ function registerSessionsCommand(program2) {
14238
+ program2.command("sessions").description("Show what your AI agent did \u2014 sessions, tool calls, cost, and file changes").option("--all", "Show all sessions (default: last 7 days)").option("--days <n>", "Show last N days of sessions", "7").option("--detail <sessionId>", "Show full tool trace for a session").action((options) => {
14239
+ console.log("");
14240
+ console.log(import_chalk22.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk22.default.dim(" \u2014 what your AI agent did"));
14241
+ console.log("");
14242
+ const historyPath = import_path35.default.join(import_os28.default.homedir(), ".claude", "history.jsonl");
14243
+ if (!import_fs32.default.existsSync(historyPath)) {
14244
+ console.log(import_chalk22.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
14245
+ console.log(import_chalk22.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
14246
+ return;
14247
+ }
14248
+ const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
14249
+ const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
14250
+ console.log(import_chalk22.default.dim(" " + rangeLabel));
14251
+ console.log("");
14252
+ process.stdout.write(import_chalk22.default.dim(" Loading\u2026"));
14253
+ const summaries = buildSessions(days);
14254
+ process.stdout.write("\r" + " ".repeat(20) + "\r");
14255
+ if (options.detail) {
14256
+ const target = summaries.find(
14257
+ (s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
14258
+ );
14259
+ if (!target) {
14260
+ console.log(import_chalk22.default.red(` Session not found: ${options.detail}`));
14261
+ console.log(import_chalk22.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
14262
+ return;
14263
+ }
14264
+ renderDetail(target);
14265
+ return;
14266
+ }
14267
+ const totalCost = summaries.reduce((sum, s) => sum + s.costUSD, 0);
14268
+ if (summaries.length > 0) renderSummary(summaries);
14269
+ renderList(summaries, totalCost);
14270
+ });
14271
+ }
14272
+
12515
14273
  // src/cli.ts
12516
14274
  var { version } = JSON.parse(
12517
- import_fs31.default.readFileSync(import_path34.default.join(__dirname, "../package.json"), "utf-8")
14275
+ import_fs35.default.readFileSync(import_path38.default.join(__dirname, "../package.json"), "utf-8")
12518
14276
  );
12519
14277
  var program = new import_commander.Command();
12520
14278
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
12521
14279
  program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
12522
- const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
12523
- const credPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "credentials.json");
12524
- if (!import_fs31.default.existsSync(import_path34.default.dirname(credPath)))
12525
- import_fs31.default.mkdirSync(import_path34.default.dirname(credPath), { recursive: true });
14280
+ const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
14281
+ const credPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "credentials.json");
14282
+ if (!import_fs35.default.existsSync(import_path38.default.dirname(credPath)))
14283
+ import_fs35.default.mkdirSync(import_path38.default.dirname(credPath), { recursive: true });
12526
14284
  const profileName = options.profile || "default";
12527
14285
  let existingCreds = {};
12528
14286
  try {
12529
- if (import_fs31.default.existsSync(credPath)) {
12530
- const raw = JSON.parse(import_fs31.default.readFileSync(credPath, "utf-8"));
14287
+ if (import_fs35.default.existsSync(credPath)) {
14288
+ const raw = JSON.parse(import_fs35.default.readFileSync(credPath, "utf-8"));
12531
14289
  if (raw.apiKey) {
12532
14290
  existingCreds = {
12533
- default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
14291
+ default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
12534
14292
  };
12535
14293
  } else {
12536
14294
  existingCreds = raw;
@@ -12538,14 +14296,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12538
14296
  }
12539
14297
  } catch {
12540
14298
  }
12541
- existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
12542
- import_fs31.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
14299
+ existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
14300
+ import_fs35.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
12543
14301
  if (profileName === "default") {
12544
- const configPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
14302
+ const configPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "config.json");
12545
14303
  let config = {};
12546
14304
  try {
12547
- if (import_fs31.default.existsSync(configPath))
12548
- config = JSON.parse(import_fs31.default.readFileSync(configPath, "utf-8"));
14305
+ if (import_fs35.default.existsSync(configPath))
14306
+ config = JSON.parse(import_fs35.default.readFileSync(configPath, "utf-8"));
12549
14307
  } catch {
12550
14308
  }
12551
14309
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -12560,39 +14318,47 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
12560
14318
  approvers.cloud = false;
12561
14319
  }
12562
14320
  s.approvers = approvers;
12563
- if (!import_fs31.default.existsSync(import_path34.default.dirname(configPath)))
12564
- import_fs31.default.mkdirSync(import_path34.default.dirname(configPath), { recursive: true });
12565
- import_fs31.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
14321
+ if (!import_fs35.default.existsSync(import_path38.default.dirname(configPath)))
14322
+ import_fs35.default.mkdirSync(import_path38.default.dirname(configPath), { recursive: true });
14323
+ import_fs35.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
12566
14324
  }
12567
14325
  if (options.profile && profileName !== "default") {
12568
- console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
12569
- console.log(import_chalk20.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
14326
+ console.log(import_chalk24.default.green(`\u2705 Profile "${profileName}" saved`));
14327
+ console.log(import_chalk24.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
12570
14328
  } else if (options.local) {
12571
- console.log(import_chalk20.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
12572
- console.log(import_chalk20.default.gray(` All decisions stay on this machine.`));
14329
+ console.log(import_chalk24.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
14330
+ console.log(import_chalk24.default.gray(` All decisions stay on this machine.`));
12573
14331
  } else {
12574
- console.log(import_chalk20.default.green(`\u2705 Logged in \u2014 agent mode`));
12575
- console.log(import_chalk20.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
14332
+ console.log(import_chalk24.default.green(`\u2705 Logged in \u2014 agent mode`));
14333
+ console.log(import_chalk24.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
12576
14334
  }
12577
14335
  });
12578
- program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
14336
+ program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("<target>", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
12579
14337
  if (target === "gemini") return await setupGemini();
12580
14338
  if (target === "claude") return await setupClaude();
12581
14339
  if (target === "cursor") return await setupCursor();
14340
+ if (target === "windsurf") return await setupWindsurf();
14341
+ if (target === "vscode") return await setupVSCode();
12582
14342
  if (target === "hud") return setupHud();
12583
- console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
14343
+ console.error(
14344
+ import_chalk24.default.red(
14345
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14346
+ )
14347
+ );
12584
14348
  process.exit(1);
12585
14349
  });
12586
- program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
14350
+ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument("[target]", "The agent to protect: claude | gemini | cursor | windsurf | vscode | hud").action(async (target) => {
12587
14351
  if (!target) {
12588
- console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
12589
- console.log(" Usage: " + import_chalk20.default.white("node9 setup <target>") + "\n");
14352
+ console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
14353
+ console.log(" Usage: " + import_chalk24.default.white("node9 setup <target>") + "\n");
12590
14354
  console.log(" Targets:");
12591
- console.log(" " + import_chalk20.default.green("claude") + " \u2014 Claude Code (hook mode)");
12592
- console.log(" " + import_chalk20.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
12593
- console.log(" " + import_chalk20.default.green("cursor") + " \u2014 Cursor (hook mode)");
14355
+ console.log(" " + import_chalk24.default.green("claude") + " \u2014 Claude Code (hook mode)");
14356
+ console.log(" " + import_chalk24.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
14357
+ console.log(" " + import_chalk24.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
14358
+ console.log(" " + import_chalk24.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
14359
+ console.log(" " + import_chalk24.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
12594
14360
  process.stdout.write(
12595
- " " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
14361
+ " " + import_chalk24.default.green("hud") + " \u2014 Claude Code security statusline\n"
12596
14362
  );
12597
14363
  console.log("");
12598
14364
  return;
@@ -12601,93 +14367,108 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
12601
14367
  if (t === "gemini") return await setupGemini();
12602
14368
  if (t === "claude") return await setupClaude();
12603
14369
  if (t === "cursor") return await setupCursor();
14370
+ if (t === "windsurf") return await setupWindsurf();
14371
+ if (t === "vscode") return await setupVSCode();
12604
14372
  if (t === "hud") return setupHud();
12605
- console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
14373
+ console.error(
14374
+ import_chalk24.default.red(
14375
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14376
+ )
14377
+ );
12606
14378
  process.exit(1);
12607
14379
  });
12608
- program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
14380
+ program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor windsurf vscode hud").argument(
14381
+ "<target>",
14382
+ "The agent to remove from: claude | gemini | cursor | windsurf | vscode | hud"
14383
+ ).action((target) => {
12609
14384
  let fn;
12610
14385
  if (target === "claude") fn = teardownClaude;
12611
14386
  else if (target === "gemini") fn = teardownGemini;
12612
14387
  else if (target === "cursor") fn = teardownCursor;
14388
+ else if (target === "windsurf") fn = teardownWindsurf;
14389
+ else if (target === "vscode") fn = teardownVSCode;
12613
14390
  else if (target === "hud") fn = teardownHud;
12614
14391
  else {
12615
14392
  console.error(
12616
- import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
14393
+ import_chalk24.default.red(
14394
+ `Unknown target: "${target}". Supported: claude, gemini, cursor, windsurf, vscode, hud`
14395
+ )
12617
14396
  );
12618
14397
  process.exit(1);
12619
14398
  }
12620
- console.log(import_chalk20.default.cyan(`
14399
+ console.log(import_chalk24.default.cyan(`
12621
14400
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
12622
14401
  `));
12623
14402
  try {
12624
14403
  fn();
12625
14404
  } catch (err2) {
12626
- console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
14405
+ console.error(import_chalk24.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
12627
14406
  process.exit(1);
12628
14407
  }
12629
- console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
14408
+ console.log(import_chalk24.default.gray("\n Restart the agent for changes to take effect."));
12630
14409
  });
12631
14410
  program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
12632
- console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
12633
- console.log(import_chalk20.default.bold("Stopping daemon..."));
14411
+ console.log(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
14412
+ console.log(import_chalk24.default.bold("Stopping daemon..."));
12634
14413
  try {
12635
14414
  stopDaemon();
12636
- console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
14415
+ console.log(import_chalk24.default.green(" \u2705 Daemon stopped"));
12637
14416
  } catch {
12638
- console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
14417
+ console.log(import_chalk24.default.blue(" \u2139\uFE0F Daemon was not running"));
12639
14418
  }
12640
- console.log(import_chalk20.default.bold("\nRemoving hooks..."));
14419
+ console.log(import_chalk24.default.bold("\nRemoving hooks..."));
12641
14420
  let teardownFailed = false;
12642
14421
  for (const [label, fn] of [
12643
14422
  ["Claude", teardownClaude],
12644
14423
  ["Gemini", teardownGemini],
12645
- ["Cursor", teardownCursor]
14424
+ ["Cursor", teardownCursor],
14425
+ ["Windsurf", teardownWindsurf],
14426
+ ["VSCode", teardownVSCode]
12646
14427
  ]) {
12647
14428
  try {
12648
14429
  fn();
12649
14430
  } catch (err2) {
12650
14431
  teardownFailed = true;
12651
14432
  console.error(
12652
- import_chalk20.default.red(
14433
+ import_chalk24.default.red(
12653
14434
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
12654
14435
  )
12655
14436
  );
12656
14437
  }
12657
14438
  }
12658
14439
  if (options.purge) {
12659
- const node9Dir = import_path34.default.join(import_os27.default.homedir(), ".node9");
12660
- if (import_fs31.default.existsSync(node9Dir)) {
14440
+ const node9Dir = import_path38.default.join(import_os31.default.homedir(), ".node9");
14441
+ if (import_fs35.default.existsSync(node9Dir)) {
12661
14442
  const confirmed = await (0, import_prompts2.confirm)({
12662
14443
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
12663
14444
  default: false
12664
14445
  });
12665
14446
  if (confirmed) {
12666
- import_fs31.default.rmSync(node9Dir, { recursive: true });
12667
- if (import_fs31.default.existsSync(node9Dir)) {
14447
+ import_fs35.default.rmSync(node9Dir, { recursive: true });
14448
+ if (import_fs35.default.existsSync(node9Dir)) {
12668
14449
  console.error(
12669
- import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
14450
+ import_chalk24.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
12670
14451
  );
12671
14452
  } else {
12672
- console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
14453
+ console.log(import_chalk24.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
12673
14454
  }
12674
14455
  } else {
12675
- console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
14456
+ console.log(import_chalk24.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
12676
14457
  }
12677
14458
  } else {
12678
- console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
14459
+ console.log(import_chalk24.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
12679
14460
  }
12680
14461
  } else {
12681
14462
  console.log(
12682
- import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
14463
+ import_chalk24.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
12683
14464
  );
12684
14465
  }
12685
14466
  if (teardownFailed) {
12686
- console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
14467
+ console.error(import_chalk24.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
12687
14468
  process.exit(1);
12688
14469
  }
12689
- console.log(import_chalk20.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
12690
- console.log(import_chalk20.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
14470
+ console.log(import_chalk24.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
14471
+ console.log(import_chalk24.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
12691
14472
  });
12692
14473
  registerDoctorCommand(program, version);
12693
14474
  program.command("explain").description(
@@ -12700,7 +14481,7 @@ program.command("explain").description(
12700
14481
  try {
12701
14482
  args = JSON.parse(trimmed);
12702
14483
  } catch {
12703
- console.error(import_chalk20.default.red(`
14484
+ console.error(import_chalk24.default.red(`
12704
14485
  \u274C Invalid JSON: ${trimmed}
12705
14486
  `));
12706
14487
  process.exit(1);
@@ -12711,54 +14492,54 @@ program.command("explain").description(
12711
14492
  }
12712
14493
  const result = await explainPolicy(tool, args);
12713
14494
  console.log("");
12714
- console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
14495
+ console.log(import_chalk24.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
12715
14496
  console.log("");
12716
- console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
14497
+ console.log(` ${import_chalk24.default.bold("Tool:")} ${import_chalk24.default.white(result.tool)}`);
12717
14498
  if (argsRaw) {
12718
- const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
12719
- console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
14499
+ const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
14500
+ console.log(` ${import_chalk24.default.bold("Input:")} ${import_chalk24.default.gray(preview2)}`);
12720
14501
  }
12721
14502
  console.log("");
12722
- console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
14503
+ console.log(import_chalk24.default.bold("Config Sources (Waterfall):"));
12723
14504
  for (const tier of result.waterfall) {
12724
- const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
14505
+ const num3 = import_chalk24.default.gray(` ${tier.tier}.`);
12725
14506
  const label = tier.label.padEnd(16);
12726
14507
  let statusStr;
12727
14508
  if (tier.tier === 1) {
12728
- statusStr = import_chalk20.default.gray(tier.note ?? "");
14509
+ statusStr = import_chalk24.default.gray(tier.note ?? "");
12729
14510
  } else if (tier.status === "active") {
12730
- const loc = tier.path ? import_chalk20.default.gray(tier.path) : "";
12731
- const note = tier.note ? import_chalk20.default.gray(`(${tier.note})`) : "";
12732
- statusStr = import_chalk20.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
14511
+ const loc = tier.path ? import_chalk24.default.gray(tier.path) : "";
14512
+ const note = tier.note ? import_chalk24.default.gray(`(${tier.note})`) : "";
14513
+ statusStr = import_chalk24.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
12733
14514
  } else {
12734
- statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
14515
+ statusStr = import_chalk24.default.gray("\u25CB " + (tier.note ?? "not found"));
12735
14516
  }
12736
- console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
14517
+ console.log(`${num3} ${import_chalk24.default.white(label)} ${statusStr}`);
12737
14518
  }
12738
14519
  console.log("");
12739
- console.log(import_chalk20.default.bold("Policy Evaluation:"));
14520
+ console.log(import_chalk24.default.bold("Policy Evaluation:"));
12740
14521
  for (const step of result.steps) {
12741
14522
  const isFinal = step.isFinal;
12742
14523
  let icon;
12743
- if (step.outcome === "allow") icon = import_chalk20.default.green(" \u2705");
12744
- else if (step.outcome === "review") icon = import_chalk20.default.red(" \u{1F534}");
12745
- else if (step.outcome === "skip") icon = import_chalk20.default.gray(" \u2500 ");
12746
- else icon = import_chalk20.default.gray(" \u25CB ");
14524
+ if (step.outcome === "allow") icon = import_chalk24.default.green(" \u2705");
14525
+ else if (step.outcome === "review") icon = import_chalk24.default.red(" \u{1F534}");
14526
+ else if (step.outcome === "skip") icon = import_chalk24.default.gray(" \u2500 ");
14527
+ else icon = import_chalk24.default.gray(" \u25CB ");
12747
14528
  const name = step.name.padEnd(18);
12748
- const nameStr = isFinal ? import_chalk20.default.white.bold(name) : import_chalk20.default.white(name);
12749
- const detail = isFinal ? import_chalk20.default.white(step.detail) : import_chalk20.default.gray(step.detail);
12750
- const arrow = isFinal ? import_chalk20.default.yellow(" \u2190 STOP") : "";
14529
+ const nameStr = isFinal ? import_chalk24.default.white.bold(name) : import_chalk24.default.white(name);
14530
+ const detail = isFinal ? import_chalk24.default.white(step.detail) : import_chalk24.default.gray(step.detail);
14531
+ const arrow = isFinal ? import_chalk24.default.yellow(" \u2190 STOP") : "";
12751
14532
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
12752
14533
  }
12753
14534
  console.log("");
12754
14535
  if (result.decision === "allow") {
12755
- console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
14536
+ console.log(import_chalk24.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk24.default.gray(" \u2014 no approval needed"));
12756
14537
  } else {
12757
14538
  console.log(
12758
- import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
14539
+ import_chalk24.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk24.default.gray(" \u2014 human approval required")
12759
14540
  );
12760
14541
  if (result.blockedByLabel) {
12761
- console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
14542
+ console.log(import_chalk24.default.gray(` Reason: ${result.blockedByLabel}`));
12762
14543
  }
12763
14544
  }
12764
14545
  console.log("");
@@ -12773,7 +14554,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
12773
14554
  try {
12774
14555
  await startTail2(options);
12775
14556
  } catch (err2) {
12776
- console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
14557
+ console.error(import_chalk24.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
12777
14558
  process.exit(1);
12778
14559
  }
12779
14560
  });
@@ -12805,14 +14586,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
12805
14586
  Run "node9 addto claude" to register it as the statusLine.`
12806
14587
  ).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
12807
14588
  if (subcommand === "debug") {
12808
- const flagFile = import_path34.default.join(import_os27.default.homedir(), ".node9", "hud-debug");
14589
+ const flagFile = import_path38.default.join(import_os31.default.homedir(), ".node9", "hud-debug");
12809
14590
  if (state === "on") {
12810
- import_fs31.default.mkdirSync(import_path34.default.dirname(flagFile), { recursive: true });
12811
- import_fs31.default.writeFileSync(flagFile, "");
14591
+ import_fs35.default.mkdirSync(import_path38.default.dirname(flagFile), { recursive: true });
14592
+ import_fs35.default.writeFileSync(flagFile, "");
12812
14593
  console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
12813
14594
  console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
12814
14595
  } else if (state === "off") {
12815
- if (import_fs31.default.existsSync(flagFile)) import_fs31.default.unlinkSync(flagFile);
14596
+ if (import_fs35.default.existsSync(flagFile)) import_fs35.default.unlinkSync(flagFile);
12816
14597
  console.log("HUD debug logging disabled.");
12817
14598
  } else {
12818
14599
  console.error("Usage: node9 hud debug on|off");
@@ -12827,7 +14608,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12827
14608
  const ms = parseDuration(options.duration);
12828
14609
  if (ms === null) {
12829
14610
  console.error(
12830
- import_chalk20.default.red(`
14611
+ import_chalk24.default.red(`
12831
14612
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
12832
14613
  `)
12833
14614
  );
@@ -12835,20 +14616,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
12835
14616
  }
12836
14617
  pauseNode9(ms, options.duration);
12837
14618
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
12838
- console.log(import_chalk20.default.yellow(`
14619
+ console.log(import_chalk24.default.yellow(`
12839
14620
  \u23F8 Node9 paused until ${expiresAt}`));
12840
- console.log(import_chalk20.default.gray(` All tool calls will be allowed without review.`));
12841
- console.log(import_chalk20.default.gray(` Run "node9 resume" to re-enable early.
14621
+ console.log(import_chalk24.default.gray(` All tool calls will be allowed without review.`));
14622
+ console.log(import_chalk24.default.gray(` Run "node9 resume" to re-enable early.
12842
14623
  `));
12843
14624
  });
12844
14625
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
12845
14626
  const { paused } = checkPause();
12846
14627
  if (!paused) {
12847
- console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
14628
+ console.log(import_chalk24.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
12848
14629
  return;
12849
14630
  }
12850
14631
  resumeNode9();
12851
- console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
14632
+ console.log(import_chalk24.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
12852
14633
  });
12853
14634
  var HOOK_BASED_AGENTS = {
12854
14635
  claude: "claude",
@@ -12861,15 +14642,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12861
14642
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
12862
14643
  const target = HOOK_BASED_AGENTS[firstArg2];
12863
14644
  console.error(
12864
- import_chalk20.default.yellow(`
14645
+ import_chalk24.default.yellow(`
12865
14646
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
12866
14647
  );
12867
- console.error(import_chalk20.default.white(`
14648
+ console.error(import_chalk24.default.white(`
12868
14649
  "${target}" uses its own hook system. Use:`));
12869
14650
  console.error(
12870
- import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
14651
+ import_chalk24.default.green(` node9 addto ${target} `) + import_chalk24.default.gray("# one-time setup")
12871
14652
  );
12872
- console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
14653
+ console.error(import_chalk24.default.green(` ${target} `) + import_chalk24.default.gray("# run normally"));
12873
14654
  process.exit(1);
12874
14655
  }
12875
14656
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -12886,7 +14667,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12886
14667
  }
12887
14668
  );
12888
14669
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
12889
- console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
14670
+ console.error(import_chalk24.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
12890
14671
  const daemonReady = await autoStartDaemonAndWait();
12891
14672
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
12892
14673
  }
@@ -12899,12 +14680,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
12899
14680
  }
12900
14681
  if (!result.approved) {
12901
14682
  console.error(
12902
- import_chalk20.default.red(`
14683
+ import_chalk24.default.red(`
12903
14684
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
12904
14685
  );
12905
14686
  process.exit(1);
12906
14687
  }
12907
- console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
14688
+ console.error(import_chalk24.default.green("\n\u2705 Approved \u2014 running command...\n"));
12908
14689
  await runProxy(fullCommand);
12909
14690
  } else {
12910
14691
  program.help();
@@ -12914,14 +14695,18 @@ registerUndoCommand(program);
12914
14695
  registerShieldCommand(program);
12915
14696
  registerConfigShowCommand(program);
12916
14697
  registerTrustCommand(program);
14698
+ registerSyncCommand(program);
14699
+ registerAgentsCommand(program);
14700
+ registerScanCommand(program);
14701
+ registerSessionsCommand(program);
12917
14702
  if (process.argv[2] !== "daemon") {
12918
14703
  process.on("unhandledRejection", (reason) => {
12919
14704
  const isCheckHook = process.argv[2] === "check";
12920
14705
  if (isCheckHook) {
12921
14706
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
12922
- const logPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "hook-debug.log");
14707
+ const logPath = import_path38.default.join(import_os31.default.homedir(), ".node9", "hook-debug.log");
12923
14708
  const msg = reason instanceof Error ? reason.message : String(reason);
12924
- import_fs31.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
14709
+ import_fs35.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
12925
14710
  `);
12926
14711
  }
12927
14712
  process.exit(0);