@node9/proxy 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -94,8 +94,8 @@ function sanitizeConfig(raw) {
94
94
  }
95
95
  }
96
96
  const lines = result.error.issues.map((issue) => {
97
- const path24 = issue.path.length > 0 ? issue.path.join(".") : "root";
98
- return ` \u2022 ${path24}: ${issue.message}`;
97
+ const path25 = issue.path.length > 0 ? issue.path.join(".") : "root";
98
+ return ` \u2022 ${path25}: ${issue.message}`;
99
99
  });
100
100
  return {
101
101
  sanitized,
@@ -1589,10 +1589,66 @@ var init_ssh_parser = __esm({
1589
1589
  }
1590
1590
  });
1591
1591
 
1592
- // src/policy/index.ts
1592
+ // src/auth/trusted-hosts.ts
1593
1593
  import fs6 from "fs";
1594
1594
  import path7 from "path";
1595
1595
  import os5 from "os";
1596
+ function getTrustedHostsPath() {
1597
+ return path7.join(os5.homedir(), ".node9", "trusted-hosts.json");
1598
+ }
1599
+ function readTrustedHosts() {
1600
+ try {
1601
+ const raw = fs6.readFileSync(getTrustedHostsPath(), "utf8");
1602
+ const parsed = JSON.parse(raw);
1603
+ return Array.isArray(parsed.hosts) ? parsed.hosts : [];
1604
+ } catch {
1605
+ return [];
1606
+ }
1607
+ }
1608
+ function writeTrustedHosts(hosts) {
1609
+ const filePath = getTrustedHostsPath();
1610
+ fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
1611
+ const tmp = filePath + ".node9-tmp";
1612
+ fs6.writeFileSync(tmp, JSON.stringify({ hosts }, null, 2));
1613
+ fs6.renameSync(tmp, filePath);
1614
+ }
1615
+ function addTrustedHost(host) {
1616
+ const hosts = readTrustedHosts();
1617
+ if (hosts.some((h) => h.host === host)) return;
1618
+ hosts.push({ host, addedAt: Date.now(), addedBy: "user" });
1619
+ writeTrustedHosts(hosts);
1620
+ }
1621
+ function removeTrustedHost(host) {
1622
+ const hosts = readTrustedHosts();
1623
+ const filtered = hosts.filter((h) => h.host !== host);
1624
+ if (filtered.length === hosts.length) return false;
1625
+ writeTrustedHosts(filtered);
1626
+ return true;
1627
+ }
1628
+ function normalizeHost(raw) {
1629
+ return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
1630
+ }
1631
+ function isTrustedHost(host) {
1632
+ const normalized = normalizeHost(host);
1633
+ return readTrustedHosts().some((entry) => {
1634
+ const entryHost = entry.host.toLowerCase();
1635
+ if (entryHost.startsWith("*.")) {
1636
+ const domain = entryHost.slice(2);
1637
+ return normalized === domain || normalized.endsWith("." + domain);
1638
+ }
1639
+ return normalized === entryHost;
1640
+ });
1641
+ }
1642
+ var init_trusted_hosts = __esm({
1643
+ "src/auth/trusted-hosts.ts"() {
1644
+ "use strict";
1645
+ }
1646
+ });
1647
+
1648
+ // src/policy/index.ts
1649
+ import fs7 from "fs";
1650
+ import path8 from "path";
1651
+ import os6 from "os";
1596
1652
  import pm from "picomatch";
1597
1653
  import { parse } from "sh-syntax";
