@node9/proxy 1.3.1 → 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 path22 = issue.path.length > 0 ? issue.path.join(".") : "root";
98
- return ` \u2022 ${path22}: ${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,
@@ -1157,13 +1157,501 @@ var init_dlp = __esm({
1157
1157
  }
1158
1158
  });
1159
1159
 
1160
- // src/policy/index.ts
1160
+ // src/utils/provenance.ts
1161
1161
  import fs5 from "fs";
1162
1162
  import path5 from "path";
1163
1163
  import os4 from "os";
1164
+ function findInPath(cmd) {
1165
+ if (path5.posix.isAbsolute(cmd)) return cmd;
1166
+ const pathEnv = process.env.PATH ?? "";
1167
+ for (const dir of pathEnv.split(path5.delimiter)) {
1168
+ if (!dir) continue;
1169
+ const full = path5.join(dir, cmd);
1170
+ try {
1171
+ fs5.accessSync(full, fs5.constants.X_OK);
1172
+ return full;
1173
+ } catch {
1174
+ }
1175
+ }
1176
+ return null;
1177
+ }
1178
+ function _classifyPath(resolved, cwd) {
1179
+ if (cwd && resolved.startsWith(cwd + "/")) {
1180
+ return { trustLevel: "user", reason: "binary in project directory" };
1181
+ }
1182
+ const osTmp = os4.tmpdir();
1183
+ const allSuspect = osTmp ? [...SUSPECT_PREFIXES, osTmp] : SUSPECT_PREFIXES;
1184
+ if (allSuspect.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1185
+ return { trustLevel: "suspect", reason: `binary in temp directory: ${resolved}` };
1186
+ }
1187
+ if (SYSTEM_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1188
+ return { trustLevel: "system", reason: "" };
1189
+ }
1190
+ if (MANAGED_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1191
+ return { trustLevel: "managed", reason: "" };
1192
+ }
1193
+ if (USER_PREFIXES.some((p) => resolved === p || resolved.startsWith(p + "/"))) {
1194
+ return { trustLevel: "user", reason: "" };
1195
+ }
1196
+ return { trustLevel: "unknown", reason: "binary in unrecognized location" };
1197
+ }
1198
+ function checkProvenance(cmd, cwd) {
1199
+ const bare = cmd.startsWith("./") ? cmd.slice(2) : cmd;
1200
+ if (path5.posix.isAbsolute(bare)) {
1201
+ const early = _classifyPath(bare, cwd);
1202
+ if (early.trustLevel === "suspect") {
1203
+ return { resolvedPath: bare, ...early };
1204
+ }
1205
+ }
1206
+ let resolved;
1207
+ try {
1208
+ const found = findInPath(bare);
1209
+ if (!found) {
1210
+ return {
1211
+ resolvedPath: cmd,
1212
+ trustLevel: "unknown",
1213
+ reason: "binary not found in PATH"
1214
+ };
1215
+ }
1216
+ resolved = fs5.realpathSync(found);
1217
+ } catch {
1218
+ return {
1219
+ resolvedPath: cmd,
1220
+ trustLevel: "unknown",
1221
+ reason: "binary not found in PATH"
1222
+ };
1223
+ }
1224
+ try {
1225
+ const stat = fs5.statSync(resolved);
1226
+ if (stat.mode & 2) {
1227
+ return {
1228
+ resolvedPath: resolved,
1229
+ trustLevel: "suspect",
1230
+ reason: "binary is world-writable"
1231
+ };
1232
+ }
1233
+ } catch {
1234
+ return {
1235
+ resolvedPath: resolved,
1236
+ trustLevel: "unknown",
1237
+ reason: "could not stat binary"
1238
+ };
1239
+ }
1240
+ const classify = _classifyPath(resolved, cwd);
1241
+ return { resolvedPath: resolved, ...classify };
1242
+ }
1243
+ var SYSTEM_PREFIXES, MANAGED_PREFIXES, USER_PREFIXES, SUSPECT_PREFIXES;
1244
+ var init_provenance = __esm({
1245
+ "src/utils/provenance.ts"() {
1246
+ "use strict";
1247
+ SYSTEM_PREFIXES = ["/usr/bin", "/usr/sbin", "/bin", "/sbin"];
1248
+ MANAGED_PREFIXES = ["/usr/local/bin", "/opt/homebrew", "/home/linuxbrew", "/nix/store"];
1249
+ USER_PREFIXES = [
1250
+ path5.join(os4.homedir(), "bin"),
1251
+ path5.join(os4.homedir(), ".local", "bin"),
1252
+ path5.join(os4.homedir(), ".cargo", "bin"),
1253
+ path5.join(os4.homedir(), ".npm-global", "bin"),
1254
+ path5.join(os4.homedir(), ".volta", "bin")
1255
+ ];
1256
+ SUSPECT_PREFIXES = ["/tmp", "/var/tmp", "/dev/shm"];
1257
+ }
1258
+ });
1259
+
1260
+ // src/policy/pipe-chain.ts
1261
+ function isSensitivePath(p) {
1262
+ return SENSITIVE_PATTERNS.some((re) => re.test(p));
1263
+ }
1264
+ function splitOnPipe(cmd) {
1265
+ const segments = [];
1266
+ let current = "";
1267
+ let inSingle = false;
1268
+ let inDouble = false;
1269
+ for (let i = 0; i < cmd.length; i++) {
1270
+ const ch = cmd[i];
1271
+ if (ch === "'" && !inDouble) {
1272
+ inSingle = !inSingle;
1273
+ current += ch;
1274
+ } else if (ch === '"' && !inSingle) {
1275
+ inDouble = !inDouble;
1276
+ current += ch;
1277
+ } else if (ch === "|" && !inSingle && !inDouble && cmd[i + 1] !== "|" && (i === 0 || cmd[i - 1] !== "|")) {
1278
+ segments.push(current.trim());
1279
+ current = "";
1280
+ } else {
1281
+ current += ch;
1282
+ }
1283
+ }
1284
+ if (current.trim()) segments.push(current.trim());
1285
+ return segments.filter(Boolean);
1286
+ }
1287
+ function positionalTokens(segment) {
1288
+ return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
1289
+ }
1290
+ function analyzePipeChain(command) {
1291
+ const segments = splitOnPipe(command);
1292
+ if (segments.length < 2) {
1293
+ return {
1294
+ isPipeline: false,
1295
+ hasSensitiveSource: false,
1296
+ hasExternalSink: false,
1297
+ hasObfuscation: false,
1298
+ sourceFiles: [],
1299
+ sinkTargets: [],
1300
+ risk: "none"
1301
+ };
1302
+ }
1303
+ const sourceFiles = [];
1304
+ const sinkTargets = [];
1305
+ let hasSensitiveSource = false;
1306
+ let hasExternalSink = false;
1307
+ let hasObfuscation = false;
1308
+ for (const segment of segments) {
1309
+ const tokens = segment.split(/\s+/).filter(Boolean);
1310
+ if (tokens.length === 0) continue;
1311
+ const binary = tokens[0].toLowerCase();
1312
+ const args = positionalTokens(segment);
1313
+ if (SOURCE_COMMANDS.has(binary)) {
1314
+ sourceFiles.push(...args);
1315
+ if (args.some(isSensitivePath)) hasSensitiveSource = true;
1316
+ }
1317
+ if (OBFUSCATORS.has(binary)) hasObfuscation = true;
1318
+ if (SINK_COMMANDS.has(binary)) {
1319
+ const targets = args.filter(
1320
+ (a) => a.includes(".") || a.includes("://") || /^\d+\.\d+/.test(a)
1321
+ );
1322
+ sinkTargets.push(...targets);
1323
+ if (targets.length > 0) hasExternalSink = true;
1324
+ }
1325
+ }
1326
+ const fullCmd = command.toLowerCase();
1327
+ if (!hasSensitiveSource) {
1328
+ const redirMatch = fullCmd.match(/<\s*(\S+)/);
1329
+ if (redirMatch && isSensitivePath(redirMatch[1])) {
1330
+ hasSensitiveSource = true;
1331
+ sourceFiles.push(redirMatch[1]);
1332
+ }
1333
+ }
1334
+ const risk = hasSensitiveSource && hasExternalSink && hasObfuscation ? "critical" : hasSensitiveSource && hasExternalSink ? "high" : hasExternalSink ? "medium" : "none";
1335
+ return {
1336
+ isPipeline: true,
1337
+ hasSensitiveSource,
1338
+ hasExternalSink,
1339
+ hasObfuscation,
1340
+ sourceFiles,
1341
+ sinkTargets,
1342
+ risk
1343
+ };
1344
+ }
1345
+ var SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS;
1346
+ var init_pipe_chain = __esm({
1347
+ "src/policy/pipe-chain.ts"() {
1348
+ "use strict";
1349
+ SOURCE_COMMANDS = /* @__PURE__ */ new Set([
1350
+ "cat",
1351
+ "head",
1352
+ "tail",
1353
+ "grep",
1354
+ "awk",
1355
+ "sed",
1356
+ "cut",
1357
+ "sort",
1358
+ "tee",
1359
+ "less",
1360
+ "more",
1361
+ "strings",
1362
+ "xxd"
1363
+ ]);
1364
+ SINK_COMMANDS = /* @__PURE__ */ new Set([
1365
+ "curl",
1366
+ "wget",
1367
+ "nc",
1368
+ "ncat",
1369
+ "netcat",
1370
+ "ssh",
1371
+ "scp",
1372
+ "rsync",
1373
+ "socat",
1374
+ "ftp",
1375
+ "sftp",
1376
+ "telnet"
1377
+ ]);
1378
+ OBFUSCATORS = /* @__PURE__ */ new Set([
1379
+ "base64",
1380
+ "gzip",
1381
+ "gunzip",
1382
+ "bzip2",
1383
+ "xz",
1384
+ "zstd",
1385
+ "openssl",
1386
+ "gpg",
1387
+ "python",
1388
+ "python3",
1389
+ "perl",
1390
+ "ruby",
1391
+ "node"
1392
+ ]);
1393
+ SENSITIVE_PATTERNS = [
1394
+ /(?:^|\/)\.env(?:\.|$)/i,
1395
+ // .env, .env.local, .env.production
1396
+ /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
1397
+ // SSH private keys
1398
+ /\.pem$|\.key$|\.p12$|\.pfx$/i,
1399
+ // certificate files
1400
+ /(?:^|\/)\.ssh\//i,
1401
+ // ~/.ssh/ directory
1402
+ /(?:^|\/)\.aws\/credentials/i,
1403
+ // AWS credentials
1404
+ /(?:^|\/)\.netrc$/i,
1405
+ // netrc (stores HTTP credentials)
1406
+ /(?:^|\/)(passwd|shadow|sudoers)$/i,
1407
+ // /etc/passwd, /etc/shadow
1408
+ /(?:^|\/)credentials(?:\.json)?$/i
1409
+ // generic credentials files
1410
+ ];
1411
+ }
1412
+ });
1413
+
1414
+ // src/policy/flag-tables.ts
1415
+ import path6 from "path";
1416
+ function extractPositionalArgs(tokens, binary) {
1417
+ const binaryName = path6.basename(binary).replace(/\.exe$/i, "");
1418
+ const flagsWithValues = FLAGS_WITH_VALUES[binaryName] ?? /* @__PURE__ */ new Set();
1419
+ const positional = [];
1420
+ let skipNext = false;
1421
+ for (const token of tokens) {
1422
+ if (skipNext) {
1423
+ skipNext = false;
1424
+ continue;
1425
+ }
1426
+ if (token.startsWith("--") && token.includes("=")) continue;
1427
+ if (token.startsWith("-") && token.length === 2 && flagsWithValues.has(token)) {
1428
+ skipNext = true;
1429
+ continue;
1430
+ }
1431
+ if (token.startsWith("--") && flagsWithValues.has(token)) {
1432
+ skipNext = true;
1433
+ continue;
1434
+ }
1435
+ const shortFlag = token.slice(0, 2);
1436
+ if (token.startsWith("-") && token.length > 2 && flagsWithValues.has(shortFlag)) continue;
1437
+ if (token.startsWith("-")) continue;
1438
+ if (token.startsWith("@")) continue;
1439
+ positional.push(token);
1440
+ }
1441
+ return positional;
1442
+ }
1443
+ function extractNetworkTargets(tokens, binary) {
1444
+ return extractPositionalArgs(tokens, binary).map((t) => t.includes("@") ? t.split("@")[1] : t).map((t) => {
1445
+ const colonIdx = t.indexOf(":");
1446
+ if (colonIdx === -1) return t;
1447
+ const afterColon = t.slice(colonIdx + 1);
1448
+ if (/^\d+$/.test(afterColon)) return t.slice(0, colonIdx);
1449
+ return t;
1450
+ }).filter(Boolean);
1451
+ }
1452
+ var FLAGS_WITH_VALUES;
1453
+ var init_flag_tables = __esm({
1454
+ "src/policy/flag-tables.ts"() {
1455
+ "use strict";
1456
+ FLAGS_WITH_VALUES = {
1457
+ curl: /* @__PURE__ */ new Set([
1458
+ "-H",
1459
+ "--header",
1460
+ "-A",
1461
+ "--user-agent",
1462
+ "-e",
1463
+ "--referer",
1464
+ "-x",
1465
+ "--proxy",
1466
+ "-u",
1467
+ "--user",
1468
+ "-d",
1469
+ "--data",
1470
+ "--data-raw",
1471
+ "--data-binary",
1472
+ "-o",
1473
+ "--output",
1474
+ "-F",
1475
+ "--form",
1476
+ "--connect-to",
1477
+ "--resolve",
1478
+ "--cacert",
1479
+ "--cert",
1480
+ "--key",
1481
+ "-m",
1482
+ "--max-time"
1483
+ ]),
1484
+ wget: /* @__PURE__ */ new Set([
1485
+ "-O",
1486
+ "--output-document",
1487
+ "-P",
1488
+ "--directory-prefix",
1489
+ "-U",
1490
+ "--user-agent",
1491
+ "-e",
1492
+ "--execute",
1493
+ "--proxy",
1494
+ "--ca-certificate"
1495
+ ]),
1496
+ nc: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w", "-W", "-I", "-O"]),
1497
+ ncat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "--proxy", "--proxy-auth", "-w", "--wait"]),
1498
+ netcat: /* @__PURE__ */ new Set(["-x", "-p", "-s", "-w"]),
1499
+ ssh: /* @__PURE__ */ new Set([
1500
+ "-i",
1501
+ "-l",
1502
+ "-p",
1503
+ "-o",
1504
+ "-E",
1505
+ "-F",
1506
+ "-J",
1507
+ "-L",
1508
+ "-R",
1509
+ "-W",
1510
+ "-b",
1511
+ "-c",
1512
+ "-D",
1513
+ "-e",
1514
+ "-I",
1515
+ "-S"
1516
+ ]),
1517
+ scp: /* @__PURE__ */ new Set(["-i", "-o", "-P", "-S"]),
1518
+ rsync: /* @__PURE__ */ new Set(["-e", "--rsh", "--rsync-path", "--password-file", "--log-file"]),
1519
+ socat: /* @__PURE__ */ new Set([])
1520
+ // socat uses address syntax, not flags — no value-flags
1521
+ };
1522
+ }
1523
+ });
1524
+
1525
+ // src/policy/ssh-parser.ts
1526
+ function tokenize(cmd) {
1527
+ const tokens = [];
1528
+ let current = "";
1529
+ let inSingle = false;
1530
+ let inDouble = false;
1531
+ for (const ch of cmd) {
1532
+ if (ch === "'" && !inDouble) {
1533
+ inSingle = !inSingle;
1534
+ } else if (ch === '"' && !inSingle) {
1535
+ inDouble = !inDouble;
1536
+ } else if ((ch === " " || ch === " ") && !inSingle && !inDouble) {
1537
+ if (current) {
1538
+ tokens.push(current);
1539
+ current = "";
1540
+ }
1541
+ } else {
1542
+ current += ch;
1543
+ }
1544
+ }
1545
+ if (current) tokens.push(current);
1546
+ return tokens;
1547
+ }
1548
+ function parseHost(raw) {
1549
+ return raw.split("@").pop().split(":")[0];
1550
+ }
1551
+ function extractAllSshHosts(tokens) {
1552
+ const hosts = /* @__PURE__ */ new Set();
1553
+ for (let i = 0; i < tokens.length; i++) {
1554
+ const t = tokens[i];
1555
+ if (t === "-J" && tokens[i + 1]) {
1556
+ for (const hop of tokens[++i].split(",")) {
1557
+ const h = parseHost(hop);
1558
+ if (h) hosts.add(h);
1559
+ }
1560
+ continue;
1561
+ }
1562
+ if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxyjump=")) {
1563
+ const val = tokens[++i].split("=").slice(1).join("=");
1564
+ for (const hop of val.split(",")) {
1565
+ const h = parseHost(hop);
1566
+ if (h) hosts.add(h);
1567
+ }
1568
+ continue;
1569
+ }
1570
+ if (t === "-o" && tokens[i + 1]?.toLowerCase().startsWith("proxycommand=")) {
1571
+ const raw = tokens[++i].split("=").slice(1).join("=").replace(/^['"]|['"]$/g, "");
1572
+ const subTokens = tokenize(raw);
1573
+ const binary = subTokens[0] ?? "";
1574
+ extractNetworkTargets(subTokens.slice(1), binary).forEach((h) => hosts.add(h));
1575
+ extractAllSshHosts(subTokens.slice(1)).forEach((h) => hosts.add(h));
1576
+ continue;
1577
+ }
1578
+ if (!t.startsWith("-")) {
1579
+ const h = parseHost(t);
1580
+ if (h) hosts.add(h);
1581
+ }
1582
+ }
1583
+ return [...hosts].filter(Boolean);
1584
+ }
1585
+ var init_ssh_parser = __esm({
1586
+ "src/policy/ssh-parser.ts"() {
1587
+ "use strict";
1588
+ init_flag_tables();
1589
+ }
1590
+ });
1591
+
1592
+ // src/auth/trusted-hosts.ts
1593
+ import fs6 from "fs";
1594
+ import path7 from "path";
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";
1164
1652
  import pm from "picomatch";
1165
1653
  import { parse } from "sh-syntax";
1166
- function tokenize(toolName) {
1654
+ function tokenize2(toolName) {
1167
1655
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
1168
1656
  }
1169
1657
  function matchesPattern(text, patterns) {
@@ -1176,9 +1664,9 @@ function matchesPattern(text, patterns) {
1176
1664
  const withoutDotSlash = text.replace(/^\.\//, "");
1177
1665
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1178
1666
  }
1179
- function getNestedValue(obj, path22) {
1667
+ function getNestedValue(obj, path25) {
1180
1668
  if (!obj || typeof obj !== "object") return null;
1181
- return path22.split(".").reduce((prev, curr) => prev?.[curr], obj);
1669
+ return path25.split(".").reduce((prev, curr) => prev?.[curr], obj);
1182
1670
  }
1183
1671
  function shouldSnapshot(toolName, args, config) {
1184
1672
  if (!config.settings.enableUndo) return false;
@@ -1313,7 +1801,7 @@ async function analyzeShellCommand(command) {
1313
1801
  }
1314
1802
  return { actions, paths, allTokens };
1315
1803
  }
1316
- async function evaluatePolicy(toolName, args, agent) {
1804
+ async function evaluatePolicy(toolName, args, agent, cwd) {
1317
1805
  const config = getConfig();
1318
1806
  if (matchesPattern(toolName, config.policy.ignoredTools)) return { decision: "allow" };
1319
1807
  if (config.policy.smartRules.length > 0) {
@@ -1343,11 +1831,66 @@ async function evaluatePolicy(toolName, args, agent) {
1343
1831
  if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
1344
1832
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)", tier: 3 };
1345
1833
  }
1834
+ const pipeAnalysis = analyzePipeChain(shellCommand);
1835
+ if (pipeAnalysis.isPipeline && (pipeAnalysis.risk === "critical" || pipeAnalysis.risk === "high")) {
1836
+ const sinks = pipeAnalysis.sinkTargets;
1837
+ const allTrusted = sinks.length > 0 && sinks.every(isTrustedHost);
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
+ }
1847
+ return {
1848
+ decision: "block",
1849
+ blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
1850
+ reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
1851
+ tier: 3
1852
+ };
1853
+ }
1854
+ if (allTrusted) {
1855
+ return { decision: "allow" };
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
+ };
1863
+ }
1864
+ const firstToken = analyzed.actions[0] ?? "";
1865
+ if (["ssh", "scp", "rsync"].includes(firstToken)) {
1866
+ const rawTokens = shellCommand.trim().split(/\s+/);
1867
+ const sshHosts = extractAllSshHosts(rawTokens.slice(1));
1868
+ allTokens.push(...sshHosts);
1869
+ }
1870
+ if (firstToken && path8.posix.isAbsolute(firstToken)) {
1871
+ const prov = checkProvenance(firstToken, cwd);
1872
+ if (prov.trustLevel === "suspect") {
1873
+ return {
1874
+ decision: config.settings.mode === "strict" ? "block" : "review",
1875
+ blockedByLabel: "Node9: Suspect Binary",
1876
+ reason: `Binary "${firstToken}" resolved to ${prov.resolvedPath} \u2014 ${prov.reason}`,
1877
+ tier: 3
1878
+ };
1879
+ }
1880
+ if (prov.trustLevel === "unknown" && config.settings.mode === "strict") {
1881
+ return {
1882
+ decision: "review",
1883
+ blockedByLabel: "Node9: Unknown Binary (strict mode)",
1884
+ reason: `Binary "${firstToken}" \u2014 ${prov.reason}`,
1885
+ tier: 3
1886
+ };
1887
+ }
1888
+ }
1346
1889
  if (isSqlTool(toolName, config.policy.toolInspection)) {
1347
1890
  allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
1348
1891
  }
1349
1892
  } else {
1350
- allTokens = tokenize(toolName);
1893
+ allTokens = tokenize2(toolName);
1351
1894
  if (args && typeof args === "object") {
1352
1895
  const flattenedArgs = JSON.stringify(args).toLowerCase();
1353
1896
  const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
@@ -1421,9 +1964,9 @@ async function evaluatePolicy(toolName, args, agent) {
1421
1964
  }
1422
1965
  async function explainPolicy(toolName, args) {
1423
1966
  const steps = [];
1424
- const globalPath = path5.join(os4.homedir(), ".node9", "config.json");
1425
- const projectPath = path5.join(process.cwd(), "node9.config.json");
1426
- const credsPath = path5.join(os4.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");
1427
1970
  const waterfall = [
1428
1971
  {
1429
1972
  tier: 1,
@@ -1434,19 +1977,19 @@ async function explainPolicy(toolName, args) {
1434
1977
  {
1435
1978
  tier: 2,
1436
1979
  label: "Cloud policy",
1437
- status: fs5.existsSync(credsPath) ? "active" : "missing",
1438
- note: fs5.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"
1439
1982
  },
1440
1983
  {
1441
1984
  tier: 3,
1442
1985
  label: "Project config",
1443
- status: fs5.existsSync(projectPath) ? "active" : "missing",
1986
+ status: fs7.existsSync(projectPath) ? "active" : "missing",
1444
1987
  path: projectPath
1445
1988
  },
1446
1989
  {
1447
1990
  tier: 4,
1448
1991
  label: "Global config",
1449
- status: fs5.existsSync(globalPath) ? "active" : "missing",
1992
+ status: fs7.existsSync(globalPath) ? "active" : "missing",
1450
1993
  path: globalPath
1451
1994
  },
1452
1995
  {
@@ -1578,7 +2121,7 @@ async function explainPolicy(toolName, args) {
1578
2121
  });
1579
2122
  }
1580
2123
  } else {
1581
- allTokens = tokenize(toolName);
2124
+ allTokens = tokenize2(toolName);
1582
2125
  let detail = `No toolInspection match for "${toolName}" \u2014 tokens: [${allTokens.join(", ")}]`;
1583
2126
  if (args && typeof args === "object") {
1584
2127
  const flattenedArgs = JSON.stringify(args).toLowerCase();
@@ -1690,21 +2233,25 @@ var init_policy = __esm({
1690
2233
  init_dlp();
1691
2234
  init_config();
1692
2235
  init_regex();
2236
+ init_provenance();
2237
+ init_pipe_chain();
2238
+ init_ssh_parser();
2239
+ init_trusted_hosts();
1693
2240
  SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
1694
2241
  }
1695
2242
  });
1696
2243
 
1697
2244
  // src/auth/state.ts
1698
- import fs6 from "fs";
1699
- import path6 from "path";
1700
- import os5 from "os";
2245
+ import fs8 from "fs";
2246
+ import path9 from "path";
2247
+ import os7 from "os";
1701
2248
  function checkPause() {
1702
2249
  try {
1703
- if (!fs6.existsSync(PAUSED_FILE)) return { paused: false };
1704
- const state = JSON.parse(fs6.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"));
1705
2252
  if (state.expiry > 0 && Date.now() >= state.expiry) {
1706
2253
  try {
1707
- fs6.unlinkSync(PAUSED_FILE);
2254
+ fs8.unlinkSync(PAUSED_FILE);
1708
2255
  } catch {
1709
2256
  }
1710
2257
  return { paused: false };
@@ -1715,11 +2262,11 @@ function checkPause() {
1715
2262
  }
1716
2263
  }
1717
2264
  function atomicWriteSync(filePath, data, options) {
1718
- const dir = path6.dirname(filePath);
1719
- if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
1720
- const tmpPath = `${filePath}.${os5.hostname()}.${process.pid}.tmp`;
1721
- fs6.writeFileSync(tmpPath, data, options);
1722
- fs6.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);
1723
2270
  }
1724
2271
  function pauseNode9(durationMs, durationStr) {
1725
2272
  const state = { expiry: Date.now() + durationMs, duration: durationStr };
@@ -1727,18 +2274,18 @@ function pauseNode9(durationMs, durationStr) {
1727
2274
  }
1728
2275
  function resumeNode9() {
1729
2276
  try {
1730
- if (fs6.existsSync(PAUSED_FILE)) fs6.unlinkSync(PAUSED_FILE);
2277
+ if (fs8.existsSync(PAUSED_FILE)) fs8.unlinkSync(PAUSED_FILE);
1731
2278
  } catch {
1732
2279
  }
1733
2280
  }
1734
2281
  function getActiveTrustSession(toolName) {
1735
2282
  try {
1736
- if (!fs6.existsSync(TRUST_FILE)) return false;
1737
- const trust = JSON.parse(fs6.readFileSync(TRUST_FILE, "utf-8"));
2283
+ if (!fs8.existsSync(TRUST_FILE)) return false;
2284
+ const trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
1738
2285
  const now = Date.now();
1739
2286
  const active = trust.entries.filter((e) => e.expiry > now);
1740
2287
  if (active.length !== trust.entries.length) {
1741
- fs6.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
2288
+ fs8.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
1742
2289
  }
1743
2290
  return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
1744
2291
  } catch {
@@ -1749,8 +2296,8 @@ function writeTrustSession(toolName, durationMs) {
1749
2296
  try {
1750
2297
  let trust = { entries: [] };
1751
2298
  try {
1752
- if (fs6.existsSync(TRUST_FILE)) {
1753
- trust = JSON.parse(fs6.readFileSync(TRUST_FILE, "utf-8"));
2299
+ if (fs8.existsSync(TRUST_FILE)) {
2300
+ trust = JSON.parse(fs8.readFileSync(TRUST_FILE, "utf-8"));
1754
2301
  }
1755
2302
  } catch {
1756
2303
  }
@@ -1766,9 +2313,9 @@ function writeTrustSession(toolName, durationMs) {
1766
2313
  }
1767
2314
  function getPersistentDecision(toolName) {
1768
2315
  try {
1769
- const file = path6.join(os5.homedir(), ".node9", "decisions.json");
1770
- if (!fs6.existsSync(file)) return null;
1771
- const decisions = JSON.parse(fs6.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"));
1772
2319
  const d = decisions[toolName];
1773
2320
  if (d === "allow" || d === "deny") return d;
1774
2321
  } catch {
@@ -1780,21 +2327,21 @@ var init_state = __esm({
1780
2327
  "src/auth/state.ts"() {
1781
2328
  "use strict";
1782
2329
  init_policy();
1783
- PAUSED_FILE = path6.join(os5.homedir(), ".node9", "PAUSED");
1784
- TRUST_FILE = path6.join(os5.homedir(), ".node9", "trust.json");
2330
+ PAUSED_FILE = path9.join(os7.homedir(), ".node9", "PAUSED");
2331
+ TRUST_FILE = path9.join(os7.homedir(), ".node9", "trust.json");
1785
2332
  }
1786
2333
  });
1787
2334
 
1788
2335
  // src/auth/daemon.ts
1789
- import fs7 from "fs";
1790
- import path7 from "path";
1791
- import os6 from "os";
2336
+ import fs9 from "fs";
2337
+ import path10 from "path";
2338
+ import os8 from "os";
1792
2339
  import { spawnSync } from "child_process";
1793
2340
  function getInternalToken() {
1794
2341
  try {
1795
- const pidFile = path7.join(os6.homedir(), ".node9", "daemon.pid");
1796
- if (!fs7.existsSync(pidFile)) return null;
1797
- const data = JSON.parse(fs7.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"));
1798
2345
  process.kill(data.pid, 0);
1799
2346
  return data.internalToken ?? null;
1800
2347
  } catch {
@@ -1802,10 +2349,10 @@ function getInternalToken() {
1802
2349
  }
1803
2350
  }
1804
2351
  function isDaemonRunning() {
1805
- const pidFile = path7.join(os6.homedir(), ".node9", "daemon.pid");
1806
- if (fs7.existsSync(pidFile)) {
2352
+ const pidFile = path10.join(os8.homedir(), ".node9", "daemon.pid");
2353
+ if (fs9.existsSync(pidFile)) {
1807
2354
  try {
1808
- const { pid, port } = JSON.parse(fs7.readFileSync(pidFile, "utf-8"));
2355
+ const { pid, port } = JSON.parse(fs9.readFileSync(pidFile, "utf-8"));
1809
2356
  if (port !== DAEMON_PORT) return false;
1810
2357
  process.kill(pid, 0);
1811
2358
  return true;
@@ -1908,7 +2455,7 @@ var init_daemon = __esm({
1908
2455
  });
1909
2456
 
1910
2457
  // src/context-sniper.ts
1911
- import path8 from "path";
2458
+ import path11 from "path";
1912
2459
  function smartTruncate(str, maxLen = 500) {
1913
2460
  if (str.length <= maxLen) return str;
1914
2461
  const edge = Math.floor(maxLen / 2) - 3;
@@ -1960,7 +2507,7 @@ function computeRiskMetadata(args, tier, blockedByLabel, matchedField, matchedWo
1960
2507
  intent = "EDIT";
1961
2508
  if (obj.file_path) {
1962
2509
  editFilePath = String(obj.file_path);
1963
- editFileName = path8.basename(editFilePath);
2510
+ editFileName = path11.basename(editFilePath);
1964
2511
  }
1965
2512
  const result = extractContext(String(obj.new_string), matchedWord);
1966
2513
  contextSnippet = result.snippet;
@@ -2017,7 +2564,7 @@ var init_context_sniper = __esm({
2017
2564
 
2018
2565
  // src/ui/native.ts
2019
2566
  import { spawn } from "child_process";
2020
- import path9 from "path";
2567
+ import path12 from "path";
2021
2568
  function formatArgs(args, matchedField, matchedWord) {
2022
2569
  if (args === null || args === void 0) return { message: "(none)", intent: "EXEC" };
2023
2570
  let parsed = args;
@@ -2036,7 +2583,7 @@ function formatArgs(args, matchedField, matchedWord) {
2036
2583
  if (typeof parsed === "object" && !Array.isArray(parsed)) {
2037
2584
  const obj = parsed;
2038
2585
  if (obj.old_string !== void 0 && obj.new_string !== void 0) {
2039
- const file = obj.file_path ? path9.basename(String(obj.file_path)) : "file";
2586
+ const file = obj.file_path ? path12.basename(String(obj.file_path)) : "file";
2040
2587
  const oldPreview = smartTruncate(String(obj.old_string), 120);
2041
2588
  const newPreview = extractContext(String(obj.new_string), matchedWord).snippet;
2042
2589
  return {
@@ -2221,8 +2768,8 @@ var init_native = __esm({
2221
2768
  });
2222
2769
 
2223
2770
  // src/auth/cloud.ts
2224
- import fs8 from "fs";
2225
- import os7 from "os";
2771
+ import fs10 from "fs";
2772
+ import os9 from "os";
2226
2773
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2227
2774
  return fetch(`${creds.apiUrl}/audit`, {
2228
2775
  method: "POST",
@@ -2234,9 +2781,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2234
2781
  context: {
2235
2782
  agent: meta?.agent,
2236
2783
  mcpServer: meta?.mcpServer,
2237
- hostname: os7.hostname(),
2784
+ hostname: os9.hostname(),
2238
2785
  cwd: process.cwd(),
2239
- platform: os7.platform()
2786
+ platform: os9.platform()
2240
2787
  }
2241
2788
  }),
2242
2789
  signal: AbortSignal.timeout(5e3)
@@ -2257,9 +2804,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2257
2804
  context: {
2258
2805
  agent: meta?.agent,
2259
2806
  mcpServer: meta?.mcpServer,
2260
- hostname: os7.hostname(),
2807
+ hostname: os9.hostname(),
2261
2808
  cwd: process.cwd(),
2262
- platform: os7.platform()
2809
+ platform: os9.platform()
2263
2810
  },
2264
2811
  ...riskMetadata && { riskMetadata }
2265
2812
  }),
@@ -2315,14 +2862,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
2315
2862
  });
2316
2863
  clearTimeout(timer);
2317
2864
  if (!res.ok) {
2318
- fs8.appendFileSync(
2865
+ fs10.appendFileSync(
2319
2866
  HOOK_DEBUG_LOG,
2320
2867
  `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
