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