1598
1654
  function tokenize2(toolName) {
@@ -1608,9 +1664,9 @@ function matchesPattern(text, patterns) {
1608
1664
  const withoutDotSlash = text.replace(/^\.\//, "");
1609
1665
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1610
1666
  }
1611
- function getNestedValue(obj, path24) {
1667
+ function getNestedValue(obj, path25) {
1612
1668
  if (!obj || typeof obj !== "object") return null;
1613
- return path24.split(".").reduce((prev, curr) => prev?.[curr], obj);
1669
+ return path25.split(".").reduce((prev, curr) => prev?.[curr], obj);
1614
1670
  }
1615
1671
  function shouldSnapshot(toolName, args, config) {
1616
1672
  if (!config.settings.enableUndo) return false;
@@ -1776,23 +1832,34 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1776
1832
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1777
1833
  }
1778
1834
  const pipeAnalysis = analyzePipeChain(shellCommand);
1779
- if (pipeAnalysis.isPipeline) {
1835
+ if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
1836
+ const sinks = pipeAnalysis.sinkTargets;
1837
+ const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
1780
1838
  if (pipeAnalysis.risk === "critical") {
1839
+ if (allTrusted) {
1840
+ return {
1841
+ decision: "review",
1842
+ blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
1843
+ reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
1844
+ tier: 3
1845
+ };
1846
+ }
1781
1847
  return {
1782
1848
  decision: "block",
1783
1849
  blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1784
- reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1850
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1785
1851
  tier: 3
1786
1852
  };
1787
1853
  }
1788
- if (pipeAnalysis.risk === "high") {
1789
- return {
1790
- decision: "review",
1791
- blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1792
- reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${pipeAnalysis.sinkTargets.join(", ")}`,
1793
- tier: 3
1794
- };
1854
+ if (allTrusted) {
1855
+ return { decision: "allow" };
1795
1856
  }
1857
+ return {
1858
+ decision: "review",
1859
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
1860
+ reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1861
+ tier: 3
1862
+ };
1796
1863
  }
1797
1864
  const firstToken = analyzed.actions[0] ?? "";
1798
1865
  if (["ssh", "scp", "rsync"].includes(firstToken)) {
@@ -1800,7 +1867,7 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1800
1867
  const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1801
1868
  allTokens.push(...sshHosts);
1802
1869
  }
1803
- if (firstToken && path7.posix.isAbsolute(firstToken)) {
1870
+ if (firstToken && path8.posix.isAbsolute(firstToken)) {
1804
1871
  const prov = checkProvenance(firstToken, cwd);
1805
1872
  if (prov.trustLevel === "suspect") {
1806
1873
  return {
@@ -1897,9 +1964,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1897
1964
  }
1898
1965
  async function explainPolicy(toolName, args) {
1899
1966
  const steps = [];
1900
- const globalPath = path7.join(os5.homedir(), ".node9", "config.json");
1901
- const projectPath = path7.join(process.cwd(), "node9.config.json");
1902
- const credsPath = path7.join(os5.homedir(), ".node9", "credentials.json");
1967
+ const globalPath = path8.join(os6.homedir(), ".node9", "config.json");
1968
+ const projectPath = path8.join(process.cwd(), "node9.config.json");
1969
+ const credsPath = path8.join(os6.homedir(), ".node9", "credentials.json");
1903
1970
  const waterfall = [
1904
1971
  {
1905
1972
  tier: 1,
@@ -1910,19 +1977,19 @@ async function explainPolicy(toolName, args) {
1910
1977
  {
1911
1978
  tier: 2,
1912
1979
  label: "Cloud policy",
1913
- status: fs6.existsSync(credsPath) ? "active" : "missing",
1914
- note: fs6.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
1980
+ status: fs7.existsSync(credsPath) ? "active" : "missing",
1981
+ note: fs7.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
1915
1982
  },
1916
1983
  {
1917
1984
  tier: 3,
1918
1985
  label: "Project config",
1919
- status: fs6.existsSync(projectPath) ? "active" : "missing",
1986
+ status: fs7.existsSync(projectPath) ? "active" : "missing",
1920
1987
  path: projectPath
1921
1988
  },
1922
1989
  {
1923
1990
  tier: 4,
1924
1991
  label: "Global config",
1925
- status: fs6.existsSync(globalPath) ? "active" : "missing",
1992
+ status: fs7.existsSync(globalPath) ? "active" : "missing",
1926
1993
  path: globalPath
1927
1994
  },
1928
1995
  {
@@ -2169,21 +2236,22 @@ var init_policy = __esm({
2169
2236
  init_provenance();
2170
2237
  init_pipe_chain();
2171
2238
  init_ssh_parser();
2239
+ init_trusted_hosts();
2172
2240
  SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
2173
2241
  }
2174
2242
  });
2175
2243
 
2176
2244
  // src/auth/state.ts
2177
- import fs7 from "fs";
2178
- import path8 from "path";
2179
- import os6 from "os";
2245
+ import fs8 from "fs";
2246
+ import path9 from "path";
2247
+ import os7 from "os";
2180
2248
  function checkPause() {
2181
2249
  try {
2182
- if (!fs7.existsSync(PAUSED_FILE)) return { paused: false };
2183
- const state = JSON.parse(fs7.readFileSync(PAUSED_FILE, "utf-8"));
2250
+ if (!fs8.existsSync(PAUSED_FILE)) return { paused: false };
2251
+ const state = JSON.parse(fs8.readFileSync(PAUSED_FILE, "utf-8"));
2184
2252
  if (state.expiry > 0 && Date.now() >= state.expiry) {
2185
2253
  try {
2186
- fs7.unlinkSync(PAUSED_FILE);
2254
+ fs8.unlinkSync(PAUSED_FILE);
2187
2255
  } catch {
2188
2256
  }
2189
2257
  return { paused: false };
@@ -2194,11 +2262,11 @@ function checkPause() {
2194
2262
  }
2195
2263
  }
2196
2264
  function atomicWriteSync(filePath, data, options) {
2197
- const dir = path8.dirname(filePath);
2198
- if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2199
- const tmpPath = `${filePath}.${os6.hostname()}.${process.pid}.tmp`;
2200
- fs7.writeFileSync(tmpPath, data, options);
2201
- fs7.renameSync(tmpPath, filePath);
2265
+ const dir = path9.dirname(filePath);
2266
+ if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
2267
+ const tmpPath = `${filePath}.${os7.hostname()}.${process.pid}.tmp`;
2268
+ fs8.writeFileSync(tmpPath, data, options);
2269
+ fs8.renameSync(tmpPath, filePath);
2202
2270
  }
2203
2271
  function pauseNode9(durationMs, durationStr) {
2204
2272
  const state = { expiry: Date.now() + durationMs, duration: durationStr };
@@ -2206,18 +2274,18 @@ function pauseNode9(durationMs, durationStr) {
2206
2274
  }
2207
2275
  function resumeNode9() {
2208
2276
  try {
2209
- if (fs7.existsSync(PAUSED_FILE)) fs7.unlinkSync(PAUSED_FILE);
2277
+ if (fs8.existsSync(PAUSED_FILE)) fs8.unlinkSync(PAUSED_FILE);
2210
2278
  } catch {
2211
2279
  }
2212
2280
  }
2213
2281
  function getActiveTrustSession(toolName) {
2214
2282
  try {
2215
- if (!fs7.existsSync(TRUST_FILE)) return false;
2216
- const trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
2283
+ if (!fs8.existsSync(TRUST_FILE)) return false;
2284
+ const trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
2217
2285
  const now = Date.now();
2218
2286
  const active = trust.entries.filter((e) => e.expiry > now);
2219
2287
  if (active.length !== trust.entries.length) {
2220
- fs7.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
2288
+ fs8.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
2221
2289
  }
2222
2290
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
2223
2291
  } catch {
@@ -2228,8 +2296,8 @@ function writeTrustSession(toolName, durationMs) {
2228
2296
  try {
2229
2297
  let trust = { entries: [] };
2230
2298
  try {
2231
- if (fs7.existsSync(TRUST_FILE)) {
2232
- trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
2299
+ if (fs8.existsSync(TRUST_FILE)) {
2300
+ trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
2233
2301
  }
2234
2302
  } catch {
2235
2303
  }
@@ -2245,9 +2313,9 @@ function writeTrustSession(toolName, durationMs) {
2245
2313
  }
2246
2314
  function getPersistentDecision(toolName) {
2247
2315
  try {
2248
- const file = path8.join(os6.homedir(), ".node9", "decisions.json");
2249
- if (!fs7.existsSync(file)) return null;
2250
- const decisions = JSON.parse(fs7.readFileSync(file, "utf-8"));
2316
+ const file = path9.join(os7.homedir(), ".node9", "decisions.json");
2317
+ if (!fs8.existsSync(file)) return null;
2318
+ const decisions = JSON.parse(fs8.readFileSync(file, "utf-8"));
2251
2319
  const d = decisions[toolName];
2252
2320
  if (d === "allow" || d === "deny") return d;
2253
2321
  } catch {
@@ -2259,21 +2327,21 @@ var init_state = __esm({
2259
2327
  "src/auth/state.ts"() {
2260
2328
  "use strict";
2261
2329
  init_policy();
2262
- PAUSED_FILE = path8.join(os6.homedir(), ".node9", "PAUSED");
2263
- TRUST_FILE = path8.join(os6.homedir(), ".node9", "trust.json");
2330
+ PAUSED_FILE = path9.join(os7.homedir(), ".node9", "PAUSED");
2331
+ TRUST_FILE = path9.join(os7.homedir(), ".node9", "trust.json");
2264
2332
  }
2265
2333
  });
2266
2334
 
2267
2335
  // src/auth/daemon.ts
2268
- import fs8 from "fs";
2269
- import path9 from "path";
2270
- import os7 from "os";
2336
+ import fs9 from "fs";
2337
+ import path10 from "path";
2338
+ import os8 from "os";
2271
2339
  import { spawnSync } from "child_process";
2272
2340
  function getInternalToken() {
2273
2341
  try {
2274
- const pidFile = path9.join(os7.homedir(), ".node9", "daemon.pid");
2275
- if (!fs8.existsSync(pidFile)) return null;
2276
- const data = JSON.parse(fs8.readFileSync(pidFile, "utf-8"));
2342
+ const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
2343
+ if (!fs9.existsSync(pidFile)) return null;
2344
+ const data = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
2277
2345
  process.kill(data.pid, 0);
2278
2346
  return data.internalToken ?? null;
2279
2347
  } catch {
@@ -2281,10 +2349,10 @@ function getInternalToken() {
2281
2349
  }
2282
2350
  }
2283
2351
  function isDaemonRunning() {
2284
- const pidFile = path9.join(os7.homedir(), ".node9", "daemon.pid");
2285
- if (fs8.existsSync(pidFile)) {
2352
+ const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
2353
+ if (fs9.existsSync(pidFile)) {
2286
2354
  try {
2287
- const { pid, port } = JSON.parse(fs8.readFileSync(pidFile, "utf-8"));
2355
+ const { pid, port } = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
2288
2356
  if (port !== DAEMON_PORT) return false;
2289
2357
  process.kill(pid, 0);
2290
2358
  return true;
@@ -2387,7 +2455,7 @@ var init_daemon = __esm({
2387
2455
  });
2388
2456
 
2389
2457
  // src/context-sniper.ts
2390
- import path10 from "path";
2458
+ import path11 from "path";
2391
2459
  function smartTruncate(str, maxLen = 500) {
2392
2460
  if (str.length <= maxLen) return str;
2393
2461
  const edge = Math.floor(maxLen / 2) - 3;
@@ -2439,7 +2507,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
2439
2507
  intent = "EDIT";
2440
2508
  if (obj.file_path) {
2441
2509
  editFilePath = String(obj.file_path);
2442
- editFileName = path10.basename(editFilePath);
2510
+ editFileName = path11.basename(editFilePath);
2443
2511
  }
2444
2512
  const result = extractContext(String(obj.new_string), matchedWord);
2445
2513
  contextSnippet = result.snippet;
@@ -2496,7 +2564,7 @@ var init_context_sniper = __esm({
2496
2564
 
2497
2565
  // src/ui/native.ts
2498
2566
  import { spawn } from "child_process";
2499
- import path11 from "path";
2567
+ import path12 from "path";
2500
2568
  function formatArgs(args, matchedField, matchedWord) {
2501
2569
  if (args === null || args === void 0) return { message: "(none)", intent: "EXEC" };
2502
2570
  let parsed = args;
@@ -2515,7 +2583,7 @@ function formatArgs(args, matchedField, matchedWord) {
2515
2583
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
2516
2584
  const obj = parsed;
2517
2585
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
2518
- const file = obj.file_path ? path11.basename(String(obj.file_path)) : "file";
2586
+ const file = obj.file_path ? path12.basename(String(obj.file_path)) : "file";
2519
2587
  const oldPreview = smartTruncate(String(obj.old_string), 120);
2520
2588
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
2521
2589
  return {
@@ -2700,8 +2768,8 @@ var init_native = __esm({
2700
2768
  });
2701
2769
 
2702
2770
  // src/auth/cloud.ts
2703
- import fs9 from "fs";
2704
- import os8 from "os";
2771
+ import fs10 from "fs";
2772
+ import os9 from "os";
2705
2773
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2706
2774
  return fetch(`${creds.apiUrl}/audit`, {
2707
2775
  method: "POST",
@@ -2713,9 +2781,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2713
2781
  context: {
2714
2782
  agent: meta?.agent,
2715
2783
  mcpServer: meta?.mcpServer,
2716
- hostname: os8.hostname(),
2784
+ hostname: os9.hostname(),
2717
2785
  cwd: process.cwd(),
2718
- platform: os8.platform()
2786
+ platform: os9.platform()
2719
2787
  }
2720
2788
  }),
2721
2789
  signal: AbortSignal.timeout(5e3)
@@ -2736,9 +2804,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2736
2804
  context: {
2737
2805
  agent: meta?.agent,
2738
2806
  mcpServer: meta?.mcpServer,
2739
- hostname: os8.hostname(),
2807
+ hostname: os9.hostname(),
2740
2808
  cwd: process.cwd(),
2741
- platform: os8.platform()
2809
+ platform: os9.platform()
2742
2810
  },
2743
2811
  ...riskMetadata && { riskMetadata }
2744
2812
  }),
@@ -2794,14 +2862,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2794
2862
  });
2795
2863
  clearTimeout(timer);
2796
2864
  if (!res.ok) {
2797
- fs9.appendFileSync(
2865
+ fs10.appendFileSync(
2798
2866
  HOOK_DEBUG_LOG,
2799
2867
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
2800
2868
  `
2801
2869
  );
2802
2870
  }
2803
2871
  } catch (err) {
2804
- fs9.appendFileSync(
2872
+ fs10.appendFileSync(
2805
2873
  HOOK_DEBUG_LOG,
2806
2874
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
2807
2875
  `
@@ -2817,8 +2885,8 @@ var init_cloud = __esm({
2817
2885
 
2818
2886
  // src/auth/orchestrator.ts
2819
2887
  import net from "net";
2820
- import path12 from "path";
2821
- import os9 from "os";
2888
+ import path13 from "path";
2889
+ import os10 from "os";
2822
2890
  import { randomUUID } from "crypto";
2823
2891
  function notifyActivity(data) {
2824
2892
  return new Promise((resolve) => {
@@ -3179,7 +3247,7 @@ var init_orchestrator = __esm({
3179
3247
  init_state();
3180
3248
  init_daemon();
3181
3249
  init_cloud();
3182
- ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path12.join(os9.tmpdir(), "node9-activity.sock");
3250
+ ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path13.join(os10.tmpdir(), "node9-activity.sock");
3183
3251
  }
3184
3252
  });
3185
3253
 
@@ -4651,9 +4719,9 @@ var init_ui2 = __esm({
4651
4719
 
4652
4720
  // src/daemon/state.ts
4653
4721
  import net2 from "net";
4654
- import fs11 from "fs";
4655
- import path14 from "path";
4656
- import os11 from "os";
4722
+ import fs12 from "fs";
4723
+ import path15 from "path";
4724
+ import os12 from "os";
4657
4725
  import { spawn as spawn2 } from "child_process";
4658
4726
  import { randomUUID as randomUUID2 } from "crypto";
4659
4727
  function getAbandonTimer() {
@@ -4678,11 +4746,11 @@ function markRejectionHandlerRegistered() {
4678
4746
  daemonRejectionHandlerRegistered = true;
4679
4747
  }
4680
4748
  function atomicWriteSync2(filePath, data, options) {
4681
- const dir = path14.dirname(filePath);
4682
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
4749
+ const dir = path15.dirname(filePath);
4750
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
4683
4751
  const tmpPath = `${filePath}.${randomUUID2()}.tmp`;
4684
- fs11.writeFileSync(tmpPath, data, options);
4685
- fs11.renameSync(tmpPath, filePath);
4752
+ fs12.writeFileSync(tmpPath, data, options);
4753
+ fs12.renameSync(tmpPath, filePath);
4686
4754
  }
4687
4755
  function redactArgs(value) {
4688
4756
  if (!value || typeof value !== "object") return value;
@@ -4702,16 +4770,16 @@ function appendAuditLog(data) {
4702
4770
  decision: data.decision,
4703
4771
  source: "daemon"
4704
4772
  };
4705
- const dir = path14.dirname(AUDIT_LOG_FILE);
4706
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
4707
- fs11.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
4773
+ const dir = path15.dirname(AUDIT_LOG_FILE);
4774
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
4775
+ fs12.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
4708
4776
  } catch {
4709
4777
  }
4710
4778
  }
4711
4779
  function getAuditHistory(limit = 20) {
4712
4780
  try {
4713
- if (!fs11.existsSync(AUDIT_LOG_FILE)) return [];
4714
- const lines = fs11.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
4781
+ if (!fs12.existsSync(AUDIT_LOG_FILE)) return [];
4782
+ const lines = fs12.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
4715
4783
  if (lines.length === 1 && lines[0] === "") return [];
4716
4784
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
4717
4785
  } catch {
@@ -4720,19 +4788,19 @@ function getAuditHistory(limit = 20) {
4720
4788
  }
4721
4789
  function getOrgName() {
4722
4790
  try {
4723
- if (fs11.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
4791
+ if (fs12.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
4724
4792
  } catch {
4725
4793
  }
4726
4794
  return null;
4727
4795
  }
4728
4796
  function hasStoredSlackKey() {
4729
- return fs11.existsSync(CREDENTIALS_FILE);
4797
+ return fs12.existsSync(CREDENTIALS_FILE);
4730
4798
  }
4731
4799
  function writeGlobalSetting(key, value) {
4732
4800
  let config = {};
4733
4801
  try {
4734
- if (fs11.existsSync(GLOBAL_CONFIG_FILE)) {
4735
- config = JSON.parse(fs11.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
4802
+ if (fs12.existsSync(GLOBAL_CONFIG_FILE)) {
4803
+ config = JSON.parse(fs12.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
4736
4804
  }
4737
4805
  } catch {
4738
4806
  }
@@ -4744,8 +4812,8 @@ function writeTrustEntry(toolName, durationMs) {
4744
4812
  try {
4745
4813
  let trust = { entries: [] };
4746
4814
  try {
4747
- if (fs11.existsSync(TRUST_FILE2))
4748
- trust = JSON.parse(fs11.readFileSync(TRUST_FILE2, "utf-8"));
4815
+ if (fs12.existsSync(TRUST_FILE2))
4816
+ trust = JSON.parse(fs12.readFileSync(TRUST_FILE2, "utf-8"));
4749
4817
  } catch {
4750
4818
  }
4751
4819
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -4756,8 +4824,8 @@ function writeTrustEntry(toolName, durationMs) {
4756
4824
  }
4757
4825
  function readPersistentDecisions() {
4758
4826
  try {
4759
- if (fs11.existsSync(DECISIONS_FILE)) {
4760
- return JSON.parse(fs11.readFileSync(DECISIONS_FILE, "utf-8"));
4827
+ if (fs12.existsSync(DECISIONS_FILE)) {
4828
+ return JSON.parse(fs12.readFileSync(DECISIONS_FILE, "utf-8"));
4761
4829
  }
4762
4830
  } catch {
4763
4831
  }
@@ -4822,7 +4890,7 @@ function abandonPending() {
4822
4890
  });
4823
4891
  if (autoStarted) {
4824
4892
  try {
4825
- fs11.unlinkSync(DAEMON_PID_FILE);
4893
+ fs12.unlinkSync(DAEMON_PID_FILE);
4826
4894
  } catch {
4827
4895
  }
4828
4896
  setTimeout(() => {
@@ -4833,7 +4901,7 @@ function abandonPending() {
4833
4901
  }
4834
4902
  function startActivitySocket() {
4835
4903
  try {
4836
- fs11.unlinkSync(ACTIVITY_SOCKET_PATH2);
4904
+ fs12.unlinkSync(ACTIVITY_SOCKET_PATH2);
4837
4905
  } catch {
4838
4906
  }
4839
4907
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -4875,7 +4943,7 @@ function startActivitySocket() {
4875
4943
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
4876
4944
  process.on("exit", () => {
4877
4945
  try {
4878
- fs11.unlinkSync(ACTIVITY_SOCKET_PATH2);
4946
+ fs12.unlinkSync(ACTIVITY_SOCKET_PATH2);
4879
4947
  } catch {
4880
4948
  }
4881
4949
  });
@@ -4885,13 +4953,13 @@ var init_state2 = __esm({
4885
4953
  "src/daemon/state.ts"() {
4886
4954
  "use strict";
4887
4955
  init_daemon();
4888
- homeDir = os11.homedir();
4889
- DAEMON_PID_FILE = path14.join(homeDir, ".node9", "daemon.pid");
4890
- DECISIONS_FILE = path14.join(homeDir, ".node9", "decisions.json");
4891
- AUDIT_LOG_FILE = path14.join(homeDir, ".node9", "audit.log");
4892
- TRUST_FILE2 = path14.join(homeDir, ".node9", "trust.json");
4893
- GLOBAL_CONFIG_FILE = path14.join(homeDir, ".node9", "config.json");
4894
- CREDENTIALS_FILE = path14.join(homeDir, ".node9", "credentials.json");
4956
+ homeDir = os12.homedir();
4957
+ DAEMON_PID_FILE = path15.join(homeDir, ".node9", "daemon.pid");
4958
+ DECISIONS_FILE = path15.join(homeDir, ".node9", "decisions.json");
4959
+ AUDIT_LOG_FILE = path15.join(homeDir, ".node9", "audit.log");
4960
+ TRUST_FILE2 = path15.join(homeDir, ".node9", "trust.json");
4961
+ GLOBAL_CONFIG_FILE = path15.join(homeDir, ".node9", "config.json");
4962
+ CREDENTIALS_FILE = path15.join(homeDir, ".node9", "credentials.json");
4895
4963
  pending = /* @__PURE__ */ new Map();
4896
4964
  sseClients = /* @__PURE__ */ new Set();
4897
4965
  _abandonTimer = null;
@@ -4905,7 +4973,7 @@ var init_state2 = __esm({
4905
4973
  "2h": 2 * 60 * 6e4
4906
4974
  };
4907
4975
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
4908
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path14.join(os11.tmpdir(), "node9-activity.sock");
4976
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path15.join(os12.tmpdir(), "node9-activity.sock");
4909
4977
  ACTIVITY_RING_SIZE = 100;
4910
4978
  activityRing = [];
4911
4979
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -4914,8 +4982,8 @@ var init_state2 = __esm({
4914
4982
 
4915
4983
  // src/daemon/server.ts
4916
4984
  import http from "http";
4917
- import fs12 from "fs";
4918
- import path15 from "path";
4985
+ import fs13 from "fs";
4986
+ import path16 from "path";
4919
4987
  import { randomUUID as randomUUID3 } from "crypto";
4920
4988
  import { spawnSync as spawnSync2 } from "child_process";
4921
4989
  import chalk2 from "chalk";
@@ -4934,7 +5002,7 @@ function startDaemon() {
4934
5002
  idleTimer = setTimeout(() => {
4935
5003
  if (autoStarted) {
4936
5004
  try {
4937
- fs12.unlinkSync(DAEMON_PID_FILE);
5005
+ fs13.unlinkSync(DAEMON_PID_FILE);
4938
5006
  } catch {
4939
5007
  }
4940
5008
  }
@@ -5076,7 +5144,7 @@ data: ${JSON.stringify(item.data)}
5076
5144
  status: "pending"
5077
5145
  });
5078
5146
  }
5079
- const projectCwd = typeof cwd === "string" && path15.isAbsolute(cwd) ? cwd : void 0;
5147
+ const projectCwd = typeof cwd === "string" && path16.isAbsolute(cwd) ? cwd : void 0;
5080
5148
  const projectConfig = getConfig(projectCwd);
5081
5149
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
5082
5150
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -5380,14 +5448,14 @@ data: ${JSON.stringify(item.data)}
5380
5448
  server.on("error", (e) => {
5381
5449
  if (e.code === "EADDRINUSE") {
5382
5450
  try {
5383
- if (fs12.existsSync(DAEMON_PID_FILE)) {
5384
- const { pid } = JSON.parse(fs12.readFileSync(DAEMON_PID_FILE, "utf-8"));
5451
+ if (fs13.existsSync(DAEMON_PID_FILE)) {
5452
+ const { pid } = JSON.parse(fs13.readFileSync(DAEMON_PID_FILE, "utf-8"));
5385
5453
  process.kill(pid, 0);
5386
5454
  return process.exit(0);
5387
5455
  }
5388
5456
  } catch {
5389
5457
  try {
5390
- fs12.unlinkSync(DAEMON_PID_FILE);
5458
+ fs13.unlinkSync(DAEMON_PID_FILE);
5391
5459
  } catch {
5392
5460
  }
5393
5461
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -5457,28 +5525,28 @@ var init_server = __esm({
5457
5525
  });
5458
5526
 
5459
5527
  // src/daemon/index.ts
5460
- import fs13 from "fs";
5528
+ import fs14 from "fs";
5461
5529
  import chalk3 from "chalk";
5462
5530
  import { spawnSync as spawnSync3 } from "child_process";
5463
5531
  function stopDaemon() {
5464
- if (!fs13.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
5532
+ if (!fs14.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
5465
5533
  try {
5466
- const { pid } = JSON.parse(fs13.readFileSync(DAEMON_PID_FILE, "utf-8"));
5534
+ const { pid } = JSON.parse(fs14.readFileSync(DAEMON_PID_FILE, "utf-8"));
5467
5535
  process.kill(pid, "SIGTERM");
5468
5536
  console.log(chalk3.green("\u2705 Stopped."));
5469
5537
  } catch {
5470
5538
  console.log(chalk3.gray("Cleaned up stale PID file."));
5471
5539
  } finally {
5472
5540
  try {
5473
- fs13.unlinkSync(DAEMON_PID_FILE);
5541
+ fs14.unlinkSync(DAEMON_PID_FILE);
5474
5542
  } catch {
5475
5543
  }
5476
5544
  }
5477
5545
  }
5478
5546
  function daemonStatus() {
5479
- if (fs13.existsSync(DAEMON_PID_FILE)) {
5547
+ if (fs14.existsSync(DAEMON_PID_FILE)) {
5480
5548
  try {
5481
- const { pid } = JSON.parse(fs13.readFileSync(DAEMON_PID_FILE, "utf-8"));
5549
+ const { pid } = JSON.parse(fs14.readFileSync(DAEMON_PID_FILE, "utf-8"));
5482
5550
  process.kill(pid, 0);
5483
5551
  console.log(chalk3.green("Node9 daemon: running"));
5484
5552
  return;
@@ -5512,10 +5580,10 @@ __export(tail_exports, {
5512
5580
  startTail: () => startTail
5513
5581
  });
5514
5582
  import http2 from "http";
5515
- import chalk14 from "chalk";
5516
- import fs20 from "fs";
5517
- import os18 from "os";
5518
- import path22 from "path";
5583
+ import chalk15 from "chalk";
5584
+ import fs21 from "fs";
5585
+ import os19 from "os";
5586
+ import path23 from "path";
5519
5587
  import readline3 from "readline";
5520
5588
  import { spawn as spawn9, execSync as execSync3 } from "child_process";
5521
5589
  function getIcon(tool) {
@@ -5531,17 +5599,17 @@ function formatBase(activity) {
5531
5599
  const toolName = activity.tool.slice(0, 16).padEnd(16);
5532
5600
  const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
5533
5601
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
5534
- return `${chalk14.gray(time)} ${icon} ${chalk14.white.bold(toolName)} ${chalk14.dim(argsPreview)}`;
5602
+ return `${chalk15.gray(time)} ${icon} ${chalk15.white.bold(toolName)} ${chalk15.dim(argsPreview)}`;
5535
5603
  }
5536
5604
  function renderResult(activity, result) {
5537
5605
  const base = formatBase(activity);
5538
5606
  let status;
5539
5607
  if (result.status === "allow") {
5540
- status = chalk14.green("\u2713 ALLOW");
5608
+ status = chalk15.green("\u2713 ALLOW");
5541
5609
  } else if (result.status === "dlp") {
5542
- status = chalk14.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5610
+ status = chalk15.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5543
5611
  } else {
5544
- status = chalk14.red("\u2717 BLOCK");
5612
+ status = chalk15.red("\u2717 BLOCK");
5545
5613
  }
5546
5614
  if (process.stdout.isTTY) {
5547
5615
  readline3.clearLine(process.stdout, 0);
@@ -5551,16 +5619,16 @@ function renderResult(activity, result) {
5551
5619
  }
5552
5620
  function renderPending(activity) {
5553
5621
  if (!process.stdout.isTTY) return;
5554
- process.stdout.write(`${formatBase(activity)} ${chalk14.yellow("\u25CF \u2026")}\r`);
5622
+ process.stdout.write(`${formatBase(activity)} ${chalk15.yellow("\u25CF \u2026")}\r`);
5555
5623
  }
5556
5624
  async function ensureDaemon() {
5557
5625
  let pidPort = null;
5558
- if (fs20.existsSync(PID_FILE)) {
5626
+ if (fs21.existsSync(PID_FILE)) {
5559
5627
  try {
5560
- const { port } = JSON.parse(fs20.readFileSync(PID_FILE, "utf-8"));
5628
+ const { port } = JSON.parse(fs21.readFileSync(PID_FILE, "utf-8"));
5561
5629
  pidPort = port;
5562
5630
  } catch {
5563
- console.error(chalk14.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
5631
+ console.error(chalk15.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
5564
5632
  }
5565
5633
  }
5566
5634
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -5571,7 +5639,7 @@ async function ensureDaemon() {
5571
5639
  if (res.ok) return checkPort;
5572
5640
  } catch {
5573
5641
  }
5574
- console.log(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5642
+ console.log(chalk15.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5575
5643
  const child = spawn9(process.execPath, [process.argv[1], "daemon"], {
5576
5644
  detached: true,
5577
5645
  stdio: "ignore",
@@ -5588,7 +5656,7 @@ async function ensureDaemon() {
5588
5656
  } catch {
5589
5657
  }
5590
5658
  }
5591
- console.error(chalk14.red("\u274C Daemon failed to start. Try: node9 daemon start"));
5659
+ console.error(chalk15.red("\u274C Daemon failed to start. Try: node9 daemon start"));
5592
5660
  process.exit(1);
5593
5661
  }
5594
5662
  function postDecisionHttp(id, decision, csrfToken, port) {
@@ -5659,7 +5727,7 @@ async function startTail(options = {}) {
5659
5727
  req2.end();
5660
5728
  });
5661
5729
  if (result.ok) {
5662
- console.log(chalk14.green("\u2713 Flight Recorder buffer cleared."));
5730
+ console.log(chalk15.green("\u2713 Flight Recorder buffer cleared."));
5663
5731
  } else if (result.code === "ECONNREFUSED") {
5664
5732
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
5665
5733
  } else if (result.code === "ETIMEDOUT") {
@@ -5722,16 +5790,16 @@ async function startTail(options = {}) {
5722
5790
  process.stdout.write(SHOW_CURSOR);
5723
5791
  postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
5724
5792
  try {
5725
- fs20.appendFileSync(
5726
- path22.join(os18.homedir(), ".node9", "hook-debug.log"),
5793
+ fs21.appendFileSync(
5794
+ path23.join(os19.homedir(), ".node9", "hook-debug.log"),
5727
5795
  `[tail] POST /decision failed: ${String(err)}
5728
5796
  `
5729
5797
  );
5730
5798
  } catch {
5731
5799
  }
5732
5800
  });
5733
- const decisionLabel = decision === "allow" ? chalk14.green("\u2713 ALLOWED (terminal)") : chalk14.red("\u2717 DENIED (terminal)");
5734
- console.log(`${chalk14.cyan("\u25C6")} ${chalk14.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
5801
+ const decisionLabel = decision === "allow" ? chalk15.green("\u2713 ALLOWED (terminal)") : chalk15.red("\u2717 DENIED (terminal)");
5802
+ console.log(`${chalk15.cyan("\u25C6")} ${chalk15.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
5735
5803
  approvalQueue.shift();
5736
5804
  cardActive = false;
5737
5805
  showNextCard();
@@ -5768,16 +5836,16 @@ async function startTail(options = {}) {
5768
5836
  }
5769
5837
  } catch {
5770
5838
  }
5771
- console.log(chalk14.cyan.bold(`
5772
- \u{1F6F0}\uFE0F Node9 tail `) + chalk14.dim(`\u2192 ${dashboardUrl}`));
5839
+ console.log(chalk15.cyan.bold(`
5840
+ \u{1F6F0}\uFE0F Node9 tail `) + chalk15.dim(`\u2192 ${dashboardUrl}`));
5773
5841
  if (canApprove) {
5774
- console.log(chalk14.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5842
+ console.log(chalk15.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5775
5843
  }
5776
5844
  if (options.history) {
5777
- console.log(chalk14.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
5845
+ console.log(chalk15.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
5778
5846
  } else {
5779
5847
  console.log(
5780
- chalk14.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
5848
+ chalk15.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
5781
5849
  );
5782
5850
  }
5783
5851
  process.on("SIGINT", () => {
@@ -5787,13 +5855,13 @@ async function startTail(options = {}) {
5787
5855
  readline3.clearLine(process.stdout, 0);
5788
5856
  readline3.cursorTo(process.stdout, 0);
5789
5857
  }
5790
- console.log(chalk14.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5858
+ console.log(chalk15.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5791
5859
  process.exit(0);
5792
5860
  });
5793
5861
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
5794
5862
  const req = http2.get(sseUrl, (res) => {
5795
5863
  if (res.statusCode !== 200) {
5796
- console.error(chalk14.red(`Failed to connect: HTTP ${res.statusCode}`));
5864
+ console.error(chalk15.red(`Failed to connect: HTTP ${res.statusCode}`));
5797
5865
  process.exit(1);
5798
5866
  }
5799
5867
  let currentEvent = "";
@@ -5823,7 +5891,7 @@ async function startTail(options = {}) {
5823
5891
  readline3.clearLine(process.stdout, 0);
5824
5892
  readline3.cursorTo(process.stdout, 0);
5825
5893
  }
5826
- console.log(chalk14.red("\n\u274C Daemon disconnected."));
5894
+ console.log(chalk15.red("\n\u274C Daemon disconnected."));
5827
5895
  process.exit(1);
5828
5896
  });
5829
5897
  });
@@ -5903,7 +5971,7 @@ async function startTail(options = {}) {
5903
5971
  }
5904
5972
  req.on("error", (err) => {
5905
5973
  const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
5906
- console.error(chalk14.red(`
5974
+ console.error(chalk15.red(`
5907
5975
  \u274C ${msg}`));
5908
5976
  process.exit(1);
5909
5977
  });
@@ -5914,7 +5982,7 @@ var init_tail = __esm({
5914
5982
  "use strict";
5915
5983
  init_daemon2();
5916
5984
  init_core();
5917
- PID_FILE = path22.join(os18.homedir(), ".node9", "daemon.pid");
5985
+ PID_FILE = path23.join(os19.homedir(), ".node9", "daemon.pid");
5918
5986
  ICONS = {
5919
5987
  bash: "\u{1F4BB}",
5920
5988
  shell: "\u{1F4BB}",
@@ -5952,9 +6020,9 @@ init_core();
5952
6020
  import { Command } from "commander";
5953
6021
 
5954
6022
  // src/setup.ts
5955
- import fs10 from "fs";
5956
- import path13 from "path";
5957
- import os10 from "os";
6023
+ import fs11 from "fs";
6024
+ import path14 from "path";
6025
+ import os11 from "os";
5958
6026
  import chalk from "chalk";
5959
6027
  import { confirm } from "@inquirer/prompts";
5960
6028
  function printDaemonTip() {
@@ -5971,26 +6039,26 @@ function fullPathCommand(subcommand) {
5971
6039
  }
5972
6040
  function readJson(filePath) {
5973
6041
  try {
5974
- if (fs10.existsSync(filePath)) {
5975
- return JSON.parse(fs10.readFileSync(filePath, "utf-8"));
6042
+ if (fs11.existsSync(filePath)) {
6043
+ return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
5976
6044
  }
5977
6045
  } catch {
5978
6046
  }
5979
6047
  return null;
5980
6048
  }
5981
6049
  function writeJson(filePath, data) {
5982
- const dir = path13.dirname(filePath);
5983
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
5984
- fs10.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
6050
+ const dir = path14.dirname(filePath);
6051
+ if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
6052
+ fs11.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
5985
6053
  }
5986
6054
  function isNode9Hook(cmd) {
5987
6055
  if (!cmd) return false;
5988
6056
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
5989
6057
  }
5990
6058
  function teardownClaude() {
5991
- const homeDir2 = os10.homedir();
5992
- const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
5993
- const mcpPath = path13.join(homeDir2, ".claude.json");
6059
+ const homeDir2 = os11.homedir();
6060
+ const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
6061
+ const mcpPath = path14.join(homeDir2, ".claude.json");
5994
6062
  let changed = false;
5995
6063
  const settings = readJson(hooksPath);
5996
6064
  if (settings?.hooks) {
@@ -6038,8 +6106,8 @@ function teardownClaude() {
6038
6106
  }
6039
6107
  }
6040
6108
  function teardownGemini() {
6041
- const homeDir2 = os10.homedir();
6042
- const settingsPath = path13.join(homeDir2, ".gemini", "settings.json");
6109
+ const homeDir2 = os11.homedir();
6110
+ const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
6043
6111
  const settings = readJson(settingsPath);
6044
6112
  if (!settings) {
6045
6113
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -6077,8 +6145,8 @@ function teardownGemini() {
6077
6145
  }
6078
6146
  }
6079
6147
  function teardownCursor() {
6080
- const homeDir2 = os10.homedir();
6081
- const mcpPath = path13.join(homeDir2, ".cursor", "mcp.json");
6148
+ const homeDir2 = os11.homedir();
6149
+ const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
6082
6150
  const mcpConfig = readJson(mcpPath);
6083
6151
  if (!mcpConfig?.mcpServers) {
6084
6152
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -6104,9 +6172,9 @@ function teardownCursor() {
6104
6172
  }
6105
6173
  }
6106
6174
  async function setupClaude() {
6107
- const homeDir2 = os10.homedir();
6108
- const mcpPath = path13.join(homeDir2, ".claude.json");
6109
- const hooksPath = path13.join(homeDir2, ".claude", "settings.json");
6175
+ const homeDir2 = os11.homedir();
6176
+ const mcpPath = path14.join(homeDir2, ".claude.json");
6177
+ const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
6110
6178
  const claudeConfig = readJson(mcpPath) ?? {};
6111
6179
  const settings = readJson(hooksPath) ?? {};
6112
6180
  const servers = claudeConfig.mcpServers ?? {};
@@ -6180,8 +6248,8 @@ async function setupClaude() {
6180
6248
  }
6181
6249
  }
6182
6250
  async function setupGemini() {
6183
- const homeDir2 = os10.homedir();
6184
- const settingsPath = path13.join(homeDir2, ".gemini", "settings.json");
6251
+ const homeDir2 = os11.homedir();
6252
+ const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
6185
6253
  const settings = readJson(settingsPath) ?? {};
6186
6254
  const servers = settings.mcpServers ?? {};
6187
6255
  let anythingChanged = false;
@@ -6263,8 +6331,8 @@ async function setupGemini() {
6263
6331
  }
6264
6332
  }
6265
6333
  async function setupCursor() {
6266
- const homeDir2 = os10.homedir();
6267
- const mcpPath = path13.join(homeDir2, ".cursor", "mcp.json");
6334
+ const homeDir2 = os11.homedir();
6335
+ const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
6268
6336
  const mcpConfig = readJson(mcpPath) ?? {};
6269
6337
  const servers = mcpConfig.mcpServers ?? {};
6270
6338
  let anythingChanged = false;
@@ -6320,10 +6388,10 @@ async function setupCursor() {
6320
6388
 
6321
6389
  // src/cli.ts
6322
6390
  init_daemon2();
6323
- import chalk15 from "chalk";
6324
- import fs21 from "fs";
6325
- import path23 from "path";
6326
- import os19 from "os";
6391
+ import chalk16 from "chalk";
6392
+ import fs22 from "fs";
6393
+ import path24 from "path";
6394
+ import os20 from "os";
6327
6395
  import { confirm as confirm3 } from "@inquirer/prompts";
6328
6396
 
6329
6397
  // src/utils/duration.ts
@@ -6548,32 +6616,32 @@ init_daemon();
6548
6616
  init_config();
6549
6617
  init_policy();
6550
6618
  import chalk5 from "chalk";
6551
- import fs15 from "fs";
6552
- import path17 from "path";
6553
- import os13 from "os";
6619
+ import fs16 from "fs";
6620
+ import path18 from "path";
6621
+ import os14 from "os";
6554
6622
 
6555
6623
  // src/undo.ts
6556
6624
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
6557
6625
  import crypto2 from "crypto";
6558
- import fs14 from "fs";
6559
- import path16 from "path";
6560
- import os12 from "os";
6561
- var SNAPSHOT_STACK_PATH = path16.join(os12.homedir(), ".node9", "snapshots.json");
6562
- var UNDO_LATEST_PATH = path16.join(os12.homedir(), ".node9", "undo_latest.txt");
6626
+ import fs15 from "fs";
6627
+ import path17 from "path";
6628
+ import os13 from "os";
6629
+ var SNAPSHOT_STACK_PATH = path17.join(os13.homedir(), ".node9", "snapshots.json");
6630
+ var UNDO_LATEST_PATH = path17.join(os13.homedir(), ".node9", "undo_latest.txt");
6563
6631
  var MAX_SNAPSHOTS = 10;
6564
6632
  var GIT_TIMEOUT = 15e3;
6565
6633
  function readStack() {
6566
6634
  try {
6567
- if (fs14.existsSync(SNAPSHOT_STACK_PATH))
6568
- return JSON.parse(fs14.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
6635
+ if (fs15.existsSync(SNAPSHOT_STACK_PATH))
6636
+ return JSON.parse(fs15.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
6569
6637
  } catch {
6570
6638
  }
6571
6639
  return [];
6572
6640
  }
6573
6641
  function writeStack(stack) {
6574
- const dir = path16.dirname(SNAPSHOT_STACK_PATH);
6575
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
6576
- fs14.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
6642
+ const dir = path17.dirname(SNAPSHOT_STACK_PATH);
6643
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
6644
+ fs15.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
6577
6645
  }
6578
6646
  function buildArgsSummary(tool, args) {
6579
6647
  if (!args || typeof args !== "object") return "";
@@ -6589,7 +6657,7 @@ function buildArgsSummary(tool, args) {
6589
6657
  function normalizeCwdForHash(cwd) {
6590
6658
  let normalized;
6591
6659
  try {
6592
- normalized = fs14.realpathSync(cwd);
6660
+ normalized = fs15.realpathSync(cwd);
6593
6661
  } catch {
6594
6662
  normalized = cwd;
6595
6663
  }
@@ -6599,16 +6667,16 @@ function normalizeCwdForHash(cwd) {
6599
6667
  }
6600
6668
  function getShadowRepoDir(cwd) {
6601
6669
  const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
6602
- return path16.join(os12.homedir(), ".node9", "snapshots", hash);
6670
+ return path17.join(os13.homedir(), ".node9", "snapshots", hash);
6603
6671
  }
6604
6672
  function cleanOrphanedIndexFiles(shadowDir) {
6605
6673
  try {
6606
6674
  const cutoff = Date.now() - 6e4;
6607
- for (const f of fs14.readdirSync(shadowDir)) {
6675
+ for (const f of fs15.readdirSync(shadowDir)) {
6608
6676
  if (f.startsWith("index_")) {
6609
- const fp = path16.join(shadowDir, f);
6677
+ const fp = path17.join(shadowDir, f);
6610
6678
  try {
6611
- if (fs14.statSync(fp).mtimeMs < cutoff) fs14.unlinkSync(fp);
6679
+ if (fs15.statSync(fp).mtimeMs < cutoff) fs15.unlinkSync(fp);
6612
6680
  } catch {
6613
6681
  }
6614
6682
  }
@@ -6620,7 +6688,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
6620
6688
  const hardcoded = [".git", ".node9"];
6621
6689
  const lines = [...hardcoded, ...ignorePaths].join("\n");
6622
6690
  try {
6623
- fs14.writeFileSync(path16.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
6691
+ fs15.writeFileSync(path17.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
6624
6692
  } catch {
6625
6693
  }
6626
6694
  }
@@ -6633,25 +6701,25 @@ function ensureShadowRepo(shadowDir, cwd) {
6633
6701
  timeout: 3e3
6634
6702
  });
6635
6703
  if (check.status === 0) {
6636
- const ptPath = path16.join(shadowDir, "project-path.txt");
6704
+ const ptPath = path17.join(shadowDir, "project-path.txt");
6637
6705
  try {
6638
- const stored = fs14.readFileSync(ptPath, "utf8").trim();
6706
+ const stored = fs15.readFileSync(ptPath, "utf8").trim();
6639
6707
  if (stored === normalizedCwd) return true;
6640
6708
  if (process.env.NODE9_DEBUG === "1")
6641
6709
  console.error(
6642
6710
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
6643
6711
  );
6644
- fs14.rmSync(shadowDir, { recursive: true, force: true });
6712
+ fs15.rmSync(shadowDir, { recursive: true, force: true });
6645
6713
  } catch {
6646
6714
  try {
6647
- fs14.writeFileSync(ptPath, normalizedCwd, "utf8");
6715
+ fs15.writeFileSync(ptPath, normalizedCwd, "utf8");
6648
6716
  } catch {
6649
6717
  }
6650
6718
  return true;
6651
6719
  }
6652
6720
  }
6653
6721
  try {
6654
- fs14.mkdirSync(shadowDir, { recursive: true });
6722
+ fs15.mkdirSync(shadowDir, { recursive: true });
6655
6723
  } catch {
6656
6724
  }
6657
6725
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -6660,7 +6728,7 @@ function ensureShadowRepo(shadowDir, cwd) {
6660
6728
  console.error("[Node9] git init --bare failed:", init.stderr?.toString());
6661
6729
  return false;
6662
6730
  }
6663
- const configFile = path16.join(shadowDir, "config");
6731
+ const configFile = path17.join(shadowDir, "config");
6664
6732
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
6665
6733
  timeout: 3e3
6666
6734
  });
@@ -6668,7 +6736,7 @@ function ensureShadowRepo(shadowDir, cwd) {
6668
6736
  timeout: 3e3
6669
6737
  });
6670
6738
  try {
6671
- fs14.writeFileSync(path16.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
6739
+ fs15.writeFileSync(path17.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
6672
6740
  } catch {
6673
6741
  }
6674
6742
  return true;
@@ -6691,7 +6759,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6691
6759
  const shadowDir = getShadowRepoDir(cwd);
6692
6760
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
6693
6761
  writeShadowExcludes(shadowDir, ignorePaths);
6694
- indexFile = path16.join(shadowDir, `index_${process.pid}_${Date.now()}`);
6762
+ indexFile = path17.join(shadowDir, `index_${process.pid}_${Date.now()}`);
6695
6763
  const shadowEnv = {
6696
6764
  ...process.env,
6697
6765
  GIT_DIR: shadowDir,
@@ -6720,7 +6788,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6720
6788
  const shouldGc = stack.length % 5 === 0;
6721
6789
  if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
6722
6790
  writeStack(stack);
6723
- fs14.writeFileSync(UNDO_LATEST_PATH, commitHash);
6791
+ fs15.writeFileSync(UNDO_LATEST_PATH, commitHash);
6724
6792
  if (shouldGc) {
6725
6793
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
6726
6794
  }
@@ -6731,7 +6799,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6731
6799
  } finally {
6732
6800
  if (indexFile) {
6733
6801
  try {
6734
- fs14.unlinkSync(indexFile);
6802
+ fs15.unlinkSync(indexFile);
6735
6803
  } catch {
6736
6804
  }
6737
6805
  }
@@ -6800,9 +6868,9 @@ function applyUndo(hash, cwd) {
6800
6868
  timeout: GIT_TIMEOUT
6801
6869
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
6802
6870
  for (const file of [...tracked, ...untracked]) {
6803
- const fullPath = path16.join(dir, file);
6804
- if (!snapshotFiles.has(file) && fs14.existsSync(fullPath)) {
6805
- fs14.unlinkSync(fullPath);
6871
+ const fullPath = path17.join(dir, file);
6872
+ if (!snapshotFiles.has(file) && fs15.existsSync(fullPath)) {
6873
+ fs15.unlinkSync(fullPath);
6806
6874
  }
6807
6875
  }
6808
6876
  return true;
@@ -6826,9 +6894,9 @@ function registerCheckCommand(program2) {
6826
6894
  } catch (err) {
6827
6895
  const tempConfig = getConfig();
6828
6896
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
6829
- const logPath = path17.join(os13.homedir(), ".node9", "hook-debug.log");
6897
+ const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
6830
6898
  const errMsg = err instanceof Error ? err.message : String(err);
6831
- fs15.appendFileSync(
6899
+ fs16.appendFileSync(
6832
6900
  logPath,
6833
6901
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
6834
6902
  RAW: ${raw}
@@ -6839,10 +6907,10 @@ RAW: ${raw}
6839
6907
  }
6840
6908
  const config = getConfig(payload.cwd || void 0);
6841
6909
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
6842
- const logPath = path17.join(os13.homedir(), ".node9", "hook-debug.log");
6843
- if (!fs15.existsSync(path17.dirname(logPath)))
6844
- fs15.mkdirSync(path17.dirname(logPath), { recursive: true });
6845
- fs15.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
6910
+ const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
6911
+ if (!fs16.existsSync(path18.dirname(logPath)))
6912
+ fs16.mkdirSync(path18.dirname(logPath), { recursive: true });
6913
+ fs16.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
6846
6914
  `);
6847
6915
  }
6848
6916
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -6855,8 +6923,8 @@ RAW: ${raw}
6855
6923
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
6856
6924
  let ttyFd = null;
6857
6925
  try {
6858
- ttyFd = fs15.openSync("/dev/tty", "w");
6859
- const writeTty = (line) => fs15.writeSync(ttyFd, line + "\n");
6926
+ ttyFd = fs16.openSync("/dev/tty", "w");
6927
+ const writeTty = (line) => fs16.writeSync(ttyFd, line + "\n");
6860
6928
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
6861
6929
  writeTty(chalk5.bgRed.white.bold(`
6862
6930
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -6872,7 +6940,7 @@ RAW: ${raw}
6872
6940
  } finally {
6873
6941
  if (ttyFd !== null)
6874
6942
  try {
6875
- fs15.closeSync(ttyFd);
6943
+ fs16.closeSync(ttyFd);
6876
6944
  } catch {
6877
6945
  }
6878
6946
  }
@@ -6903,7 +6971,7 @@ RAW: ${raw}
6903
6971
  if (shouldSnapshot(toolName, toolInput, config)) {
6904
6972
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
6905
6973
  }
6906
- const safeCwdForAuth = typeof payload.cwd === "string" && path17.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6974
+ const safeCwdForAuth = typeof payload.cwd === "string" && path18.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6907
6975
  const result = await authorizeHeadless(toolName, toolInput, meta, {
6908
6976
  cwd: safeCwdForAuth
6909
6977
  });
@@ -6915,12 +6983,12 @@ RAW: ${raw}
6915
6983
  }
6916
6984
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
6917
6985
  try {
6918
- const tty = fs15.openSync("/dev/tty", "w");
6919
- fs15.writeSync(
6986
+ const tty = fs16.openSync("/dev/tty", "w");
6987
+ fs16.writeSync(
6920
6988
  tty,
6921
6989
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
6922
6990
  );
6923
- fs15.closeSync(tty);
6991
+ fs16.closeSync(tty);
6924
6992
  } catch {
6925
6993
  }
6926
6994
  const daemonReady = await autoStartDaemonAndWait();
@@ -6947,9 +7015,9 @@ RAW: ${raw}
6947
7015
  });
6948
7016
  } catch (err) {
6949
7017
  if (process.env.NODE9_DEBUG === "1") {
6950
- const logPath = path17.join(os13.homedir(), ".node9", "hook-debug.log");
7018
+ const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
6951
7019
  const errMsg = err instanceof Error ? err.message : String(err);
6952
- fs15.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
7020
+ fs16.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
6953
7021
  `);
6954
7022
  }
6955
7023
  process.exit(0);
@@ -6986,9 +7054,9 @@ RAW: ${raw}
6986
7054
  init_audit();
6987
7055
  init_config();
6988
7056
  init_policy();
6989
- import fs16 from "fs";
6990
- import path18 from "path";
6991
- import os14 from "os";
7057
+ import fs17 from "fs";
7058
+ import path19 from "path";
7059
+ import os15 from "os";
6992
7060
  function sanitize3(value) {
6993
7061
  return value.replace(/[\x00-\x1F\x7F]/g, "");
6994
7062
  }
@@ -7007,11 +7075,11 @@ function registerLogCommand(program2) {
7007
7075
  decision: "allowed",
7008
7076
  source: "post-hook"
7009
7077
  };
7010
- const logPath = path18.join(os14.homedir(), ".node9", "audit.log");
7011
- if (!fs16.existsSync(path18.dirname(logPath)))
7012
- fs16.mkdirSync(path18.dirname(logPath), { recursive: true });
7013
- fs16.appendFileSync(logPath, JSON.stringify(entry) + "\n");
7014
- const safeCwd = typeof payload.cwd === "string" && path18.isAbsolute(payload.cwd) ? payload.cwd : void 0;
7078
+ const logPath = path19.join(os15.homedir(), ".node9", "audit.log");
7079
+ if (!fs17.existsSync(path19.dirname(logPath)))
7080
+ fs17.mkdirSync(path19.dirname(logPath), { recursive: true });
7081
+ fs17.appendFileSync(logPath, JSON.stringify(entry) + "\n");
7082
+ const safeCwd = typeof payload.cwd === "string" && path19.isAbsolute(payload.cwd) ? payload.cwd : void 0;
7015
7083
  const config = getConfig(safeCwd);
7016
7084
  if (shouldSnapshot(tool, {}, config)) {
7017
7085
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -7020,9 +7088,9 @@ function registerLogCommand(program2) {
7020
7088
  const msg = err instanceof Error ? err.message : String(err);
7021
7089
  process.stderr.write(`[Node9] audit log error: ${msg}
7022
7090
  `);
7023
- const debugPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
7091
+ const debugPath = path19.join(os15.homedir(), ".node9", "hook-debug.log");
7024
7092
  try {
7025
- fs16.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
7093
+ fs17.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
7026
7094
  `);
7027
7095
  } catch {
7028
7096
  }
@@ -7327,13 +7395,13 @@ function registerConfigShowCommand(program2) {
7327
7395
  // src/cli/commands/doctor.ts
7328
7396
  init_daemon();
7329
7397
  import chalk7 from "chalk";
7330
- import fs17 from "fs";
7331
- import path19 from "path";
7332
- import os15 from "os";
7398
+ import fs18 from "fs";
7399
+ import path20 from "path";
7400
+ import os16 from "os";
7333
7401
  import { execSync as execSync2 } from "child_process";
7334
7402
  function registerDoctorCommand(program2, version2) {
7335
7403
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
7336
- const homeDir2 = os15.homedir();
7404
+ const homeDir2 = os16.homedir();
7337
7405
  let failures = 0;
7338
7406
  function pass(msg) {
7339
7407
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -7382,10 +7450,10 @@ function registerDoctorCommand(program2, version2) {
7382
7450
  );
7383
7451
  }
7384
7452
  section("Configuration");
7385
- const globalConfigPath = path19.join(homeDir2, ".node9", "config.json");
7386
- if (fs17.existsSync(globalConfigPath)) {
7453
+ const globalConfigPath = path20.join(homeDir2, ".node9", "config.json");
7454
+ if (fs18.existsSync(globalConfigPath)) {
7387
7455
  try {
7388
- JSON.parse(fs17.readFileSync(globalConfigPath, "utf-8"));
7456
+ JSON.parse(fs18.readFileSync(globalConfigPath, "utf-8"));
7389
7457
  pass("~/.node9/config.json found and valid");
7390
7458
  } catch {
7391
7459
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -7393,10 +7461,10 @@ function registerDoctorCommand(program2, version2) {
7393
7461
  } else {
7394
7462
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
7395
7463
  }
7396
- const projectConfigPath = path19.join(process.cwd(), "node9.config.json");
7397
- if (fs17.existsSync(projectConfigPath)) {
7464
+ const projectConfigPath = path20.join(process.cwd(), "node9.config.json");
7465
+ if (fs18.existsSync(projectConfigPath)) {
7398
7466
  try {
7399
- JSON.parse(fs17.readFileSync(projectConfigPath, "utf-8"));
7467
+ JSON.parse(fs18.readFileSync(projectConfigPath, "utf-8"));
7400
7468
  pass("node9.config.json found and valid (project)");
7401
7469
  } catch {
7402
7470
  fail(
@@ -7405,8 +7473,8 @@ function registerDoctorCommand(program2, version2) {
7405
7473
  );
7406
7474
  }
7407
7475
  }
7408
- const credsPath = path19.join(homeDir2, ".node9", "credentials.json");
7409
- if (fs17.existsSync(credsPath)) {
7476
+ const credsPath = path20.join(homeDir2, ".node9", "credentials.json");
7477
+ if (fs18.existsSync(credsPath)) {
7410
7478
  pass("Cloud credentials found (~/.node9/credentials.json)");
7411
7479
  } else {
7412
7480
  warn(
@@ -7415,10 +7483,10 @@ function registerDoctorCommand(program2, version2) {
7415
7483
  );
7416
7484
  }
7417
7485
  section("Agent Hooks");
7418
- const claudeSettingsPath = path19.join(homeDir2, ".claude", "settings.json");
7419
- if (fs17.existsSync(claudeSettingsPath)) {
7486
+ const claudeSettingsPath = path20.join(homeDir2, ".claude", "settings.json");
7487
+ if (fs18.existsSync(claudeSettingsPath)) {
7420
7488
  try {
7421
- const cs = JSON.parse(fs17.readFileSync(claudeSettingsPath, "utf-8"));
7489
+ const cs = JSON.parse(fs18.readFileSync(claudeSettingsPath, "utf-8"));
7422
7490
  const hasHook = cs.hooks?.PreToolUse?.some(
7423
7491
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
7424
7492
  );
@@ -7434,10 +7502,10 @@ function registerDoctorCommand(program2, version2) {
7434
7502
  } else {
7435
7503
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
7436
7504
  }
7437
- const geminiSettingsPath = path19.join(homeDir2, ".gemini", "settings.json");
7438
- if (fs17.existsSync(geminiSettingsPath)) {
7505
+ const geminiSettingsPath = path20.join(homeDir2, ".gemini", "settings.json");
7506
+ if (fs18.existsSync(geminiSettingsPath)) {
7439
7507
  try {
7440
- const gs = JSON.parse(fs17.readFileSync(geminiSettingsPath, "utf-8"));
7508
+ const gs = JSON.parse(fs18.readFileSync(geminiSettingsPath, "utf-8"));
7441
7509
  const hasHook = gs.hooks?.BeforeTool?.some(
7442
7510
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
7443
7511
  );
@@ -7453,10 +7521,10 @@ function registerDoctorCommand(program2, version2) {
7453
7521
  } else {
7454
7522
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
7455
7523
  }
7456
- const cursorHooksPath = path19.join(homeDir2, ".cursor", "hooks.json");
7457
- if (fs17.existsSync(cursorHooksPath)) {
7524
+ const cursorHooksPath = path20.join(homeDir2, ".cursor", "hooks.json");
7525
+ if (fs18.existsSync(cursorHooksPath)) {
7458
7526
  try {
7459
- const cur = JSON.parse(fs17.readFileSync(cursorHooksPath, "utf-8"));
7527
+ const cur = JSON.parse(fs18.readFileSync(cursorHooksPath, "utf-8"));
7460
7528
  const hasHook = cur.hooks?.preToolUse?.some(
7461
7529
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
7462
7530
  );
@@ -7494,9 +7562,9 @@ function registerDoctorCommand(program2, version2) {
7494
7562
 
7495
7563
  // src/cli/commands/audit.ts
7496
7564
  import chalk8 from "chalk";
7497
- import fs18 from "fs";
7498
- import path20 from "path";
7499
- import os16 from "os";
7565
+ import fs19 from "fs";
7566
+ import path21 from "path";
7567
+ import os17 from "os";
7500
7568
  function formatRelativeTime(timestamp) {
7501
7569
  const diff = Date.now() - new Date(timestamp).getTime();
7502
7570
  const sec = Math.floor(diff / 1e3);
@@ -7509,14 +7577,14 @@ function formatRelativeTime(timestamp) {
7509
7577
  }
7510
7578
  function registerAuditCommand(program2) {
7511
7579
  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) => {
7512
- const logPath = path20.join(os16.homedir(), ".node9", "audit.log");
7513
- if (!fs18.existsSync(logPath)) {
7580
+ const logPath = path21.join(os17.homedir(), ".node9", "audit.log");
7581
+ if (!fs19.existsSync(logPath)) {
7514
7582
  console.log(
7515
7583
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
7516
7584
  );
7517
7585
  return;
7518
7586
  }
7519
- const raw = fs18.readFileSync(logPath, "utf-8");
7587
+ const raw = fs19.readFileSync(logPath, "utf-8");
7520
7588
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
7521
7589
  let entries = lines.flatMap((line) => {
7522
7590
  try {
@@ -7636,9 +7704,9 @@ function registerDaemonCommand(program2) {
7636
7704
  init_core();
7637
7705
  init_daemon();
7638
7706
  import chalk10 from "chalk";
7639
- import fs19 from "fs";
7640
- import path21 from "path";
7641
- import os17 from "os";
7707
+ import fs20 from "fs";
7708
+ import path22 from "path";
7709
+ import os18 from "os";
7642
7710
  function registerStatusCommand(program2) {
7643
7711
  program2.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
7644
7712
  const creds = getCredentials();
@@ -7673,13 +7741,13 @@ function registerStatusCommand(program2) {
7673
7741
  console.log("");
7674
7742
  const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
7675
7743
  console.log(` Mode: ${modeLabel}`);
7676
- const projectConfig = path21.join(process.cwd(), "node9.config.json");
7677
- const globalConfig = path21.join(os17.homedir(), ".node9", "config.json");
7744
+ const projectConfig = path22.join(process.cwd(), "node9.config.json");
7745
+ const globalConfig = path22.join(os18.homedir(), ".node9", "config.json");
7678
7746
  console.log(
7679
- ` Local: ${fs19.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
7747
+ ` Local: ${fs20.existsSync(projectConfig) ? chalk10.green("Active (node9.config.json)") : chalk10.gray("Not present")}`
7680
7748
  );
7681
7749
  console.log(
7682
- ` Global: ${fs19.existsSync(globalConfig) ? chalk10.green("Active (~/.node9/config.json)") : chalk10.gray("Not present")}`
7750
+ ` Global: ${fs20.existsSync(globalConfig) ? chalk10.green("Active (~/.node9/config.json)") : chalk10.gray("Not present")}`
7683
7751
  );
7684
7752
  if (mergedConfig.policy.sandboxPaths.length > 0) {
7685
7753
  console.log(
@@ -8063,22 +8131,77 @@ function registerMcpGatewayCommand(program2) {
8063
8131
  });
8064
8132
  }
8065
8133
 
8134
+ // src/cli/commands/trust.ts
8135
+ init_trusted_hosts();
8136
+ import chalk14 from "chalk";
8137
+ function isValidHost(host) {
8138
+ return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
8139
+ }
8140
+ function registerTrustCommand(program2) {
8141
+ const trustCmd = program2.command("trust").description("Manage trusted network hosts (reduces approval friction for known destinations)");
8142
+ trustCmd.command("add <host>").description("Add a trusted host \u2014 pipe-chain blocks targeting this host are downgraded").action((host) => {
8143
+ const normalized = normalizeHost(host.trim());
8144
+ if (!isValidHost(normalized)) {
8145
+ console.error(
8146
+ chalk14.red(`
8147
+ \u274C Invalid host: "${host}"
8148
+ `) + chalk14.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
8149
+ );
8150
+ process.exit(1);
8151
+ }
8152
+ addTrustedHost(normalized);
8153
+ console.log(chalk14.green(`
8154
+ \u2705 ${normalized} added to trusted hosts.`));
8155
+ console.log(
8156
+ chalk14.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
8157
+ );
8158
+ });
8159
+ trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
8160
+ const normalized = normalizeHost(host.trim());
8161
+ const removed = removeTrustedHost(normalized);
8162
+ if (!removed) {
8163
+ console.error(chalk14.yellow(`
8164
+ \u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
8165
+ `));
8166
+ process.exit(1);
8167
+ }
8168
+ console.log(chalk14.green(`
8169
+ \u2705 ${normalized} removed from trusted hosts.
8170
+ `));
8171
+ });
8172
+ trustCmd.command("list").description("Show all trusted hosts").action(() => {
8173
+ const hosts = readTrustedHosts();
8174
+ if (hosts.length === 0) {
8175
+ console.log(chalk14.gray("\n No trusted hosts configured.\n"));
8176
+ console.log(` Add one: ${chalk14.cyan("node9 trust add api.mycompany.com")}
8177
+ `);
8178
+ return;
8179
+ }
8180
+ console.log(chalk14.bold("\n\u{1F513} Trusted Hosts\n"));
8181
+ for (const entry of hosts) {
8182
+ const date = new Date(entry.addedAt).toLocaleDateString();
8183
+ console.log(` ${chalk14.cyan(entry.host.padEnd(40))} ${chalk14.gray(`added ${date}`)}`);
8184
+ }
8185
+ console.log("");
8186
+ });
8187
+ }
8188
+
8066
8189
  // src/cli.ts
8067
8190
  var { version } = JSON.parse(
8068
- fs21.readFileSync(path23.join(__dirname, "../package.json"), "utf-8")
8191
+ fs22.readFileSync(path24.join(__dirname, "../package.json"), "utf-8")
8069
8192
  );
8070
8193
  var program = new Command();
8071
8194
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
8072
8195
  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) => {
8073
8196
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
8074
- const credPath = path23.join(os19.homedir(), ".node9", "credentials.json");
8075
- if (!fs21.existsSync(path23.dirname(credPath)))
8076
- fs21.mkdirSync(path23.dirname(credPath), { recursive: true });
8197
+ const credPath = path24.join(os20.homedir(), ".node9", "credentials.json");
8198
+ if (!fs22.existsSync(path24.dirname(credPath)))
8199
+ fs22.mkdirSync(path24.dirname(credPath), { recursive: true });
8077
8200
  const profileName = options.profile || "default";
8078
8201
  let existingCreds = {};
8079
8202
  try {
8080
- if (fs21.existsSync(credPath)) {
8081
- const raw = JSON.parse(fs21.readFileSync(credPath, "utf-8"));
8203
+ if (fs22.existsSync(credPath)) {
8204
+ const raw = JSON.parse(fs22.readFileSync(credPath, "utf-8"));
8082
8205
  if (raw.apiKey) {
8083
8206
  existingCreds = {
8084
8207
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -8090,13 +8213,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
8090
8213
  } catch {
8091
8214
  }
8092
8215
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
8093
- fs21.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
8216
+ fs22.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
8094
8217
  if (profileName === "default") {
8095
- const configPath = path23.join(os19.homedir(), ".node9", "config.json");
8218
+ const configPath = path24.join(os20.homedir(), ".node9", "config.json");
8096
8219
  let config = {};
8097
8220
  try {
8098
- if (fs21.existsSync(configPath))
8099
- config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
8221
+ if (fs22.existsSync(configPath))
8222
+ config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
8100
8223
  } catch {
8101
8224
  }
8102
8225
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -8111,36 +8234,36 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
8111
8234
  approvers.cloud = false;
8112
8235
  }
8113
8236
  s.approvers = approvers;
8114
- if (!fs21.existsSync(path23.dirname(configPath)))
8115
- fs21.mkdirSync(path23.dirname(configPath), { recursive: true });
8116
- fs21.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
8237
+ if (!fs22.existsSync(path24.dirname(configPath)))
8238
+ fs22.mkdirSync(path24.dirname(configPath), { recursive: true });
8239
+ fs22.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
8117
8240
  }
8118
8241
  if (options.profile && profileName !== "default") {
8119
- console.log(chalk15.green(`\u2705 Profile "${profileName}" saved`));
8120
- console.log(chalk15.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
8242
+ console.log(chalk16.green(`\u2705 Profile "${profileName}" saved`));
8243
+ console.log(chalk16.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
8121
8244
  } else if (options.local) {
8122
- console.log(chalk15.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
8123
- console.log(chalk15.gray(` All decisions stay on this machine.`));
8245
+ console.log(chalk16.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
8246
+ console.log(chalk16.gray(` All decisions stay on this machine.`));
8124
8247
  } else {
8125
- console.log(chalk15.green(`\u2705 Logged in \u2014 agent mode`));
8126
- console.log(chalk15.gray(` Team policy enforced for all calls via Node9 cloud.`));
8248
+ console.log(chalk16.green(`\u2705 Logged in \u2014 agent mode`));
8249
+ console.log(chalk16.gray(` Team policy enforced for all calls via Node9 cloud.`));
8127
8250
  }
8128
8251
  });
8129
8252
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to protect: claude | gemini | cursor").action(async (target) => {
8130
8253
  if (target === "gemini") return await setupGemini();
8131
8254
  if (target === "claude") return await setupClaude();
8132
8255
  if (target === "cursor") return await setupCursor();
8133
- console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8256
+ console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8134
8257
  process.exit(1);
8135
8258
  });
8136
8259
  program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor").argument("[target]", "The agent to protect: claude | gemini | cursor").action(async (target) => {
8137
8260
  if (!target) {
8138
- console.log(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
8139
- console.log(" Usage: " + chalk15.white("node9 setup <target>") + "\n");
8261
+ console.log(chalk16.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
8262
+ console.log(" Usage: " + chalk16.white("node9 setup <target>") + "\n");
8140
8263
  console.log(" Targets:");
8141
- console.log(" " + chalk15.green("claude") + " \u2014 Claude Code (hook mode)");
8142
- console.log(" " + chalk15.green("gemini") + " \u2014 Gemini CLI (hook mode)");
8143
- console.log(" " + chalk15.green("cursor") + " \u2014 Cursor (hook mode)");
8264
+ console.log(" " + chalk16.green("claude") + " \u2014 Claude Code (hook mode)");
8265
+ console.log(" " + chalk16.green("gemini") + " \u2014 Gemini CLI (hook mode)");
8266
+ console.log(" " + chalk16.green("cursor") + " \u2014 Cursor (hook mode)");
8144
8267
  console.log("");
8145
8268
  return;
8146
8269
  }
@@ -8148,7 +8271,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
8148
8271
  if (t === "gemini") return await setupGemini();
8149
8272
  if (t === "claude") return await setupClaude();
8150
8273
  if (t === "cursor") return await setupCursor();
8151
- console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8274
+ console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8152
8275
  process.exit(1);
8153
8276
  });
8154
8277
  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) => {
@@ -8157,30 +8280,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
8157
8280
  else if (target === "gemini") fn = teardownGemini;
8158
8281
  else if (target === "cursor") fn = teardownCursor;
8159
8282
  else {
8160
- console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8283
+ console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8161
8284
  process.exit(1);
8162
8285
  }
8163
- console.log(chalk15.cyan(`
8286
+ console.log(chalk16.cyan(`
8164
8287
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
8165
8288
  `));
8166
8289
  try {
8167
8290
  fn();
8168
8291
  } catch (err) {
8169
- console.error(chalk15.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
8292
+ console.error(chalk16.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
8170
8293
  process.exit(1);
8171
8294
  }
8172
- console.log(chalk15.gray("\n Restart the agent for changes to take effect."));
8295
+ console.log(chalk16.gray("\n Restart the agent for changes to take effect."));
8173
8296
  });
8174
8297
  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) => {
8175
- console.log(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
8176
- console.log(chalk15.bold("Stopping daemon..."));
8298
+ console.log(chalk16.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
8299
+ console.log(chalk16.bold("Stopping daemon..."));
8177
8300
  try {
8178
8301
  stopDaemon();
8179
- console.log(chalk15.green(" \u2705 Daemon stopped"));
8302
+ console.log(chalk16.green(" \u2705 Daemon stopped"));
8180
8303
  } catch {
8181
- console.log(chalk15.blue(" \u2139\uFE0F Daemon was not running"));
8304
+ console.log(chalk16.blue(" \u2139\uFE0F Daemon was not running"));
8182
8305
  }
8183
- console.log(chalk15.bold("\nRemoving hooks..."));
8306
+ console.log(chalk16.bold("\nRemoving hooks..."));
8184
8307
  let teardownFailed = false;
8185
8308
  for (const [label, fn] of [
8186
8309
  ["Claude", teardownClaude],
@@ -8192,45 +8315,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
8192
8315
  } catch (err) {
8193
8316
  teardownFailed = true;
8194
8317
  console.error(
8195
- chalk15.red(
8318
+ chalk16.red(
8196
8319
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
8197
8320
  )
8198
8321
  );
8199
8322
  }
8200
8323
  }
8201
8324
  if (options.purge) {
8202
- const node9Dir = path23.join(os19.homedir(), ".node9");
8203
- if (fs21.existsSync(node9Dir)) {
8325
+ const node9Dir = path24.join(os20.homedir(), ".node9");
8326
+ if (fs22.existsSync(node9Dir)) {
8204
8327
  const confirmed = await confirm3({
8205
8328
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
8206
8329
  default: false
8207
8330
  });
8208
8331
  if (confirmed) {
8209
- fs21.rmSync(node9Dir, { recursive: true });
8210
- if (fs21.existsSync(node9Dir)) {
8332
+ fs22.rmSync(node9Dir, { recursive: true });
8333
+ if (fs22.existsSync(node9Dir)) {
8211
8334
  console.error(
8212
- chalk15.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
8335
+ chalk16.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
8213
8336
  );
8214
8337
  } else {
8215
- console.log(chalk15.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
8338
+ console.log(chalk16.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
8216
8339
  }
8217
8340
  } else {
8218
- console.log(chalk15.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
8341
+ console.log(chalk16.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
8219
8342
  }
8220
8343
  } else {
8221
- console.log(chalk15.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
8344
+ console.log(chalk16.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
8222
8345
  }
8223
8346
  } else {
8224
8347
  console.log(
8225
- chalk15.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
8348
+ chalk16.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
8226
8349
  );
8227
8350
  }
8228
8351
  if (teardownFailed) {
8229
- console.error(chalk15.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
8352
+ console.error(chalk16.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
8230
8353
  process.exit(1);
8231
8354
  }
8232
- console.log(chalk15.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
8233
- console.log(chalk15.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
8355
+ console.log(chalk16.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
8356
+ console.log(chalk16.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
8234
8357
  });
8235
8358
  registerDoctorCommand(program, version);
8236
8359
  program.command("explain").description(
@@ -8243,7 +8366,7 @@ program.command("explain").description(
8243
8366
  try {
8244
8367
  args = JSON.parse(trimmed);
8245
8368
  } catch {
8246
- console.error(chalk15.red(`
8369
+ console.error(chalk16.red(`
8247
8370
  \u274C Invalid JSON: ${trimmed}
8248
8371
  `));
8249
8372
  process.exit(1);
@@ -8254,63 +8377,63 @@ program.command("explain").description(
8254
8377
  }
8255
8378
  const result = await explainPolicy(tool, args);
8256
8379
  console.log("");
8257
- console.log(chalk15.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
8380
+ console.log(chalk16.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
8258
8381
  console.log("");
8259
- console.log(` ${chalk15.bold("Tool:")} ${chalk15.white(result.tool)}`);
8382
+ console.log(` ${chalk16.bold("Tool:")} ${chalk16.white(result.tool)}`);
8260
8383
  if (argsRaw) {
8261
8384
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
8262
- console.log(` ${chalk15.bold("Input:")} ${chalk15.gray(preview)}`);
8385
+ console.log(` ${chalk16.bold("Input:")} ${chalk16.gray(preview)}`);
8263
8386
  }
8264
8387
  console.log("");
8265
- console.log(chalk15.bold("Config Sources (Waterfall):"));
8388
+ console.log(chalk16.bold("Config Sources (Waterfall):"));
8266
8389
  for (const tier of result.waterfall) {
8267
- const num = chalk15.gray(` ${tier.tier}.`);
8390
+ const num = chalk16.gray(` ${tier.tier}.`);
8268
8391
  const label = tier.label.padEnd(16);
8269
8392
  let statusStr;
8270
8393
  if (tier.tier === 1) {
8271
- statusStr = chalk15.gray(tier.note ?? "");
8394
+ statusStr = chalk16.gray(tier.note ?? "");
8272
8395
  } else if (tier.status === "active") {
8273
- const loc = tier.path ? chalk15.gray(tier.path) : "";
8274
- const note = tier.note ? chalk15.gray(`(${tier.note})`) : "";
8275
- statusStr = chalk15.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
8396
+ const loc = tier.path ? chalk16.gray(tier.path) : "";
8397
+ const note = tier.note ? chalk16.gray(`(${tier.note})`) : "";
8398
+ statusStr = chalk16.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
8276
8399
  } else {
8277
- statusStr = chalk15.gray("\u25CB " + (tier.note ?? "not found"));
8400
+ statusStr = chalk16.gray("\u25CB " + (tier.note ?? "not found"));
8278
8401
  }
8279
- console.log(`${num} ${chalk15.white(label)} ${statusStr}`);
8402
+ console.log(`${num} ${chalk16.white(label)} ${statusStr}`);
8280
8403
  }
8281
8404
  console.log("");
8282
- console.log(chalk15.bold("Policy Evaluation:"));
8405
+ console.log(chalk16.bold("Policy Evaluation:"));
8283
8406
  for (const step of result.steps) {
8284
8407
  const isFinal = step.isFinal;
8285
8408
  let icon;
8286
- if (step.outcome === "allow") icon = chalk15.green(" \u2705");
8287
- else if (step.outcome === "review") icon = chalk15.red(" \u{1F534}");
8288
- else if (step.outcome === "skip") icon = chalk15.gray(" \u2500 ");
8289
- else icon = chalk15.gray(" \u25CB ");
8409
+ if (step.outcome === "allow") icon = chalk16.green(" \u2705");
8410
+ else if (step.outcome === "review") icon = chalk16.red(" \u{1F534}");
8411
+ else if (step.outcome === "skip") icon = chalk16.gray(" \u2500 ");
8412
+ else icon = chalk16.gray(" \u25CB ");
8290
8413
  const name = step.name.padEnd(18);
8291
- const nameStr = isFinal ? chalk15.white.bold(name) : chalk15.white(name);
8292
- const detail = isFinal ? chalk15.white(step.detail) : chalk15.gray(step.detail);
8293
- const arrow = isFinal ? chalk15.yellow(" \u2190 STOP") : "";
8414
+ const nameStr = isFinal ? chalk16.white.bold(name) : chalk16.white(name);
8415
+ const detail = isFinal ? chalk16.white(step.detail) : chalk16.gray(step.detail);
8416
+ const arrow = isFinal ? chalk16.yellow(" \u2190 STOP") : "";
8294
8417
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
8295
8418
  }
8296
8419
  console.log("");
8297
8420
  if (result.decision === "allow") {
8298
- console.log(chalk15.green.bold(" Decision: \u2705 ALLOW") + chalk15.gray(" \u2014 no approval needed"));
8421
+ console.log(chalk16.green.bold(" Decision: \u2705 ALLOW") + chalk16.gray(" \u2014 no approval needed"));
8299
8422
  } else {
8300
8423
  console.log(
8301
- chalk15.red.bold(" Decision: \u{1F534} REVIEW") + chalk15.gray(" \u2014 human approval required")
8424
+ chalk16.red.bold(" Decision: \u{1F534} REVIEW") + chalk16.gray(" \u2014 human approval required")
8302
8425
  );
8303
8426
  if (result.blockedByLabel) {
8304
- console.log(chalk15.gray(` Reason: ${result.blockedByLabel}`));
8427
+ console.log(chalk16.gray(` Reason: ${result.blockedByLabel}`));
8305
8428
  }
8306
8429
  }
8307
8430
  console.log("");
8308
8431
  });
8309
8432
  program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
8310
- const configPath = path23.join(os19.homedir(), ".node9", "config.json");
8311
- if (fs21.existsSync(configPath) && !options.force) {
8312
- console.log(chalk15.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
8313
- console.log(chalk15.gray(` Run with --force to overwrite.`));
8433
+ const configPath = path24.join(os20.homedir(), ".node9", "config.json");
8434
+ if (fs22.existsSync(configPath) && !options.force) {
8435
+ console.log(chalk16.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
8436
+ console.log(chalk16.gray(` Run with --force to overwrite.`));
8314
8437
  return;
8315
8438
  }
8316
8439
  const requestedMode = options.mode.toLowerCase();
@@ -8322,13 +8445,13 @@ program.command("init").description("Create ~/.node9/config.json with default po
8322
8445
  mode: safeMode
8323
8446
  }
8324
8447
  };
8325
- const dir = path23.dirname(configPath);
8326
- if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
8327
- fs21.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
8328
- console.log(chalk15.green(`\u2705 Global config created: ${configPath}`));
8329
- console.log(chalk15.cyan(` Mode set to: ${safeMode}`));
8448
+ const dir = path24.dirname(configPath);
8449
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
8450
+ fs22.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
8451
+ console.log(chalk16.green(`\u2705 Global config created: ${configPath}`));
8452
+ console.log(chalk16.cyan(` Mode set to: ${safeMode}`));
8330
8453
  console.log(
8331
- chalk15.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
8454
+ chalk16.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
8332
8455
  );
8333
8456
  });
8334
8457
  registerAuditCommand(program);
@@ -8339,7 +8462,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
8339
8462
  try {
8340
8463
  await startTail2(options);
8341
8464
  } catch (err) {
8342
- console.error(chalk15.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
8465
+ console.error(chalk16.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
8343
8466
  process.exit(1);
8344
8467
  }
8345
8468
  });
@@ -8351,7 +8474,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
8351
8474
  const ms = parseDuration(options.duration);
8352
8475
  if (ms === null) {
8353
8476
  console.error(
8354
- chalk15.red(`
8477
+ chalk16.red(`
8355
8478
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
8356
8479
  `)
8357
8480
  );
@@ -8359,20 +8482,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
8359
8482
  }
8360
8483
  pauseNode9(ms, options.duration);
8361
8484
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
8362
- console.log(chalk15.yellow(`
8485
+ console.log(chalk16.yellow(`
8363
8486
  \u23F8 Node9 paused until ${expiresAt}`));
8364
- console.log(chalk15.gray(` All tool calls will be allowed without review.`));
8365
- console.log(chalk15.gray(` Run "node9 resume" to re-enable early.
8487
+ console.log(chalk16.gray(` All tool calls will be allowed without review.`));
8488
+ console.log(chalk16.gray(` Run "node9 resume" to re-enable early.
8366
8489
  `));
8367
8490
  });
8368
8491
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
8369
8492
  const { paused } = checkPause();
8370
8493
  if (!paused) {
8371
- console.log(chalk15.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
8494
+ console.log(chalk16.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
8372
8495
  return;
8373
8496
  }
8374
8497
  resumeNode9();
8375
- console.log(chalk15.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
8498
+ console.log(chalk16.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
8376
8499
  });
8377
8500
  var HOOK_BASED_AGENTS = {
8378
8501
  claude: "claude",
@@ -8385,15 +8508,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
8385
8508
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
8386
8509
  const target = HOOK_BASED_AGENTS[firstArg2];
8387
8510
  console.error(
8388
- chalk15.yellow(`
8511
+ chalk16.yellow(`
8389
8512
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
8390
8513
  );
8391
- console.error(chalk15.white(`
8514
+ console.error(chalk16.white(`
8392
8515
  "${target}" uses its own hook system. Use:`));
8393
8516
  console.error(
8394
- chalk15.green(` node9 addto ${target} `) + chalk15.gray("# one-time setup")
8517
+ chalk16.green(` node9 addto ${target} `) + chalk16.gray("# one-time setup")
8395
8518
  );
8396
- console.error(chalk15.green(` ${target} `) + chalk15.gray("# run normally"));
8519
+ console.error(chalk16.green(` ${target} `) + chalk16.gray("# run normally"));
8397
8520
  process.exit(1);
8398
8521
  }
8399
8522
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -8410,7 +8533,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
8410
8533
  }
8411
8534
  );
8412
8535
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
8413
- console.error(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
8536
+ console.error(chalk16.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
8414
8537
  const daemonReady = await autoStartDaemonAndWait();
8415
8538
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
8416
8539
  }
@@ -8423,12 +8546,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
8423
8546
  }
8424
8547
  if (!result.approved) {
8425
8548
  console.error(
8426
- chalk15.red(`
8549
+ chalk16.red(`
8427
8550
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
8428
8551
  );
8429
8552
  process.exit(1);
8430
8553
  }
8431
- console.error(chalk15.green("\n\u2705 Approved \u2014 running command...\n"));
8554
+ console.error(chalk16.green("\n\u2705 Approved \u2014 running command...\n"));
8432
8555
  await runProxy(fullCommand);
8433
8556
  } else {
8434
8557
  program.help();
@@ -8437,14 +8560,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
8437
8560
  registerUndoCommand(program);
8438
8561
  registerShieldCommand(program);
8439
8562
  registerConfigShowCommand(program);
8563
+ registerTrustCommand(program);
8440
8564
  if (process.argv[2] !== "daemon") {
8441
8565
  process.on("unhandledRejection", (reason) => {
8442
8566
  const isCheckHook = process.argv[2] === "check";
8443
8567
  if (isCheckHook) {
8444
8568
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
8445
- const logPath = path23.join(os19.homedir(), ".node9", "hook-debug.log");
8569
+ const logPath = path24.join(os20.homedir(), ".node9", "hook-debug.log");
8446
8570
  const msg = reason instanceof Error ? reason.message : String(reason);
8447
- fs21.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
8571
+ fs22.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
8448
8572
  `);
8449
8573
  }
8450
8574
  process.exit(0);