2321
2868
  `
2322
2869
  );
2323
2870
  }
2324
2871
  } catch (err) {
2325
- fs8.appendFileSync(
2872
+ fs10.appendFileSync(
2326
2873
  HOOK_DEBUG_LOG,
2327
2874
  `[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
2328
2875
  `
@@ -2338,8 +2885,8 @@ var init_cloud = __esm({
2338
2885
 
2339
2886
  // src/auth/orchestrator.ts
2340
2887
  import net from "net";
2341
- import path10 from "path";
2342
- import os8 from "os";
2888
+ import path13 from "path";
2889
+ import os10 from "os";
2343
2890
  import { randomUUID } from "crypto";
2344
2891
  function notifyActivity(data) {
2345
2892
  return new Promise((resolve) => {
@@ -2422,7 +2969,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2422
2969
  }
2423
2970
  if (config.settings.mode === "audit") {
2424
2971
  if (!isIgnoredTool(toolName)) {
2425
- const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
2972
+ const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
2426
2973
  if (policyResult.decision === "review") {
2427
2974
  appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
2428
2975
  if (approvers.cloud && creds?.apiKey) {
@@ -2700,7 +3247,7 @@ var init_orchestrator = __esm({
2700
3247
  init_state();
2701
3248
  init_daemon();
2702
3249
  init_cloud();
2703
- ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path10.join(os8.tmpdir(), "node9-activity.sock");
3250
+ ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path13.join(os10.tmpdir(), "node9-activity.sock");
2704
3251
  }
2705
3252
  });
2706
3253
 
@@ -4172,9 +4719,9 @@ var init_ui2 = __esm({
4172
4719
 
4173
4720
  // src/daemon/state.ts
4174
4721
  import net2 from "net";
4175
- import fs10 from "fs";
4176
- import path12 from "path";
4177
- import os10 from "os";
4722
+ import fs12 from "fs";
4723
+ import path15 from "path";
4724
+ import os12 from "os";
4178
4725
  import { spawn as spawn2 } from "child_process";
4179
4726
  import { randomUUID as randomUUID2 } from "crypto";
4180
4727
  function getAbandonTimer() {
@@ -4199,11 +4746,11 @@ function markRejectionHandlerRegistered() {
4199
4746
  daemonRejectionHandlerRegistered = true;
4200
4747
  }
4201
4748
  function atomicWriteSync2(filePath, data, options) {
4202
- const dir = path12.dirname(filePath);
4203
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
4749
+ const dir = path15.dirname(filePath);
4750
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
4204
4751
  const tmpPath = `${filePath}.${randomUUID2()}.tmp`;
4205
- fs10.writeFileSync(tmpPath, data, options);
4206
- fs10.renameSync(tmpPath, filePath);
4752
+ fs12.writeFileSync(tmpPath, data, options);
4753
+ fs12.renameSync(tmpPath, filePath);
4207
4754
  }
4208
4755
  function redactArgs(value) {
4209
4756
  if (!value || typeof value !== "object") return value;
@@ -4223,16 +4770,16 @@ function appendAuditLog(data) {
4223
4770
  decision: data.decision,
4224
4771
  source: "daemon"
4225
4772
  };
4226
- const dir = path12.dirname(AUDIT_LOG_FILE);
4227
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
4228
- fs10.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");
4229
4776
  } catch {
4230
4777
  }
4231
4778
  }
4232
4779
  function getAuditHistory(limit = 20) {
4233
4780
  try {
4234
- if (!fs10.existsSync(AUDIT_LOG_FILE)) return [];
4235
- const lines = fs10.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");
4236
4783
  if (lines.length === 1 && lines[0] === "") return [];
4237
4784
  return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
4238
4785
  } catch {
@@ -4241,19 +4788,19 @@ function getAuditHistory(limit = 20) {
4241
4788
  }
4242
4789
  function getOrgName() {
4243
4790
  try {
4244
- if (fs10.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
4791
+ if (fs12.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
4245
4792
  } catch {
4246
4793
  }
4247
4794
  return null;
4248
4795
  }
4249
4796
  function hasStoredSlackKey() {
4250
- return fs10.existsSync(CREDENTIALS_FILE);
4797
+ return fs12.existsSync(CREDENTIALS_FILE);
4251
4798
  }
4252
4799
  function writeGlobalSetting(key, value) {
4253
4800
  let config = {};
4254
4801
  try {
4255
- if (fs10.existsSync(GLOBAL_CONFIG_FILE)) {
4256
- config = JSON.parse(fs10.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
4802
+ if (fs12.existsSync(GLOBAL_CONFIG_FILE)) {
4803
+ config = JSON.parse(fs12.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
4257
4804
  }
4258
4805
  } catch {
4259
4806
  }
@@ -4265,8 +4812,8 @@ function writeTrustEntry(toolName, durationMs) {
4265
4812
  try {
4266
4813
  let trust = { entries: [] };
4267
4814
  try {
4268
- if (fs10.existsSync(TRUST_FILE2))
4269
- trust = JSON.parse(fs10.readFileSync(TRUST_FILE2, "utf-8"));
4815
+ if (fs12.existsSync(TRUST_FILE2))
4816
+ trust = JSON.parse(fs12.readFileSync(TRUST_FILE2, "utf-8"));
4270
4817
  } catch {
4271
4818
  }
4272
4819
  trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
@@ -4277,8 +4824,8 @@ function writeTrustEntry(toolName, durationMs) {
4277
4824
  }
4278
4825
  function readPersistentDecisions() {
4279
4826
  try {
4280
- if (fs10.existsSync(DECISIONS_FILE)) {
4281
- return JSON.parse(fs10.readFileSync(DECISIONS_FILE, "utf-8"));
4827
+ if (fs12.existsSync(DECISIONS_FILE)) {
4828
+ return JSON.parse(fs12.readFileSync(DECISIONS_FILE, "utf-8"));
4282
4829
  }
4283
4830
  } catch {
4284
4831
  }
@@ -4343,7 +4890,7 @@ function abandonPending() {
4343
4890
  });
4344
4891
  if (autoStarted) {
4345
4892
  try {
4346
- fs10.unlinkSync(DAEMON_PID_FILE);
4893
+ fs12.unlinkSync(DAEMON_PID_FILE);
4347
4894
  } catch {
4348
4895
  }
4349
4896
  setTimeout(() => {
@@ -4354,7 +4901,7 @@ function abandonPending() {
4354
4901
  }
4355
4902
  function startActivitySocket() {
4356
4903
  try {
4357
- fs10.unlinkSync(ACTIVITY_SOCKET_PATH2);
4904
+ fs12.unlinkSync(ACTIVITY_SOCKET_PATH2);
4358
4905
  } catch {
4359
4906
  }
4360
4907
  const ACTIVITY_MAX_BYTES = 1024 * 1024;
@@ -4396,7 +4943,7 @@ function startActivitySocket() {
4396
4943
  unixServer.listen(ACTIVITY_SOCKET_PATH2);
4397
4944
  process.on("exit", () => {
4398
4945
  try {
4399
- fs10.unlinkSync(ACTIVITY_SOCKET_PATH2);
4946
+ fs12.unlinkSync(ACTIVITY_SOCKET_PATH2);
4400
4947
  } catch {
4401
4948
  }
4402
4949
  });
@@ -4406,13 +4953,13 @@ var init_state2 = __esm({
4406
4953
  "src/daemon/state.ts"() {
4407
4954
  "use strict";
4408
4955
  init_daemon();
4409
- homeDir = os10.homedir();
4410
- DAEMON_PID_FILE = path12.join(homeDir, ".node9", "daemon.pid");
4411
- DECISIONS_FILE = path12.join(homeDir, ".node9", "decisions.json");
4412
- AUDIT_LOG_FILE = path12.join(homeDir, ".node9", "audit.log");
4413
- TRUST_FILE2 = path12.join(homeDir, ".node9", "trust.json");
4414
- GLOBAL_CONFIG_FILE = path12.join(homeDir, ".node9", "config.json");
4415
- CREDENTIALS_FILE = path12.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");
4416
4963
  pending = /* @__PURE__ */ new Map();
4417
4964
  sseClients = /* @__PURE__ */ new Set();
4418
4965
  _abandonTimer = null;
@@ -4426,7 +4973,7 @@ var init_state2 = __esm({
4426
4973
  "2h": 2 * 60 * 6e4
4427
4974
  };
4428
4975
  autoStarted = process.env.NODE9_AUTO_STARTED === "1";
4429
- ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path12.join(os10.tmpdir(), "node9-activity.sock");
4976
+ ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path15.join(os12.tmpdir(), "node9-activity.sock");
4430
4977
  ACTIVITY_RING_SIZE = 100;
4431
4978
  activityRing = [];
4432
4979
  SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
@@ -4435,8 +4982,8 @@ var init_state2 = __esm({
4435
4982
 
4436
4983
  // src/daemon/server.ts
4437
4984
  import http from "http";
4438
- import fs11 from "fs";
4439
- import path13 from "path";
4985
+ import fs13 from "fs";
4986
+ import path16 from "path";
4440
4987
  import { randomUUID as randomUUID3 } from "crypto";
4441
4988
  import { spawnSync as spawnSync2 } from "child_process";
4442
4989
  import chalk2 from "chalk";
@@ -4455,7 +5002,7 @@ function startDaemon() {
4455
5002
  idleTimer = setTimeout(() => {
4456
5003
  if (autoStarted) {
4457
5004
  try {
4458
- fs11.unlinkSync(DAEMON_PID_FILE);
5005
+ fs13.unlinkSync(DAEMON_PID_FILE);
4459
5006
  } catch {
4460
5007
  }
4461
5008
  }
@@ -4597,7 +5144,7 @@ data: ${JSON.stringify(item.data)}
4597
5144
  status: "pending"
4598
5145
  });
4599
5146
  }
4600
- const projectCwd = typeof cwd === "string" && path13.isAbsolute(cwd) ? cwd : void 0;
5147
+ const projectCwd = typeof cwd === "string" && path16.isAbsolute(cwd) ? cwd : void 0;
4601
5148
  const projectConfig = getConfig(projectCwd);
4602
5149
  const browserEnabled = projectConfig.settings.approvers?.browser !== false;
4603
5150
  const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
@@ -4901,14 +5448,14 @@ data: ${JSON.stringify(item.data)}
4901
5448
  server.on("error", (e) => {
4902
5449
  if (e.code === "EADDRINUSE") {
4903
5450
  try {
4904
- if (fs11.existsSync(DAEMON_PID_FILE)) {
4905
- const { pid } = JSON.parse(fs11.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"));
4906
5453
  process.kill(pid, 0);
4907
5454
  return process.exit(0);
4908
5455
  }
4909
5456
  } catch {
4910
5457
  try {
4911
- fs11.unlinkSync(DAEMON_PID_FILE);
5458
+ fs13.unlinkSync(DAEMON_PID_FILE);
4912
5459
  } catch {
4913
5460
  }
4914
5461
  server.listen(DAEMON_PORT, DAEMON_HOST);
@@ -4978,28 +5525,28 @@ var init_server = __esm({
4978
5525
  });
4979
5526
 
4980
5527
  // src/daemon/index.ts
4981
- import fs12 from "fs";
5528
+ import fs14 from "fs";
4982
5529
  import chalk3 from "chalk";
4983
5530
  import { spawnSync as spawnSync3 } from "child_process";
4984
5531
  function stopDaemon() {
4985
- if (!fs12.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."));
4986
5533
  try {
4987
- const { pid } = JSON.parse(fs12.readFileSync(DAEMON_PID_FILE, "utf-8"));
5534
+ const { pid } = JSON.parse(fs14.readFileSync(DAEMON_PID_FILE, "utf-8"));
4988
5535
  process.kill(pid, "SIGTERM");
4989
5536
  console.log(chalk3.green("\u2705 Stopped."));
4990
5537
  } catch {
4991
5538
  console.log(chalk3.gray("Cleaned up stale PID file."));
4992
5539
  } finally {
4993
5540
  try {
4994
- fs12.unlinkSync(DAEMON_PID_FILE);
5541
+ fs14.unlinkSync(DAEMON_PID_FILE);
4995
5542
  } catch {
4996
5543
  }
4997
5544
  }
4998
5545
  }
4999
5546
  function daemonStatus() {
5000
- if (fs12.existsSync(DAEMON_PID_FILE)) {
5547
+ if (fs14.existsSync(DAEMON_PID_FILE)) {
5001
5548
  try {
5002
- const { pid } = JSON.parse(fs12.readFileSync(DAEMON_PID_FILE, "utf-8"));
5549
+ const { pid } = JSON.parse(fs14.readFileSync(DAEMON_PID_FILE, "utf-8"));
5003
5550
  process.kill(pid, 0);
5004
5551
  console.log(chalk3.green("Node9 daemon: running"));
5005
5552
  return;
@@ -5033,10 +5580,10 @@ __export(tail_exports, {
5033
5580
  startTail: () => startTail
5034
5581
  });
5035
5582
  import http2 from "http";
5036
- import chalk14 from "chalk";
5037
- import fs19 from "fs";
5038
- import os17 from "os";
5039
- import path20 from "path";
5583
+ import chalk15 from "chalk";
5584
+ import fs21 from "fs";
5585
+ import os19 from "os";
5586
+ import path23 from "path";
5040
5587
  import readline3 from "readline";
5041
5588
  import { spawn as spawn9, execSync as execSync3 } from "child_process";
5042
5589
  function getIcon(tool) {
@@ -5052,17 +5599,17 @@ function formatBase(activity) {
5052
5599
  const toolName = activity.tool.slice(0, 16).padEnd(16);
5053
5600
  const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
5054
5601
  const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
5055
- 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)}`;
5056
5603
  }
5057
5604
  function renderResult(activity, result) {
5058
5605
  const base = formatBase(activity);
5059
5606
  let status;
5060
5607
  if (result.status === "allow") {
5061
- status = chalk14.green("\u2713 ALLOW");
5608
+ status = chalk15.green("\u2713 ALLOW");
5062
5609
  } else if (result.status === "dlp") {
5063
- status = chalk14.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5610
+ status = chalk15.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
5064
5611
  } else {
5065
- status = chalk14.red("\u2717 BLOCK");
5612
+ status = chalk15.red("\u2717 BLOCK");
5066
5613
  }
5067
5614
  if (process.stdout.isTTY) {
5068
5615
  readline3.clearLine(process.stdout, 0);
@@ -5072,16 +5619,16 @@ function renderResult(activity, result) {
5072
5619
  }
5073
5620
  function renderPending(activity) {
5074
5621
  if (!process.stdout.isTTY) return;
5075
- process.stdout.write(`${formatBase(activity)} ${chalk14.yellow("\u25CF \u2026")}\r`);
5622
+ process.stdout.write(`${formatBase(activity)} ${chalk15.yellow("\u25CF \u2026")}\r`);
5076
5623
  }
5077
5624
  async function ensureDaemon() {
5078
5625
  let pidPort = null;
5079
- if (fs19.existsSync(PID_FILE)) {
5626
+ if (fs21.existsSync(PID_FILE)) {
5080
5627
  try {
5081
- const { port } = JSON.parse(fs19.readFileSync(PID_FILE, "utf-8"));
5628
+ const { port } = JSON.parse(fs21.readFileSync(PID_FILE, "utf-8"));
5082
5629
  pidPort = port;
5083
5630
  } catch {
5084
- 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."));
5085
5632
  }
5086
5633
  }
5087
5634
  const checkPort = pidPort ?? DAEMON_PORT;
@@ -5092,7 +5639,7 @@ async function ensureDaemon() {
5092
5639
  if (res.ok) return checkPort;
5093
5640
  } catch {
5094
5641
  }
5095
- console.log(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5642
+ console.log(chalk15.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
5096
5643
  const child = spawn9(process.execPath, [process.argv[1], "daemon"], {
5097
5644
  detached: true,
5098
5645
  stdio: "ignore",
@@ -5109,7 +5656,7 @@ async function ensureDaemon() {
5109
5656
  } catch {
5110
5657
  }
5111
5658
  }
5112
- 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"));
5113
5660
  process.exit(1);
5114
5661
  }
5115
5662
  function postDecisionHttp(id, decision, csrfToken, port) {
@@ -5180,7 +5727,7 @@ async function startTail(options = {}) {
5180
5727
  req2.end();
5181
5728
  });
5182
5729
  if (result.ok) {
5183
- console.log(chalk14.green("\u2713 Flight Recorder buffer cleared."));
5730
+ console.log(chalk15.green("\u2713 Flight Recorder buffer cleared."));
5184
5731
  } else if (result.code === "ECONNREFUSED") {
5185
5732
  throw new Error("Daemon is not running. Start it with: node9 daemon start");
5186
5733
  } else if (result.code === "ETIMEDOUT") {
@@ -5243,16 +5790,16 @@ async function startTail(options = {}) {
5243
5790
  process.stdout.write(SHOW_CURSOR);
5244
5791
  postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
5245
5792
  try {
5246
- fs19.appendFileSync(
5247
- path20.join(os17.homedir(), ".node9", "hook-debug.log"),
5793
+ fs21.appendFileSync(
5794
+ path23.join(os19.homedir(), ".node9", "hook-debug.log"),
5248
5795
  `[tail] POST /decision failed: ${String(err)}
5249
5796
  `
5250
5797
  );
5251
5798
  } catch {
5252
5799
  }
5253
5800
  });
5254
- const decisionLabel = decision === "allow" ? chalk14.green("\u2713 ALLOWED (terminal)") : chalk14.red("\u2717 DENIED (terminal)");
5255
- 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}`);
5256
5803
  approvalQueue.shift();
5257
5804
  cardActive = false;
5258
5805
  showNextCard();
@@ -5289,16 +5836,16 @@ async function startTail(options = {}) {
5289
5836
  }
5290
5837
  } catch {
5291
5838
  }
5292
- console.log(chalk14.cyan.bold(`
5293
- \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}`));
5294
5841
  if (canApprove) {
5295
- console.log(chalk14.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5842
+ console.log(chalk15.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
5296
5843
  }
5297
5844
  if (options.history) {
5298
- 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"));
5299
5846
  } else {
5300
5847
  console.log(
5301
- 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")
5302
5849
  );
5303
5850
  }
5304
5851
  process.on("SIGINT", () => {
@@ -5308,13 +5855,13 @@ async function startTail(options = {}) {
5308
5855
  readline3.clearLine(process.stdout, 0);
5309
5856
  readline3.cursorTo(process.stdout, 0);
5310
5857
  }
5311
- console.log(chalk14.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5858
+ console.log(chalk15.dim("\n\u{1F6F0}\uFE0F Disconnected."));
5312
5859
  process.exit(0);
5313
5860
  });
5314
5861
  const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
5315
5862
  const req = http2.get(sseUrl, (res) => {
5316
5863
  if (res.statusCode !== 200) {
5317
- console.error(chalk14.red(`Failed to connect: HTTP ${res.statusCode}`));
5864
+ console.error(chalk15.red(`Failed to connect: HTTP ${res.statusCode}`));
5318
5865
  process.exit(1);
5319
5866
  }
5320
5867
  let currentEvent = "";
@@ -5344,7 +5891,7 @@ async function startTail(options = {}) {
5344
5891
  readline3.clearLine(process.stdout, 0);
5345
5892
  readline3.cursorTo(process.stdout, 0);
5346
5893
  }
5347
- console.log(chalk14.red("\n\u274C Daemon disconnected."));
5894
+ console.log(chalk15.red("\n\u274C Daemon disconnected."));
5348
5895
  process.exit(1);
5349
5896
  });
5350
5897
  });
@@ -5424,7 +5971,7 @@ async function startTail(options = {}) {
5424
5971
  }
5425
5972
  req.on("error", (err) => {
5426
5973
  const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
5427
- console.error(chalk14.red(`
5974
+ console.error(chalk15.red(`
5428
5975
  \u274C ${msg}`));
5429
5976
  process.exit(1);
5430
5977
  });
@@ -5435,7 +5982,7 @@ var init_tail = __esm({
5435
5982
  "use strict";
5436
5983
  init_daemon2();
5437
5984
  init_core();
5438
- PID_FILE = path20.join(os17.homedir(), ".node9", "daemon.pid");
5985
+ PID_FILE = path23.join(os19.homedir(), ".node9", "daemon.pid");
5439
5986
  ICONS = {
5440
5987
  bash: "\u{1F4BB}",
5441
5988
  shell: "\u{1F4BB}",
@@ -5473,9 +6020,9 @@ init_core();
5473
6020
  import { Command } from "commander";
5474
6021
 
5475
6022
  // src/setup.ts
5476
- import fs9 from "fs";
5477
- import path11 from "path";
5478
- import os9 from "os";
6023
+ import fs11 from "fs";
6024
+ import path14 from "path";
6025
+ import os11 from "os";
5479
6026
  import chalk from "chalk";
5480
6027
  import { confirm } from "@inquirer/prompts";
5481
6028
  function printDaemonTip() {
@@ -5492,26 +6039,26 @@ function fullPathCommand(subcommand) {
5492
6039
  }
5493
6040
  function readJson(filePath) {
5494
6041
  try {
5495
- if (fs9.existsSync(filePath)) {
5496
- return JSON.parse(fs9.readFileSync(filePath, "utf-8"));
6042
+ if (fs11.existsSync(filePath)) {
6043
+ return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
5497
6044
  }
5498
6045
  } catch {
5499
6046
  }
5500
6047
  return null;
5501
6048
  }
5502
6049
  function writeJson(filePath, data) {
5503
- const dir = path11.dirname(filePath);
5504
- if (!fs9.existsSync(dir)) fs9.mkdirSync(dir, { recursive: true });
5505
- fs9.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");
5506
6053
  }
5507
6054
  function isNode9Hook(cmd) {
5508
6055
  if (!cmd) return false;
5509
6056
  return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
5510
6057
  }
5511
6058
  function teardownClaude() {
5512
- const homeDir2 = os9.homedir();
5513
- const hooksPath = path11.join(homeDir2, ".claude", "settings.json");
5514
- const mcpPath = path11.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");
5515
6062
  let changed = false;
5516
6063
  const settings = readJson(hooksPath);
5517
6064
  if (settings?.hooks) {
@@ -5559,8 +6106,8 @@ function teardownClaude() {
5559
6106
  }
5560
6107
  }
5561
6108
  function teardownGemini() {
5562
- const homeDir2 = os9.homedir();
5563
- const settingsPath = path11.join(homeDir2, ".gemini", "settings.json");
6109
+ const homeDir2 = os11.homedir();
6110
+ const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
5564
6111
  const settings = readJson(settingsPath);
5565
6112
  if (!settings) {
5566
6113
  console.log(chalk.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
@@ -5598,8 +6145,8 @@ function teardownGemini() {
5598
6145
  }
5599
6146
  }
5600
6147
  function teardownCursor() {
5601
- const homeDir2 = os9.homedir();
5602
- const mcpPath = path11.join(homeDir2, ".cursor", "mcp.json");
6148
+ const homeDir2 = os11.homedir();
6149
+ const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
5603
6150
  const mcpConfig = readJson(mcpPath);
5604
6151
  if (!mcpConfig?.mcpServers) {
5605
6152
  console.log(chalk.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
@@ -5625,9 +6172,9 @@ function teardownCursor() {
5625
6172
  }
5626
6173
  }
5627
6174
  async function setupClaude() {
5628
- const homeDir2 = os9.homedir();
5629
- const mcpPath = path11.join(homeDir2, ".claude.json");
5630
- const hooksPath = path11.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");
5631
6178
  const claudeConfig = readJson(mcpPath) ?? {};
5632
6179
  const settings = readJson(hooksPath) ?? {};
5633
6180
  const servers = claudeConfig.mcpServers ?? {};
@@ -5701,8 +6248,8 @@ async function setupClaude() {
5701
6248
  }
5702
6249
  }
5703
6250
  async function setupGemini() {
5704
- const homeDir2 = os9.homedir();
5705
- const settingsPath = path11.join(homeDir2, ".gemini", "settings.json");
6251
+ const homeDir2 = os11.homedir();
6252
+ const settingsPath = path14.join(homeDir2, ".gemini", "settings.json");
5706
6253
  const settings = readJson(settingsPath) ?? {};
5707
6254
  const servers = settings.mcpServers ?? {};
5708
6255
  let anythingChanged = false;
@@ -5784,8 +6331,8 @@ async function setupGemini() {
5784
6331
  }
5785
6332
  }
5786
6333
  async function setupCursor() {
5787
- const homeDir2 = os9.homedir();
5788
- const mcpPath = path11.join(homeDir2, ".cursor", "mcp.json");
6334
+ const homeDir2 = os11.homedir();
6335
+ const mcpPath = path14.join(homeDir2, ".cursor", "mcp.json");
5789
6336
  const mcpConfig = readJson(mcpPath) ?? {};
5790
6337
  const servers = mcpConfig.mcpServers ?? {};
5791
6338
  let anythingChanged = false;
@@ -5841,10 +6388,10 @@ async function setupCursor() {
5841
6388
 
5842
6389
  // src/cli.ts
5843
6390
  init_daemon2();
5844
- import chalk15 from "chalk";
5845
- import fs20 from "fs";
5846
- import path21 from "path";
5847
- import os18 from "os";
6391
+ import chalk16 from "chalk";
6392
+ import fs22 from "fs";
6393
+ import path24 from "path";
6394
+ import os20 from "os";
5848
6395
  import { confirm as confirm3 } from "@inquirer/prompts";
5849
6396
 
5850
6397
  // src/utils/duration.ts
@@ -6069,32 +6616,32 @@ init_daemon();
6069
6616
  init_config();
6070
6617
  init_policy();
6071
6618
  import chalk5 from "chalk";
6072
- import fs14 from "fs";
6073
- import path15 from "path";
6074
- import os12 from "os";
6619
+ import fs16 from "fs";
6620
+ import path18 from "path";
6621
+ import os14 from "os";
6075
6622
 
6076
6623
  // src/undo.ts
6077
6624
  import { spawnSync as spawnSync4, spawn as spawn5 } from "child_process";
6078
6625
  import crypto2 from "crypto";
6079
- import fs13 from "fs";
6080
- import path14 from "path";
6081
- import os11 from "os";
6082
- var SNAPSHOT_STACK_PATH = path14.join(os11.homedir(), ".node9", "snapshots.json");
6083
- var UNDO_LATEST_PATH = path14.join(os11.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");
6084
6631
  var MAX_SNAPSHOTS = 10;
6085
6632
  var GIT_TIMEOUT = 15e3;
6086
6633
  function readStack() {
6087
6634
  try {
6088
- if (fs13.existsSync(SNAPSHOT_STACK_PATH))
6089
- return JSON.parse(fs13.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
6635
+ if (fs15.existsSync(SNAPSHOT_STACK_PATH))
6636
+ return JSON.parse(fs15.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
6090
6637
  } catch {
6091
6638
  }
6092
6639
  return [];
6093
6640
  }
6094
6641
  function writeStack(stack) {
6095
- const dir = path14.dirname(SNAPSHOT_STACK_PATH);
6096
- if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
6097
- fs13.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));
6098
6645
  }
6099
6646
  function buildArgsSummary(tool, args) {
6100
6647
  if (!args || typeof args !== "object") return "";
@@ -6110,7 +6657,7 @@ function buildArgsSummary(tool, args) {
6110
6657
  function normalizeCwdForHash(cwd) {
6111
6658
  let normalized;
6112
6659
  try {
6113
- normalized = fs13.realpathSync(cwd);
6660
+ normalized = fs15.realpathSync(cwd);
6114
6661
  } catch {
6115
6662
  normalized = cwd;
6116
6663
  }
@@ -6120,16 +6667,16 @@ function normalizeCwdForHash(cwd) {
6120
6667
  }
6121
6668
  function getShadowRepoDir(cwd) {
6122
6669
  const hash = crypto2.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
6123
- return path14.join(os11.homedir(), ".node9", "snapshots", hash);
6670
+ return path17.join(os13.homedir(), ".node9", "snapshots", hash);
6124
6671
  }
6125
6672
  function cleanOrphanedIndexFiles(shadowDir) {
6126
6673
  try {
6127
6674
  const cutoff = Date.now() - 6e4;
6128
- for (const f of fs13.readdirSync(shadowDir)) {
6675
+ for (const f of fs15.readdirSync(shadowDir)) {
6129
6676
  if (f.startsWith("index_")) {
6130
- const fp = path14.join(shadowDir, f);
6677
+ const fp = path17.join(shadowDir, f);
6131
6678
  try {
6132
- if (fs13.statSync(fp).mtimeMs < cutoff) fs13.unlinkSync(fp);
6679
+ if (fs15.statSync(fp).mtimeMs < cutoff) fs15.unlinkSync(fp);
6133
6680
  } catch {
6134
6681
  }
6135
6682
  }
@@ -6141,7 +6688,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
6141
6688
  const hardcoded = [".git", ".node9"];
6142
6689
  const lines = [...hardcoded, ...ignorePaths].join("\n");
6143
6690
  try {
6144
- fs13.writeFileSync(path14.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
6691
+ fs15.writeFileSync(path17.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
6145
6692
  } catch {
6146
6693
  }
6147
6694
  }
@@ -6154,25 +6701,25 @@ function ensureShadowRepo(shadowDir, cwd) {
6154
6701
  timeout: 3e3
6155
6702
  });
6156
6703
  if (check.status === 0) {
6157
- const ptPath = path14.join(shadowDir, "project-path.txt");
6704
+ const ptPath = path17.join(shadowDir, "project-path.txt");
6158
6705
  try {
6159
- const stored = fs13.readFileSync(ptPath, "utf8").trim();
6706
+ const stored = fs15.readFileSync(ptPath, "utf8").trim();
6160
6707
  if (stored === normalizedCwd) return true;
6161
6708
  if (process.env.NODE9_DEBUG === "1")
6162
6709
  console.error(
6163
6710
  `[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
6164
6711
  );
6165
- fs13.rmSync(shadowDir, { recursive: true, force: true });
6712
+ fs15.rmSync(shadowDir, { recursive: true, force: true });
6166
6713
  } catch {
6167
6714
  try {
6168
- fs13.writeFileSync(ptPath, normalizedCwd, "utf8");
6715
+ fs15.writeFileSync(ptPath, normalizedCwd, "utf8");
6169
6716
  } catch {
6170
6717
  }
6171
6718
  return true;
6172
6719
  }
6173
6720
  }
6174
6721
  try {
6175
- fs13.mkdirSync(shadowDir, { recursive: true });
6722
+ fs15.mkdirSync(shadowDir, { recursive: true });
6176
6723
  } catch {
6177
6724
  }
6178
6725
  const init = spawnSync4("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
@@ -6181,7 +6728,7 @@ function ensureShadowRepo(shadowDir, cwd) {
6181
6728
  console.error("[Node9] git init --bare failed:", init.stderr?.toString());
6182
6729
  return false;
6183
6730
  }
6184
- const configFile = path14.join(shadowDir, "config");
6731
+ const configFile = path17.join(shadowDir, "config");
6185
6732
  spawnSync4("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
6186
6733
  timeout: 3e3
6187
6734
  });
@@ -6189,7 +6736,7 @@ function ensureShadowRepo(shadowDir, cwd) {
6189
6736
  timeout: 3e3
6190
6737
  });
6191
6738
  try {
6192
- fs13.writeFileSync(path14.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
6739
+ fs15.writeFileSync(path17.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
6193
6740
  } catch {
6194
6741
  }
6195
6742
  return true;
@@ -6212,7 +6759,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6212
6759
  const shadowDir = getShadowRepoDir(cwd);
6213
6760
  if (!ensureShadowRepo(shadowDir, cwd)) return null;
6214
6761
  writeShadowExcludes(shadowDir, ignorePaths);
6215
- indexFile = path14.join(shadowDir, `index_${process.pid}_${Date.now()}`);
6762
+ indexFile = path17.join(shadowDir, `index_${process.pid}_${Date.now()}`);
6216
6763
  const shadowEnv = {
6217
6764
  ...process.env,
6218
6765
  GIT_DIR: shadowDir,
@@ -6241,7 +6788,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6241
6788
  const shouldGc = stack.length % 5 === 0;
6242
6789
  if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
6243
6790
  writeStack(stack);
6244
- fs13.writeFileSync(UNDO_LATEST_PATH, commitHash);
6791
+ fs15.writeFileSync(UNDO_LATEST_PATH, commitHash);
6245
6792
  if (shouldGc) {
6246
6793
  spawn5("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
6247
6794
  }
@@ -6252,7 +6799,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
6252
6799
  } finally {
6253
6800
  if (indexFile) {
6254
6801
  try {
6255
- fs13.unlinkSync(indexFile);
6802
+ fs15.unlinkSync(indexFile);
6256
6803
  } catch {
6257
6804
  }
6258
6805
  }
@@ -6321,9 +6868,9 @@ function applyUndo(hash, cwd) {
6321
6868
  timeout: GIT_TIMEOUT
6322
6869
  }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
6323
6870
  for (const file of [...tracked, ...untracked]) {
6324
- const fullPath = path14.join(dir, file);
6325
- if (!snapshotFiles.has(file) && fs13.existsSync(fullPath)) {
6326
- fs13.unlinkSync(fullPath);
6871
+ const fullPath = path17.join(dir, file);
6872
+ if (!snapshotFiles.has(file) && fs15.existsSync(fullPath)) {
6873
+ fs15.unlinkSync(fullPath);
6327
6874
  }
6328
6875
  }
6329
6876
  return true;
@@ -6347,9 +6894,9 @@ function registerCheckCommand(program2) {
6347
6894
  } catch (err) {
6348
6895
  const tempConfig = getConfig();
6349
6896
  if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
6350
- const logPath = path15.join(os12.homedir(), ".node9", "hook-debug.log");
6897
+ const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
6351
6898
  const errMsg = err instanceof Error ? err.message : String(err);
6352
- fs14.appendFileSync(
6899
+ fs16.appendFileSync(
6353
6900
  logPath,
6354
6901
  `[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
6355
6902
  RAW: ${raw}
@@ -6360,10 +6907,10 @@ RAW: ${raw}
6360
6907
  }
6361
6908
  const config = getConfig(payload.cwd || void 0);
6362
6909
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
6363
- const logPath = path15.join(os12.homedir(), ".node9", "hook-debug.log");
6364
- if (!fs14.existsSync(path15.dirname(logPath)))
6365
- fs14.mkdirSync(path15.dirname(logPath), { recursive: true });
6366
- fs14.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}
6367
6914
  `);
6368
6915
  }
6369
6916
  const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
@@ -6376,8 +6923,8 @@ RAW: ${raw}
6376
6923
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
6377
6924
  let ttyFd = null;
6378
6925
  try {
6379
- ttyFd = fs14.openSync("/dev/tty", "w");
6380
- const writeTty = (line) => fs14.writeSync(ttyFd, line + "\n");
6926
+ ttyFd = fs16.openSync("/dev/tty", "w");
6927
+ const writeTty = (line) => fs16.writeSync(ttyFd, line + "\n");
6381
6928
  if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
6382
6929
  writeTty(chalk5.bgRed.white.bold(`
6383
6930
  \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
@@ -6393,7 +6940,7 @@ RAW: ${raw}
6393
6940
  } finally {
6394
6941
  if (ttyFd !== null)
6395
6942
  try {
6396
- fs14.closeSync(ttyFd);
6943
+ fs16.closeSync(ttyFd);
6397
6944
  } catch {
6398
6945
  }
6399
6946
  }
@@ -6424,7 +6971,7 @@ RAW: ${raw}
6424
6971
  if (shouldSnapshot(toolName, toolInput, config)) {
6425
6972
  await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
6426
6973
  }
6427
- const safeCwdForAuth = typeof payload.cwd === "string" && path15.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6974
+ const safeCwdForAuth = typeof payload.cwd === "string" && path18.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6428
6975
  const result = await authorizeHeadless(toolName, toolInput, meta, {
6429
6976
  cwd: safeCwdForAuth
6430
6977
  });
@@ -6436,12 +6983,12 @@ RAW: ${raw}
6436
6983
  }
6437
6984
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
6438
6985
  try {
6439
- const tty = fs14.openSync("/dev/tty", "w");
6440
- fs14.writeSync(
6986
+ const tty = fs16.openSync("/dev/tty", "w");
6987
+ fs16.writeSync(
6441
6988
  tty,
6442
6989
  chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
6443
6990
  );
6444
- fs14.closeSync(tty);
6991
+ fs16.closeSync(tty);
6445
6992
  } catch {
6446
6993
  }
6447
6994
  const daemonReady = await autoStartDaemonAndWait();
@@ -6468,9 +7015,9 @@ RAW: ${raw}
6468
7015
  });
6469
7016
  } catch (err) {
6470
7017
  if (process.env.NODE9_DEBUG === "1") {
6471
- const logPath = path15.join(os12.homedir(), ".node9", "hook-debug.log");
7018
+ const logPath = path18.join(os14.homedir(), ".node9", "hook-debug.log");
6472
7019
  const errMsg = err instanceof Error ? err.message : String(err);
6473
- fs14.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
7020
+ fs16.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
6474
7021
  `);
6475
7022
  }
6476
7023
  process.exit(0);
@@ -6507,9 +7054,9 @@ RAW: ${raw}
6507
7054
  init_audit();
6508
7055
  init_config();
6509
7056
  init_policy();
6510
- import fs15 from "fs";
6511
- import path16 from "path";
6512
- import os13 from "os";
7057
+ import fs17 from "fs";
7058
+ import path19 from "path";
7059
+ import os15 from "os";
6513
7060
  function sanitize3(value) {
6514
7061
  return value.replace(/[\x00-\x1F\x7F]/g, "");
6515
7062
  }
@@ -6528,11 +7075,11 @@ function registerLogCommand(program2) {
6528
7075
  decision: "allowed",
6529
7076
  source: "post-hook"
6530
7077
  };
6531
- const logPath = path16.join(os13.homedir(), ".node9", "audit.log");
6532
- if (!fs15.existsSync(path16.dirname(logPath)))
6533
- fs15.mkdirSync(path16.dirname(logPath), { recursive: true });
6534
- fs15.appendFileSync(logPath, JSON.stringify(entry) + "\n");
6535
- const safeCwd = typeof payload.cwd === "string" && path16.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;
6536
7083
  const config = getConfig(safeCwd);
6537
7084
  if (shouldSnapshot(tool, {}, config)) {
6538
7085
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
@@ -6541,9 +7088,9 @@ function registerLogCommand(program2) {
6541
7088
  const msg = err instanceof Error ? err.message : String(err);
6542
7089
  process.stderr.write(`[Node9] audit log error: ${msg}
6543
7090
  `);
6544
- const debugPath = path16.join(os13.homedir(), ".node9", "hook-debug.log");
7091
+ const debugPath = path19.join(os15.homedir(), ".node9", "hook-debug.log");
6545
7092
  try {
6546
- fs15.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
7093
+ fs17.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
6547
7094
  `);
6548
7095
  } catch {
6549
7096
  }
@@ -6848,13 +7395,13 @@ function registerConfigShowCommand(program2) {
6848
7395
  // src/cli/commands/doctor.ts
6849
7396
  init_daemon();
6850
7397
  import chalk7 from "chalk";
6851
- import fs16 from "fs";
6852
- import path17 from "path";
6853
- import os14 from "os";
7398
+ import fs18 from "fs";
7399
+ import path20 from "path";
7400
+ import os16 from "os";
6854
7401
  import { execSync as execSync2 } from "child_process";
6855
7402
  function registerDoctorCommand(program2, version2) {
6856
7403
  program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
6857
- const homeDir2 = os14.homedir();
7404
+ const homeDir2 = os16.homedir();
6858
7405
  let failures = 0;
6859
7406
  function pass(msg) {
6860
7407
  console.log(chalk7.green(" \u2705 ") + msg);
@@ -6903,10 +7450,10 @@ function registerDoctorCommand(program2, version2) {
6903
7450
  );
6904
7451
  }
6905
7452
  section("Configuration");
6906
- const globalConfigPath = path17.join(homeDir2, ".node9", "config.json");
6907
- if (fs16.existsSync(globalConfigPath)) {
7453
+ const globalConfigPath = path20.join(homeDir2, ".node9", "config.json");
7454
+ if (fs18.existsSync(globalConfigPath)) {
6908
7455
  try {
6909
- JSON.parse(fs16.readFileSync(globalConfigPath, "utf-8"));
7456
+ JSON.parse(fs18.readFileSync(globalConfigPath, "utf-8"));
6910
7457
  pass("~/.node9/config.json found and valid");
6911
7458
  } catch {
6912
7459
  fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
@@ -6914,10 +7461,10 @@ function registerDoctorCommand(program2, version2) {
6914
7461
  } else {
6915
7462
  warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
6916
7463
  }
6917
- const projectConfigPath = path17.join(process.cwd(), "node9.config.json");
6918
- if (fs16.existsSync(projectConfigPath)) {
7464
+ const projectConfigPath = path20.join(process.cwd(), "node9.config.json");
7465
+ if (fs18.existsSync(projectConfigPath)) {
6919
7466
  try {
6920
- JSON.parse(fs16.readFileSync(projectConfigPath, "utf-8"));
7467
+ JSON.parse(fs18.readFileSync(projectConfigPath, "utf-8"));
6921
7468
  pass("node9.config.json found and valid (project)");
6922
7469
  } catch {
6923
7470
  fail(
@@ -6926,8 +7473,8 @@ function registerDoctorCommand(program2, version2) {
6926
7473
  );
6927
7474
  }
6928
7475
  }
6929
- const credsPath = path17.join(homeDir2, ".node9", "credentials.json");
6930
- if (fs16.existsSync(credsPath)) {
7476
+ const credsPath = path20.join(homeDir2, ".node9", "credentials.json");
7477
+ if (fs18.existsSync(credsPath)) {
6931
7478
  pass("Cloud credentials found (~/.node9/credentials.json)");
6932
7479
  } else {
6933
7480
  warn(
@@ -6936,10 +7483,10 @@ function registerDoctorCommand(program2, version2) {
6936
7483
  );
6937
7484
  }
6938
7485
  section("Agent Hooks");
6939
- const claudeSettingsPath = path17.join(homeDir2, ".claude", "settings.json");
6940
- if (fs16.existsSync(claudeSettingsPath)) {
7486
+ const claudeSettingsPath = path20.join(homeDir2, ".claude", "settings.json");
7487
+ if (fs18.existsSync(claudeSettingsPath)) {
6941
7488
  try {
6942
- const cs = JSON.parse(fs16.readFileSync(claudeSettingsPath, "utf-8"));
7489
+ const cs = JSON.parse(fs18.readFileSync(claudeSettingsPath, "utf-8"));
6943
7490
  const hasHook = cs.hooks?.PreToolUse?.some(
6944
7491
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
6945
7492
  );
@@ -6955,10 +7502,10 @@ function registerDoctorCommand(program2, version2) {
6955
7502
  } else {
6956
7503
  warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
6957
7504
  }
6958
- const geminiSettingsPath = path17.join(homeDir2, ".gemini", "settings.json");
6959
- if (fs16.existsSync(geminiSettingsPath)) {
7505
+ const geminiSettingsPath = path20.join(homeDir2, ".gemini", "settings.json");
7506
+ if (fs18.existsSync(geminiSettingsPath)) {
6960
7507
  try {
6961
- const gs = JSON.parse(fs16.readFileSync(geminiSettingsPath, "utf-8"));
7508
+ const gs = JSON.parse(fs18.readFileSync(geminiSettingsPath, "utf-8"));
6962
7509
  const hasHook = gs.hooks?.BeforeTool?.some(
6963
7510
  (m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
6964
7511
  );
@@ -6974,10 +7521,10 @@ function registerDoctorCommand(program2, version2) {
6974
7521
  } else {
6975
7522
  warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
6976
7523
  }
6977
- const cursorHooksPath = path17.join(homeDir2, ".cursor", "hooks.json");
6978
- if (fs16.existsSync(cursorHooksPath)) {
7524
+ const cursorHooksPath = path20.join(homeDir2, ".cursor", "hooks.json");
7525
+ if (fs18.existsSync(cursorHooksPath)) {
6979
7526
  try {
6980
- const cur = JSON.parse(fs16.readFileSync(cursorHooksPath, "utf-8"));
7527
+ const cur = JSON.parse(fs18.readFileSync(cursorHooksPath, "utf-8"));
6981
7528
  const hasHook = cur.hooks?.preToolUse?.some(
6982
7529
  (h) => h.command?.includes("node9") || h.command?.includes("cli.js")
6983
7530
  );
@@ -7015,9 +7562,9 @@ function registerDoctorCommand(program2, version2) {
7015
7562
 
7016
7563
  // src/cli/commands/audit.ts
7017
7564
  import chalk8 from "chalk";
7018
- import fs17 from "fs";
7019
- import path18 from "path";
7020
- import os15 from "os";
7565
+ import fs19 from "fs";
7566
+ import path21 from "path";
7567
+ import os17 from "os";
7021
7568
  function formatRelativeTime(timestamp) {
7022
7569
  const diff = Date.now() - new Date(timestamp).getTime();
7023
7570
  const sec = Math.floor(diff / 1e3);
@@ -7030,14 +7577,14 @@ function formatRelativeTime(timestamp) {
7030
7577
  }
7031
7578
  function registerAuditCommand(program2) {
7032
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) => {
7033
- const logPath = path18.join(os15.homedir(), ".node9", "audit.log");
7034
- if (!fs17.existsSync(logPath)) {
7580
+ const logPath = path21.join(os17.homedir(), ".node9", "audit.log");
7581
+ if (!fs19.existsSync(logPath)) {
7035
7582
  console.log(
7036
7583
  chalk8.yellow("No audit logs found. Run node9 with an agent to generate entries.")
7037
7584
  );
7038
7585
  return;
7039
7586
  }
7040
- const raw = fs17.readFileSync(logPath, "utf-8");
7587
+ const raw = fs19.readFileSync(logPath, "utf-8");
7041
7588
  const lines = raw.split("\n").filter((l) => l.trim() !== "");
7042
7589
  let entries = lines.flatMap((line) => {
7043
7590
  try {
@@ -7157,9 +7704,9 @@ function registerDaemonCommand(program2) {
7157
7704
  init_core();
7158
7705
  init_daemon();
7159
7706
  import chalk10 from "chalk";
7160
- import fs18 from "fs";
7161
- import path19 from "path";
7162
- import os16 from "os";
7707
+ import fs20 from "fs";
7708
+ import path22 from "path";
7709
+ import os18 from "os";
7163
7710
  function registerStatusCommand(program2) {
7164
7711
  program2.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
7165
7712
  const creds = getCredentials();
@@ -7194,13 +7741,13 @@ function registerStatusCommand(program2) {
7194
7741
  console.log("");
7195
7742
  const modeLabel = settings.mode === "audit" ? chalk10.blue("audit") : settings.mode === "strict" ? chalk10.red("strict") : chalk10.white("standard");
7196
7743
  console.log(` Mode: ${modeLabel}`);
7197
- const projectConfig = path19.join(process.cwd(), "node9.config.json");
7198
- const globalConfig = path19.join(os16.homedir(), ".node9", "config.json");
7744
+ const projectConfig = path22.join(process.cwd(), "node9.config.json");
7745
+ const globalConfig = path22.join(os18.homedir(), ".node9", "config.json");
7199
7746
  console.log(
7200
- ` Local: ${fs18.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")}`
7201
7748
  );
7202
7749
  console.log(
7203
- ` Global: ${fs18.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")}`
7204
7751
  );
7205
7752
  if (mergedConfig.policy.sandboxPaths.length > 0) {
7206
7753
  console.log(
@@ -7379,6 +7926,7 @@ import readline2 from "readline";
7379
7926
  import chalk13 from "chalk";
7380
7927
  import { spawn as spawn8 } from "child_process";
7381
7928
  import { execa as execa2 } from "execa";
7929
+ init_provenance();
7382
7930
  function sanitize4(value) {
7383
7931
  return value.replace(/[\x00-\x1F\x7F]/g, "");
7384
7932
  }
@@ -7391,7 +7939,7 @@ function extractMcpServer(toolName) {
7391
7939
  const match = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
7392
7940
  return match?.[1];
7393
7941
  }
7394
- function tokenize2(cmd) {
7942
+ function tokenize3(cmd) {
7395
7943
  const tokens = [];
7396
7944
  let current = "";
7397
7945
  let inDouble = false;
@@ -7426,7 +7974,7 @@ function tokenize2(cmd) {
7426
7974
  return tokens;
7427
7975
  }
7428
7976
  async function runMcpGateway(upstreamCommand) {
7429
- const commandParts = tokenize2(upstreamCommand);
7977
+ const commandParts = tokenize3(upstreamCommand);
7430
7978
  const cmd = commandParts[0];
7431
7979
  const cmdArgs = commandParts.slice(1);
7432
7980
  let executable = cmd;
@@ -7435,6 +7983,15 @@ async function runMcpGateway(upstreamCommand) {
7435
7983
  if (stdout) executable = stdout.trim();
7436
7984
  } catch {
7437
7985
  }
7986
+ const prov = checkProvenance(executable);
7987
+ if (prov.trustLevel === "suspect") {
7988
+ console.error(
7989
+ chalk13.red(
7990
+ `\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
7991
+ )
7992
+ );
7993
+ console.error(chalk13.red(" Verify this binary is trusted before proceeding."));
7994
+ }
7438
7995
  console.error(chalk13.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
7439
7996
  const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
7440
7997
  "NODE_OPTIONS",
@@ -7574,22 +8131,77 @@ function registerMcpGatewayCommand(program2) {
7574
8131
  });
7575
8132
  }
7576
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
+
7577
8189
  // src/cli.ts
7578
8190
  var { version } = JSON.parse(
7579
- fs20.readFileSync(path21.join(__dirname, "../package.json"), "utf-8")
8191
+ fs22.readFileSync(path24.join(__dirname, "../package.json"), "utf-8")
7580
8192
  );
7581
8193
  var program = new Command();
7582
8194
  program.name("node9").description("The Sudo Command for AI Agents").version(version);
7583
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) => {
7584
8196
  const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
7585
- const credPath = path21.join(os18.homedir(), ".node9", "credentials.json");
7586
- if (!fs20.existsSync(path21.dirname(credPath)))
7587
- fs20.mkdirSync(path21.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 });
7588
8200
  const profileName = options.profile || "default";
7589
8201
  let existingCreds = {};
7590
8202
  try {
7591
- if (fs20.existsSync(credPath)) {
7592
- const raw = JSON.parse(fs20.readFileSync(credPath, "utf-8"));
8203
+ if (fs22.existsSync(credPath)) {
8204
+ const raw = JSON.parse(fs22.readFileSync(credPath, "utf-8"));
7593
8205
  if (raw.apiKey) {
7594
8206
  existingCreds = {
7595
8207
  default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
@@ -7601,13 +8213,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
7601
8213
  } catch {
7602
8214
  }
7603
8215
  existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
7604
- fs20.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
8216
+ fs22.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
7605
8217
  if (profileName === "default") {
7606
- const configPath = path21.join(os18.homedir(), ".node9", "config.json");
8218
+ const configPath = path24.join(os20.homedir(), ".node9", "config.json");
7607
8219
  let config = {};
7608
8220
  try {
7609
- if (fs20.existsSync(configPath))
7610
- config = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
8221
+ if (fs22.existsSync(configPath))
8222
+ config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
7611
8223
  } catch {
7612
8224
  }
7613
8225
  if (!config.settings || typeof config.settings !== "object") config.settings = {};
@@ -7622,36 +8234,36 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
7622
8234
  approvers.cloud = false;
7623
8235
  }
7624
8236
  s.approvers = approvers;
7625
- if (!fs20.existsSync(path21.dirname(configPath)))
7626
- fs20.mkdirSync(path21.dirname(configPath), { recursive: true });
7627
- fs20.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 });
7628
8240
  }
7629
8241
  if (options.profile && profileName !== "default") {
7630
- console.log(chalk15.green(`\u2705 Profile "${profileName}" saved`));
7631
- 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`));
7632
8244
  } else if (options.local) {
7633
- console.log(chalk15.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
7634
- 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.`));
7635
8247
  } else {
7636
- console.log(chalk15.green(`\u2705 Logged in \u2014 agent mode`));
7637
- 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.`));
7638
8250
  }
7639
8251
  });
7640
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) => {
7641
8253
  if (target === "gemini") return await setupGemini();
7642
8254
  if (target === "claude") return await setupClaude();
7643
8255
  if (target === "cursor") return await setupCursor();
7644
- console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8256
+ console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7645
8257
  process.exit(1);
7646
8258
  });
7647
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) => {
7648
8260
  if (!target) {
7649
- console.log(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
7650
- 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");
7651
8263
  console.log(" Targets:");
7652
- console.log(" " + chalk15.green("claude") + " \u2014 Claude Code (hook mode)");
7653
- console.log(" " + chalk15.green("gemini") + " \u2014 Gemini CLI (hook mode)");
7654
- 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)");
7655
8267
  console.log("");
7656
8268
  return;
7657
8269
  }
@@ -7659,7 +8271,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
7659
8271
  if (t === "gemini") return await setupGemini();
7660
8272
  if (t === "claude") return await setupClaude();
7661
8273
  if (t === "cursor") return await setupCursor();
7662
- console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8274
+ console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7663
8275
  process.exit(1);
7664
8276
  });
7665
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) => {
@@ -7668,30 +8280,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
7668
8280
  else if (target === "gemini") fn = teardownGemini;
7669
8281
  else if (target === "cursor") fn = teardownCursor;
7670
8282
  else {
7671
- console.error(chalk15.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
8283
+ console.error(chalk16.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
7672
8284
  process.exit(1);
7673
8285
  }
7674
- console.log(chalk15.cyan(`
8286
+ console.log(chalk16.cyan(`
7675
8287
  \u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
7676
8288
  `));
7677
8289
  try {
7678
8290
  fn();
7679
8291
  } catch (err) {
7680
- 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)}`));
7681
8293
  process.exit(1);
7682
8294
  }
7683
- 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."));
7684
8296
  });
7685
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) => {
7686
- console.log(chalk15.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
7687
- 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..."));
7688
8300
  try {
7689
8301
  stopDaemon();
7690
- console.log(chalk15.green(" \u2705 Daemon stopped"));
8302
+ console.log(chalk16.green(" \u2705 Daemon stopped"));
7691
8303
  } catch {
7692
- console.log(chalk15.blue(" \u2139\uFE0F Daemon was not running"));
8304
+ console.log(chalk16.blue(" \u2139\uFE0F Daemon was not running"));
7693
8305
  }
7694
- console.log(chalk15.bold("\nRemoving hooks..."));
8306
+ console.log(chalk16.bold("\nRemoving hooks..."));
7695
8307
  let teardownFailed = false;
7696
8308
  for (const [label, fn] of [
7697
8309
  ["Claude", teardownClaude],
@@ -7703,45 +8315,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
7703
8315
  } catch (err) {
7704
8316
  teardownFailed = true;
7705
8317
  console.error(
7706
- chalk15.red(
8318
+ chalk16.red(
7707
8319
  ` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
7708
8320
  )
7709
8321
  );
7710
8322
  }
7711
8323
  }
7712
8324
  if (options.purge) {
7713
- const node9Dir = path21.join(os18.homedir(), ".node9");
7714
- if (fs20.existsSync(node9Dir)) {
8325
+ const node9Dir = path24.join(os20.homedir(), ".node9");
8326
+ if (fs22.existsSync(node9Dir)) {
7715
8327
  const confirmed = await confirm3({
7716
8328
  message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
7717
8329
  default: false
7718
8330
  });
7719
8331
  if (confirmed) {
7720
- fs20.rmSync(node9Dir, { recursive: true });
7721
- if (fs20.existsSync(node9Dir)) {
8332
+ fs22.rmSync(node9Dir, { recursive: true });
8333
+ if (fs22.existsSync(node9Dir)) {
7722
8334
  console.error(
7723
- 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.")
7724
8336
  );
7725
8337
  } else {
7726
- 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)"));
7727
8339
  }
7728
8340
  } else {
7729
- console.log(chalk15.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
8341
+ console.log(chalk16.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
7730
8342
  }
7731
8343
  } else {
7732
- 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"));
7733
8345
  }
7734
8346
  } else {
7735
8347
  console.log(
7736
- 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")
7737
8349
  );
7738
8350
  }
7739
8351
  if (teardownFailed) {
7740
- 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."));
7741
8353
  process.exit(1);
7742
8354
  }
7743
- console.log(chalk15.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
7744
- 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"));
7745
8357
  });
7746
8358
  registerDoctorCommand(program, version);
7747
8359
  program.command("explain").description(
@@ -7754,7 +8366,7 @@ program.command("explain").description(
7754
8366
  try {
7755
8367
  args = JSON.parse(trimmed);
7756
8368
  } catch {
7757
- console.error(chalk15.red(`
8369
+ console.error(chalk16.red(`
7758
8370
  \u274C Invalid JSON: ${trimmed}
7759
8371
  `));
7760
8372
  process.exit(1);
@@ -7765,63 +8377,63 @@ program.command("explain").description(
7765
8377
  }
7766
8378
  const result = await explainPolicy(tool, args);
7767
8379
  console.log("");
7768
- console.log(chalk15.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
8380
+ console.log(chalk16.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
7769
8381
  console.log("");
7770
- console.log(` ${chalk15.bold("Tool:")} ${chalk15.white(result.tool)}`);
8382
+ console.log(` ${chalk16.bold("Tool:")} ${chalk16.white(result.tool)}`);
7771
8383
  if (argsRaw) {
7772
8384
  const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
7773
- console.log(` ${chalk15.bold("Input:")} ${chalk15.gray(preview)}`);
8385
+ console.log(` ${chalk16.bold("Input:")} ${chalk16.gray(preview)}`);
7774
8386
  }
7775
8387
  console.log("");
7776
- console.log(chalk15.bold("Config Sources (Waterfall):"));
8388
+ console.log(chalk16.bold("Config Sources (Waterfall):"));
7777
8389
  for (const tier of result.waterfall) {
7778
- const num = chalk15.gray(` ${tier.tier}.`);
8390
+ const num = chalk16.gray(` ${tier.tier}.`);
7779
8391
  const label = tier.label.padEnd(16);
7780
8392
  let statusStr;
7781
8393
  if (tier.tier === 1) {
7782
- statusStr = chalk15.gray(tier.note ?? "");
8394
+ statusStr = chalk16.gray(tier.note ?? "");
7783
8395
  } else if (tier.status === "active") {
7784
- const loc = tier.path ? chalk15.gray(tier.path) : "";
7785
- const note = tier.note ? chalk15.gray(`(${tier.note})`) : "";
7786
- 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 : "");
7787
8399
  } else {
7788
- statusStr = chalk15.gray("\u25CB " + (tier.note ?? "not found"));
8400
+ statusStr = chalk16.gray("\u25CB " + (tier.note ?? "not found"));
7789
8401
  }
7790
- console.log(`${num} ${chalk15.white(label)} ${statusStr}`);
8402
+ console.log(`${num} ${chalk16.white(label)} ${statusStr}`);
7791
8403
  }
7792
8404
  console.log("");
7793
- console.log(chalk15.bold("Policy Evaluation:"));
8405
+ console.log(chalk16.bold("Policy Evaluation:"));
7794
8406
  for (const step of result.steps) {
7795
8407
  const isFinal = step.isFinal;
7796
8408
  let icon;
7797
- if (step.outcome === "allow") icon = chalk15.green(" \u2705");
7798
- else if (step.outcome === "review") icon = chalk15.red(" \u{1F534}");
7799
- else if (step.outcome === "skip") icon = chalk15.gray(" \u2500 ");
7800
- 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 ");
7801
8413
  const name = step.name.padEnd(18);
7802
- const nameStr = isFinal ? chalk15.white.bold(name) : chalk15.white(name);
7803
- const detail = isFinal ? chalk15.white(step.detail) : chalk15.gray(step.detail);
7804
- 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") : "";
7805
8417
  console.log(`${icon} ${nameStr} ${detail}${arrow}`);
7806
8418
  }
7807
8419
  console.log("");
7808
8420
  if (result.decision === "allow") {
7809
- 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"));
7810
8422
  } else {
7811
8423
  console.log(
7812
- 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")
7813
8425
  );
7814
8426
  if (result.blockedByLabel) {
7815
- console.log(chalk15.gray(` Reason: ${result.blockedByLabel}`));
8427
+ console.log(chalk16.gray(` Reason: ${result.blockedByLabel}`));
7816
8428
  }
7817
8429
  }
7818
8430
  console.log("");
7819
8431
  });
7820
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) => {
7821
- const configPath = path21.join(os18.homedir(), ".node9", "config.json");
7822
- if (fs20.existsSync(configPath) && !options.force) {
7823
- console.log(chalk15.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
7824
- 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.`));
7825
8437
  return;
7826
8438
  }
7827
8439
  const requestedMode = options.mode.toLowerCase();
@@ -7833,13 +8445,13 @@ program.command("init").description("Create ~/.node9/config.json with default po
7833
8445
  mode: safeMode
7834
8446
  }
7835
8447
  };
7836
- const dir = path21.dirname(configPath);
7837
- if (!fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
7838
- fs20.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
7839
- console.log(chalk15.green(`\u2705 Global config created: ${configPath}`));
7840
- 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}`));
7841
8453
  console.log(
7842
- 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.`)
7843
8455
  );
7844
8456
  });
7845
8457
  registerAuditCommand(program);
@@ -7850,7 +8462,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
7850
8462
  try {
7851
8463
  await startTail2(options);
7852
8464
  } catch (err) {
7853
- 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)}`));
7854
8466
  process.exit(1);
7855
8467
  }
7856
8468
  });
@@ -7862,7 +8474,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
7862
8474
  const ms = parseDuration(options.duration);
7863
8475
  if (ms === null) {
7864
8476
  console.error(
7865
- chalk15.red(`
8477
+ chalk16.red(`
7866
8478
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
7867
8479
  `)
7868
8480
  );
@@ -7870,20 +8482,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
7870
8482
  }
7871
8483
  pauseNode9(ms, options.duration);
7872
8484
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
7873
- console.log(chalk15.yellow(`
8485
+ console.log(chalk16.yellow(`
7874
8486
  \u23F8 Node9 paused until ${expiresAt}`));
7875
- console.log(chalk15.gray(` All tool calls will be allowed without review.`));
7876
- 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.
7877
8489
  `));
7878
8490
  });
7879
8491
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
7880
8492
  const { paused } = checkPause();
7881
8493
  if (!paused) {
7882
- 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"));
7883
8495
  return;
7884
8496
  }
7885
8497
  resumeNode9();
7886
- 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"));
7887
8499
  });
7888
8500
  var HOOK_BASED_AGENTS = {
7889
8501
  claude: "claude",
@@ -7896,15 +8508,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7896
8508
  if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
7897
8509
  const target = HOOK_BASED_AGENTS[firstArg2];
7898
8510
  console.error(
7899
- chalk15.yellow(`
8511
+ chalk16.yellow(`
7900
8512
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
7901
8513
  );
7902
- console.error(chalk15.white(`
8514
+ console.error(chalk16.white(`
7903
8515
  "${target}" uses its own hook system. Use:`));
7904
8516
  console.error(
7905
- chalk15.green(` node9 addto ${target} `) + chalk15.gray("# one-time setup")
8517
+ chalk16.green(` node9 addto ${target} `) + chalk16.gray("# one-time setup")
7906
8518
  );
7907
- console.error(chalk15.green(` ${target} `) + chalk15.gray("# run normally"));
8519
+ console.error(chalk16.green(` ${target} `) + chalk16.gray("# run normally"));
7908
8520
  process.exit(1);
7909
8521
  }
7910
8522
  const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
@@ -7921,7 +8533,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7921
8533
  }
7922
8534
  );
7923
8535
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
7924
- 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..."));
7925
8537
  const daemonReady = await autoStartDaemonAndWait();
7926
8538
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
7927
8539
  }
@@ -7934,12 +8546,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7934
8546
  }
7935
8547
  if (!result.approved) {
7936
8548
  console.error(
7937
- chalk15.red(`
8549
+ chalk16.red(`
7938
8550
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
7939
8551
  );
7940
8552
  process.exit(1);
7941
8553
  }
7942
- console.error(chalk15.green("\n\u2705 Approved \u2014 running command...\n"));
8554
+ console.error(chalk16.green("\n\u2705 Approved \u2014 running command...\n"));
7943
8555
  await runProxy(fullCommand);
7944
8556
  } else {
7945
8557
  program.help();
@@ -7948,14 +8560,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
7948
8560
  registerUndoCommand(program);
7949
8561
  registerShieldCommand(program);
7950
8562
  registerConfigShowCommand(program);
8563
+ registerTrustCommand(program);
7951
8564
  if (process.argv[2] !== "daemon") {
7952
8565
  process.on("unhandledRejection", (reason) => {
7953
8566
  const isCheckHook = process.argv[2] === "check";
7954
8567
  if (isCheckHook) {
7955
8568
  if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
7956
- const logPath = path21.join(os18.homedir(), ".node9", "hook-debug.log");
8569
+ const logPath = path24.join(os20.homedir(), ".node9", "hook-debug.log");
7957
8570
  const msg = reason instanceof Error ? reason.message : String(reason);
7958
- fs20.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
8571
+ fs22.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
7959
8572
  `);
7960
8573
  }
7961
8574
  process.exit(0);