@node9/proxy 1.1.6 → 1.1.7
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/README.md +1 -1
- package/dist/cli.js +757 -509
- package/dist/cli.mjs +761 -513
- package/dist/index.js +104 -198
- package/dist/index.mjs +104 -198
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -217,21 +217,6 @@ ${smartTruncate(str, 500)}`
|
|
|
217
217
|
}
|
|
218
218
|
return { intent: "EXEC", message: smartTruncate(JSON.stringify(parsed), 200) };
|
|
219
219
|
}
|
|
220
|
-
function sendDesktopNotification(title, body) {
|
|
221
|
-
if (isTestEnv()) return;
|
|
222
|
-
try {
|
|
223
|
-
if (process.platform === "darwin") {
|
|
224
|
-
const script = `display notification "${body.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"`;
|
|
225
|
-
(0, import_child_process.spawn)("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
226
|
-
} else if (process.platform === "linux") {
|
|
227
|
-
(0, import_child_process.spawn)("notify-send", [title, body, "--icon=dialog-warning"], {
|
|
228
|
-
detached: true,
|
|
229
|
-
stdio: "ignore"
|
|
230
|
-
}).unref();
|
|
231
|
-
}
|
|
232
|
-
} catch {
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
220
|
function escapePango(text) {
|
|
236
221
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
237
222
|
}
|
|
@@ -274,9 +259,6 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
274
259
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
275
260
|
const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 ${intentLabel}`;
|
|
276
261
|
const message = buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked);
|
|
277
|
-
process.stderr.write(import_chalk.default.yellow(`
|
|
278
|
-
\u{1F6E1}\uFE0F Node9: Intercepted "${toolName}" \u2014 awaiting user...
|
|
279
|
-
`));
|
|
280
262
|
return new Promise((resolve) => {
|
|
281
263
|
let childProcess = null;
|
|
282
264
|
const onAbort = () => {
|
|
@@ -348,13 +330,12 @@ end run`;
|
|
|
348
330
|
}
|
|
349
331
|
});
|
|
350
332
|
}
|
|
351
|
-
var import_child_process, import_path2,
|
|
333
|
+
var import_child_process, import_path2, isTestEnv;
|
|
352
334
|
var init_native = __esm({
|
|
353
335
|
"src/ui/native.ts"() {
|
|
354
336
|
"use strict";
|
|
355
337
|
import_child_process = require("child_process");
|
|
356
338
|
import_path2 = __toESM(require("path"));
|
|
357
|
-
import_chalk = __toESM(require("chalk"));
|
|
358
339
|
init_context_sniper();
|
|
359
340
|
isTestEnv = () => {
|
|
360
341
|
return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || !!process.env.VITEST || process.env.CI === "true" || !!process.env.CI || process.env.NODE9_TESTING === "1";
|
|
@@ -443,6 +424,7 @@ var init_config_schema = __esm({
|
|
|
443
424
|
enableUndo: import_zod.z.boolean().optional(),
|
|
444
425
|
enableHookLogDebug: import_zod.z.boolean().optional(),
|
|
445
426
|
approvalTimeoutMs: import_zod.z.number().nonnegative().optional(),
|
|
427
|
+
approvalTimeoutSeconds: import_zod.z.number().nonnegative().optional(),
|
|
446
428
|
flightRecorder: import_zod.z.boolean().optional(),
|
|
447
429
|
approvers: import_zod.z.object({
|
|
448
430
|
native: import_zod.z.boolean().optional(),
|
|
@@ -1664,14 +1646,12 @@ function getPersistentDecision(toolName) {
|
|
|
1664
1646
|
}
|
|
1665
1647
|
return null;
|
|
1666
1648
|
}
|
|
1667
|
-
async function
|
|
1649
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd) {
|
|
1668
1650
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
1669
|
-
const
|
|
1670
|
-
const
|
|
1671
|
-
const onAbort = () => checkCtrl.abort();
|
|
1672
|
-
if (signal) signal.addEventListener("abort", onAbort);
|
|
1651
|
+
const ctrl = new AbortController();
|
|
1652
|
+
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
1673
1653
|
try {
|
|
1674
|
-
const
|
|
1654
|
+
const res = await fetch(`${base}/check`, {
|
|
1675
1655
|
method: "POST",
|
|
1676
1656
|
headers: { "Content-Type": "application/json" },
|
|
1677
1657
|
body: JSON.stringify({
|
|
@@ -1682,32 +1662,34 @@ async function askDaemon(toolName, args, meta, signal, riskMetadata, activityId)
|
|
|
1682
1662
|
fromCLI: true,
|
|
1683
1663
|
// Pass the flight-recorder ID so the daemon uses the same UUID for
|
|
1684
1664
|
// activity-result as the CLI used for the pending activity event.
|
|
1685
|
-
// Without this, the two UUIDs never match and tail.ts never resolves
|
|
1686
|
-
// the pending item.
|
|
1687
1665
|
activityId,
|
|
1688
|
-
...riskMetadata && { riskMetadata }
|
|
1666
|
+
...riskMetadata && { riskMetadata },
|
|
1667
|
+
...cwd && { cwd }
|
|
1689
1668
|
}),
|
|
1690
|
-
signal:
|
|
1669
|
+
signal: ctrl.signal
|
|
1691
1670
|
});
|
|
1692
|
-
if (!
|
|
1693
|
-
const { id } = await
|
|
1694
|
-
|
|
1695
|
-
const waitTimer = setTimeout(() => waitCtrl.abort(), 12e4);
|
|
1696
|
-
const onWaitAbort = () => waitCtrl.abort();
|
|
1697
|
-
if (signal) signal.addEventListener("abort", onWaitAbort);
|
|
1698
|
-
try {
|
|
1699
|
-
const waitRes = await fetch(`${base}/wait/${id}`, { signal: waitCtrl.signal });
|
|
1700
|
-
if (!waitRes.ok) return "deny";
|
|
1701
|
-
const { decision } = await waitRes.json();
|
|
1702
|
-
if (decision === "allow") return "allow";
|
|
1703
|
-
if (decision === "abandoned") return "abandoned";
|
|
1704
|
-
return "deny";
|
|
1705
|
-
} finally {
|
|
1706
|
-
clearTimeout(waitTimer);
|
|
1707
|
-
if (signal) signal.removeEventListener("abort", onWaitAbort);
|
|
1708
|
-
}
|
|
1671
|
+
if (!res.ok) throw new Error("Daemon fail");
|
|
1672
|
+
const { id } = await res.json();
|
|
1673
|
+
return id;
|
|
1709
1674
|
} finally {
|
|
1710
|
-
clearTimeout(
|
|
1675
|
+
clearTimeout(timer);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async function waitForDaemonDecision(id, signal) {
|
|
1679
|
+
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
1680
|
+
const waitCtrl = new AbortController();
|
|
1681
|
+
const waitTimer = setTimeout(() => waitCtrl.abort(), 12e4);
|
|
1682
|
+
const onAbort = () => waitCtrl.abort();
|
|
1683
|
+
if (signal) signal.addEventListener("abort", onAbort);
|
|
1684
|
+
try {
|
|
1685
|
+
const waitRes = await fetch(`${base}/wait/${id}`, { signal: waitCtrl.signal });
|
|
1686
|
+
if (!waitRes.ok) return { decision: "deny" };
|
|
1687
|
+
const { decision, source } = await waitRes.json();
|
|
1688
|
+
if (decision === "allow") return { decision: "allow", source };
|
|
1689
|
+
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
1690
|
+
return { decision: "deny", source };
|
|
1691
|
+
} finally {
|
|
1692
|
+
clearTimeout(waitTimer);
|
|
1711
1693
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
1712
1694
|
}
|
|
1713
1695
|
}
|
|
@@ -1754,12 +1736,12 @@ function notifyActivity(data) {
|
|
|
1754
1736
|
}
|
|
1755
1737
|
});
|
|
1756
1738
|
}
|
|
1757
|
-
async function authorizeHeadless(toolName, args,
|
|
1739
|
+
async function authorizeHeadless(toolName, args, meta, options) {
|
|
1758
1740
|
if (!options?.calledFromDaemon) {
|
|
1759
1741
|
const actId = (0, import_crypto2.randomUUID)();
|
|
1760
1742
|
const actTs = Date.now();
|
|
1761
1743
|
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
1762
|
-
const result = await _authorizeHeadlessCore(toolName, args,
|
|
1744
|
+
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
1763
1745
|
...options,
|
|
1764
1746
|
activityId: actId
|
|
1765
1747
|
});
|
|
@@ -1774,14 +1756,14 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1774
1756
|
}
|
|
1775
1757
|
return result;
|
|
1776
1758
|
}
|
|
1777
|
-
return _authorizeHeadlessCore(toolName, args,
|
|
1759
|
+
return _authorizeHeadlessCore(toolName, args, meta, options);
|
|
1778
1760
|
}
|
|
1779
|
-
async function _authorizeHeadlessCore(toolName, args,
|
|
1761
|
+
async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
1780
1762
|
if (process.env.NODE9_PAUSED === "1") return { approved: true, checkedBy: "paused" };
|
|
1781
1763
|
const pauseState = checkPause();
|
|
1782
1764
|
if (pauseState.paused) return { approved: true, checkedBy: "paused" };
|
|
1783
1765
|
const creds = getCredentials();
|
|
1784
|
-
const config = getConfig();
|
|
1766
|
+
const config = getConfig(options?.cwd);
|
|
1785
1767
|
const isTestEnv2 = !!(process.env.VITEST || process.env.NODE_ENV === "test" || process.env.CI || process.env.NODE9_TESTING === "1");
|
|
1786
1768
|
const approvers = {
|
|
1787
1769
|
...config.settings.approvers || { native: true, browser: true, cloud: true, terminal: true }
|
|
@@ -1826,10 +1808,6 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
1826
1808
|
if (approvers.cloud && creds?.apiKey) {
|
|
1827
1809
|
await auditLocalAllow(toolName, args, "audit-mode", creds, meta);
|
|
1828
1810
|
}
|
|
1829
|
-
sendDesktopNotification(
|
|
1830
|
-
"Node9 Audit Mode",
|
|
1831
|
-
`Would have blocked "${toolName}" (${policyResult.blockedByLabel || "Local Config"}) \u2014 running in audit mode`
|
|
1832
|
-
);
|
|
1833
1811
|
}
|
|
1834
1812
|
}
|
|
1835
1813
|
return { approved: true, checkedBy: "audit" };
|
|
@@ -1889,23 +1867,12 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
1889
1867
|
return { approved: true };
|
|
1890
1868
|
}
|
|
1891
1869
|
let cloudRequestId = null;
|
|
1892
|
-
let isRemoteLocked = false;
|
|
1893
1870
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
1894
1871
|
if (cloudEnforced) {
|
|
1895
1872
|
try {
|
|
1896
1873
|
const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
|
|
1897
1874
|
if (!initResult.pending) {
|
|
1898
1875
|
if (initResult.shadowMode) {
|
|
1899
|
-
console.error(
|
|
1900
|
-
import_chalk2.default.yellow(
|
|
1901
|
-
`
|
|
1902
|
-
\u26A0\uFE0F Node9 Shadow Mode: Action allowed, but would have been blocked by company policy.`
|
|
1903
|
-
)
|
|
1904
|
-
);
|
|
1905
|
-
if (initResult.shadowReason) {
|
|
1906
|
-
console.error(import_chalk2.default.dim(` Reason: ${initResult.shadowReason}
|
|
1907
|
-
`));
|
|
1908
|
-
}
|
|
1909
1876
|
return { approved: true, checkedBy: "cloud" };
|
|
1910
1877
|
}
|
|
1911
1878
|
return {
|
|
@@ -1917,36 +1884,8 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
1917
1884
|
};
|
|
1918
1885
|
}
|
|
1919
1886
|
cloudRequestId = initResult.requestId || null;
|
|
1920
|
-
isRemoteLocked = !!initResult.remoteApprovalOnly;
|
|
1921
1887
|
explainableLabel = "Organization Policy (SaaS)";
|
|
1922
|
-
} catch
|
|
1923
|
-
const error = err;
|
|
1924
|
-
const isAuthError = error.message.includes("401") || error.message.includes("403");
|
|
1925
|
-
const isNetworkError = error.message.includes("fetch") || error.name === "AbortError" || error.message.includes("ECONNREFUSED");
|
|
1926
|
-
const reason = isAuthError ? "Invalid or missing API key. Run `node9 login` to generate a key (must start with n9_live_)." : isNetworkError ? "Could not reach the Node9 cloud. Check your network or API URL." : error.message;
|
|
1927
|
-
console.error(
|
|
1928
|
-
import_chalk2.default.yellow(`
|
|
1929
|
-
\u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + import_chalk2.default.dim(`
|
|
1930
|
-
Falling back to local rules...
|
|
1931
|
-
`)
|
|
1932
|
-
);
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
if (!options?.calledFromDaemon) {
|
|
1936
|
-
if (cloudEnforced && cloudRequestId) {
|
|
1937
|
-
console.error(
|
|
1938
|
-
import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
|
|
1939
|
-
);
|
|
1940
|
-
console.error(
|
|
1941
|
-
import_chalk2.default.cyan(" Dashboard \u2192 ") + import_chalk2.default.bold("Mission Control > Activity Feed\n")
|
|
1942
|
-
);
|
|
1943
|
-
} else if (!cloudEnforced) {
|
|
1944
|
-
const cloudOffReason = !creds?.apiKey ? "no API key \u2014 run `node9 login` to connect" : "privacy mode (cloud disabled)";
|
|
1945
|
-
console.error(
|
|
1946
|
-
import_chalk2.default.dim(`
|
|
1947
|
-
\u{1F6E1}\uFE0F Node9: intercepted "${toolName}" \u2014 cloud off (${cloudOffReason})
|
|
1948
|
-
`)
|
|
1949
|
-
);
|
|
1888
|
+
} catch {
|
|
1950
1889
|
}
|
|
1951
1890
|
}
|
|
1952
1891
|
const abortController = new AbortController();
|
|
@@ -1973,15 +1912,29 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
1973
1912
|
}
|
|
1974
1913
|
let viewerId = null;
|
|
1975
1914
|
const internalToken = getInternalToken();
|
|
1915
|
+
let daemonEntryId = null;
|
|
1916
|
+
if ((approvers.browser || approvers.terminal) && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
1917
|
+
if (cloudEnforced && cloudRequestId) {
|
|
1918
|
+
viewerId = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
|
|
1919
|
+
daemonEntryId = viewerId;
|
|
1920
|
+
} else {
|
|
1921
|
+
try {
|
|
1922
|
+
daemonEntryId = await registerDaemonEntry(
|
|
1923
|
+
toolName,
|
|
1924
|
+
args,
|
|
1925
|
+
meta,
|
|
1926
|
+
riskMetadata,
|
|
1927
|
+
options?.activityId,
|
|
1928
|
+
options?.cwd
|
|
1929
|
+
);
|
|
1930
|
+
} catch {
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1976
1934
|
if (cloudEnforced && cloudRequestId) {
|
|
1977
1935
|
racePromises.push(
|
|
1978
1936
|
(async () => {
|
|
1979
1937
|
try {
|
|
1980
|
-
if (isDaemonRunning() && internalToken && !options?.calledFromDaemon) {
|
|
1981
|
-
viewerId = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(
|
|
1982
|
-
() => null
|
|
1983
|
-
);
|
|
1984
|
-
}
|
|
1985
1938
|
const cloudResult = await pollNode9SaaS(cloudRequestId, creds, signal);
|
|
1986
1939
|
return {
|
|
1987
1940
|
approved: cloudResult.approved,
|
|
@@ -2006,7 +1959,7 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
2006
1959
|
args,
|
|
2007
1960
|
meta?.agent,
|
|
2008
1961
|
explainableLabel,
|
|
2009
|
-
|
|
1962
|
+
false,
|
|
2010
1963
|
signal,
|
|
2011
1964
|
policyMatchedField,
|
|
2012
1965
|
policyMatchedWord
|
|
@@ -2021,96 +1974,31 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
2021
1974
|
reason: isApproved ? void 0 : "The human user clicked 'Block' on the system dialog window.",
|
|
2022
1975
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
2023
1976
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
2024
|
-
blockedByLabel: "User Decision (Native)"
|
|
1977
|
+
blockedByLabel: "User Decision (Native)",
|
|
1978
|
+
decisionSource: "native"
|
|
2025
1979
|
};
|
|
2026
1980
|
})()
|
|
2027
1981
|
);
|
|
2028
1982
|
}
|
|
2029
|
-
if (
|
|
1983
|
+
if (daemonEntryId && (approvers.browser || approvers.terminal)) {
|
|
2030
1984
|
racePromises.push(
|
|
2031
1985
|
(async () => {
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
2049
|
-
const isApproved = daemonDecision === "allow";
|
|
2050
|
-
return {
|
|
2051
|
-
approved: isApproved,
|
|
2052
|
-
reason: isApproved ? void 0 : "The human user rejected this action via the Node9 Browser Dashboard.",
|
|
2053
|
-
checkedBy: isApproved ? "daemon" : void 0,
|
|
2054
|
-
blockedBy: isApproved ? void 0 : "local-decision",
|
|
2055
|
-
blockedByLabel: "User Decision (Browser)"
|
|
2056
|
-
};
|
|
2057
|
-
} catch (err) {
|
|
2058
|
-
throw err;
|
|
2059
|
-
}
|
|
2060
|
-
})()
|
|
2061
|
-
);
|
|
2062
|
-
}
|
|
2063
|
-
if (approvers.terminal && allowTerminalFallback && process.stdout.isTTY) {
|
|
2064
|
-
racePromises.push(
|
|
2065
|
-
(async () => {
|
|
2066
|
-
try {
|
|
2067
|
-
if (explainableLabel.includes("DLP")) {
|
|
2068
|
-
console.log(import_chalk2.default.bgRed.white.bold(` \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
2069
|
-
console.log(
|
|
2070
|
-
import_chalk2.default.red.bold(` A sensitive secret was detected in the tool arguments!`)
|
|
2071
|
-
);
|
|
2072
|
-
} else {
|
|
2073
|
-
console.log(import_chalk2.default.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
|
|
2074
|
-
}
|
|
2075
|
-
console.log(`${import_chalk2.default.bold("Action:")} ${import_chalk2.default.red(toolName)}`);
|
|
2076
|
-
console.log(`${import_chalk2.default.bold("Flagged By:")} ${import_chalk2.default.yellow(explainableLabel)}`);
|
|
2077
|
-
if (isRemoteLocked) {
|
|
2078
|
-
console.log(import_chalk2.default.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
|
|
2079
|
-
`));
|
|
2080
|
-
await new Promise((_, reject) => {
|
|
2081
|
-
signal.addEventListener("abort", () => reject(new Error("Aborted by SaaS")));
|
|
2082
|
-
});
|
|
2083
|
-
}
|
|
2084
|
-
const TIMEOUT_MS = 6e4;
|
|
2085
|
-
let timer;
|
|
2086
|
-
const result = await new Promise((resolve, reject) => {
|
|
2087
|
-
timer = setTimeout(() => reject(new Error("Terminal Timeout")), TIMEOUT_MS);
|
|
2088
|
-
(0, import_prompts.confirm)(
|
|
2089
|
-
{ message: `Authorize? (auto-deny in ${TIMEOUT_MS / 1e3}s)`, default: false },
|
|
2090
|
-
{ signal }
|
|
2091
|
-
).then(resolve).catch(reject);
|
|
2092
|
-
});
|
|
2093
|
-
clearTimeout(timer);
|
|
2094
|
-
return {
|
|
2095
|
-
approved: result,
|
|
2096
|
-
reason: result ? void 0 : "The human user typed 'N' in the terminal to reject this action.",
|
|
2097
|
-
checkedBy: result ? "terminal" : void 0,
|
|
2098
|
-
blockedBy: result ? void 0 : "local-decision",
|
|
2099
|
-
blockedByLabel: "User Decision (Terminal)"
|
|
2100
|
-
};
|
|
2101
|
-
} catch (err) {
|
|
2102
|
-
const error = err;
|
|
2103
|
-
if (error.name === "AbortError" || error.message?.includes("Prompt was canceled") || error.message?.includes("Aborted by SaaS"))
|
|
2104
|
-
throw err;
|
|
2105
|
-
if (error.message === "Terminal Timeout") {
|
|
2106
|
-
return {
|
|
2107
|
-
approved: false,
|
|
2108
|
-
reason: "The terminal prompt timed out without a human response.",
|
|
2109
|
-
blockedBy: "local-decision"
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
throw err;
|
|
2113
|
-
}
|
|
1986
|
+
const { decision: daemonDecision, source: decisionSource } = await waitForDaemonDecision(
|
|
1987
|
+
daemonEntryId,
|
|
1988
|
+
signal
|
|
1989
|
+
);
|
|
1990
|
+
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
1991
|
+
const isApproved = daemonDecision === "allow";
|
|
1992
|
+
const src = decisionSource === "terminal" || decisionSource === "browser" ? decisionSource : approvers.browser ? "browser" : "terminal";
|
|
1993
|
+
const via = src === "terminal" ? "Terminal (node9 tail)" : "Browser Dashboard";
|
|
1994
|
+
return {
|
|
1995
|
+
approved: isApproved,
|
|
1996
|
+
reason: isApproved ? void 0 : `The human user rejected this action via the Node9 ${via}.`,
|
|
1997
|
+
checkedBy: isApproved ? "daemon" : void 0,
|
|
1998
|
+
blockedBy: isApproved ? void 0 : "local-decision",
|
|
1999
|
+
blockedByLabel: `User Decision (${via})`,
|
|
2000
|
+
decisionSource: src
|
|
2001
|
+
};
|
|
2114
2002
|
})()
|
|
2115
2003
|
);
|
|
2116
2004
|
}
|
|
@@ -2161,7 +2049,12 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
2161
2049
|
}
|
|
2162
2050
|
});
|
|
2163
2051
|
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
|
|
2164
|
-
await resolveNode9SaaS(
|
|
2052
|
+
await resolveNode9SaaS(
|
|
2053
|
+
cloudRequestId,
|
|
2054
|
+
creds,
|
|
2055
|
+
finalResult.approved,
|
|
2056
|
+
finalResult.decisionSource ?? finalResult.checkedBy ?? "local"
|
|
2057
|
+
);
|
|
2165
2058
|
}
|
|
2166
2059
|
if (!isManual) {
|
|
2167
2060
|
appendLocalAudit(
|
|
@@ -2209,6 +2102,8 @@ function getConfig(cwd) {
|
|
|
2209
2102
|
mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
|
|
2210
2103
|
if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
|
|
2211
2104
|
if (s.approvalTimeoutMs !== void 0) mergedSettings.approvalTimeoutMs = s.approvalTimeoutMs;
|
|
2105
|
+
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
2106
|
+
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
2212
2107
|
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
2213
2108
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
2214
2109
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -2433,11 +2328,9 @@ async function pollNode9SaaS(requestId, creds, signal) {
|
|
|
2433
2328
|
if (!statusRes.ok) continue;
|
|
2434
2329
|
const { status, reason } = await statusRes.json();
|
|
2435
2330
|
if (status === "APPROVED") {
|
|
2436
|
-
console.error(import_chalk2.default.green("\u2705 Approved via Cloud.\n"));
|
|
2437
2331
|
return { approved: true, reason };
|
|
2438
2332
|
}
|
|
2439
2333
|
if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
|
|
2440
|
-
console.error(import_chalk2.default.red("\u274C Denied via Cloud.\n"));
|
|
2441
2334
|
return { approved: false, reason };
|
|
2442
2335
|
}
|
|
2443
2336
|
} catch {
|
|
@@ -2445,27 +2338,40 @@ async function pollNode9SaaS(requestId, creds, signal) {
|
|
|
2445
2338
|
}
|
|
2446
2339
|
return { approved: false, reason: "Cloud approval timed out after 10 minutes." };
|
|
2447
2340
|
}
|
|
2448
|
-
async function resolveNode9SaaS(requestId, creds, approved) {
|
|
2341
|
+
async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
2449
2342
|
try {
|
|
2450
2343
|
const resolveUrl = `${creds.apiUrl}/requests/${requestId}`;
|
|
2451
2344
|
const ctrl = new AbortController();
|
|
2452
2345
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
2453
|
-
await fetch(resolveUrl, {
|
|
2346
|
+
const res = await fetch(resolveUrl, {
|
|
2454
2347
|
method: "PATCH",
|
|
2455
2348
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
|
|
2456
|
-
body: JSON.stringify({
|
|
2349
|
+
body: JSON.stringify({
|
|
2350
|
+
decision: approved ? "APPROVED" : "DENIED",
|
|
2351
|
+
...decidedBy && { decidedBy }
|
|
2352
|
+
}),
|
|
2457
2353
|
signal: ctrl.signal
|
|
2458
2354
|
});
|
|
2459
2355
|
clearTimeout(timer);
|
|
2460
|
-
|
|
2356
|
+
if (!res.ok) {
|
|
2357
|
+
import_fs3.default.appendFileSync(
|
|
2358
|
+
HOOK_DEBUG_LOG,
|
|
2359
|
+
`[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
|
|
2360
|
+
`
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
import_fs3.default.appendFileSync(
|
|
2365
|
+
HOOK_DEBUG_LOG,
|
|
2366
|
+
`[resolve-cloud] PATCH failed for ${requestId}: ${err.message}
|
|
2367
|
+
`
|
|
2368
|
+
);
|
|
2461
2369
|
}
|
|
2462
2370
|
}
|
|
2463
|
-
var
|
|
2371
|
+
var import_fs3, import_path5, import_os2, import_net, import_crypto2, import_child_process2, import_picomatch, import_safe_regex2, import_sh_syntax, PAUSED_FILE, TRUST_FILE, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, SQL_DML_KEYWORDS, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, DAEMON_PORT, DAEMON_HOST, ACTIVITY_SOCKET_PATH;
|
|
2464
2372
|
var init_core = __esm({
|
|
2465
2373
|
"src/core.ts"() {
|
|
2466
2374
|
"use strict";
|
|
2467
|
-
import_chalk2 = __toESM(require("chalk"));
|
|
2468
|
-
import_prompts = require("@inquirer/prompts");
|
|
2469
2375
|
import_fs3 = __toESM(require("fs"));
|
|
2470
2376
|
import_path5 = __toESM(require("path"));
|
|
2471
2377
|
import_os2 = __toESM(require("os"));
|
|
@@ -2502,8 +2408,8 @@ var init_core = __esm({
|
|
|
2502
2408
|
enableUndo: true,
|
|
2503
2409
|
// 🔥 ALWAYS TRUE BY DEFAULT for the safety net
|
|
2504
2410
|
enableHookLogDebug: true,
|
|
2505
|
-
approvalTimeoutMs:
|
|
2506
|
-
//
|
|
2411
|
+
approvalTimeoutMs: 12e4,
|
|
2412
|
+
// 120-second auto-deny timeout
|
|
2507
2413
|
flightRecorder: true,
|
|
2508
2414
|
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
2509
2415
|
},
|
|
@@ -3767,7 +3673,7 @@ var init_ui = __esm({
|
|
|
3767
3673
|
const res = await fetch('/decision/' + id, {
|
|
3768
3674
|
method: 'POST',
|
|
3769
3675
|
headers: { 'Content-Type': 'application/json', 'X-Node9-Token': CSRF_TOKEN },
|
|
3770
|
-
body: JSON.stringify({ decision, persist: !!persist }),
|
|
3676
|
+
body: JSON.stringify({ decision, persist: !!persist, source: 'browser' }),
|
|
3771
3677
|
});
|
|
3772
3678
|
if (!res.ok) throw new Error('Request failed (HTTP ' + res.status + ')');
|
|
3773
3679
|
card?.remove();
|
|
@@ -3786,7 +3692,7 @@ var init_ui = __esm({
|
|
|
3786
3692
|
const res = await fetch('/decision/' + id, {
|
|
3787
3693
|
method: 'POST',
|
|
3788
3694
|
headers: { 'Content-Type': 'application/json', 'X-Node9-Token': CSRF_TOKEN },
|
|
3789
|
-
body: JSON.stringify({ decision: 'trust', trustDuration: duration }),
|
|
3695
|
+
body: JSON.stringify({ decision: 'trust', trustDuration: duration, source: 'browser' }),
|
|
3790
3696
|
});
|
|
3791
3697
|
if (!res.ok) throw new Error('Request failed (HTTP ' + res.status + ')');
|
|
3792
3698
|
card?.remove();
|
|
@@ -4308,7 +4214,7 @@ data: ${JSON.stringify(data)}
|
|
|
4308
4214
|
`;
|
|
4309
4215
|
sseClients.forEach((client) => {
|
|
4310
4216
|
try {
|
|
4311
|
-
client.write(msg);
|
|
4217
|
+
client.res.write(msg);
|
|
4312
4218
|
} catch {
|
|
4313
4219
|
sseClients.delete(client);
|
|
4314
4220
|
}
|
|
@@ -4354,6 +4260,7 @@ function startDaemon() {
|
|
|
4354
4260
|
const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
|
|
4355
4261
|
const watchMode = process.env.NODE9_WATCH_MODE === "1";
|
|
4356
4262
|
let idleTimer;
|
|
4263
|
+
let browserOpened = false;
|
|
4357
4264
|
function resetIdleTimer() {
|
|
4358
4265
|
if (watchMode) return;
|
|
4359
4266
|
if (idleTimer) clearTimeout(idleTimer);
|
|
@@ -4370,12 +4277,15 @@ function startDaemon() {
|
|
|
4370
4277
|
}
|
|
4371
4278
|
resetIdleTimer();
|
|
4372
4279
|
const server = import_http.default.createServer(async (req, res) => {
|
|
4373
|
-
const
|
|
4280
|
+
const reqUrl = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
4281
|
+
const { pathname } = reqUrl;
|
|
4374
4282
|
if (req.method === "GET" && pathname === "/") {
|
|
4375
4283
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4376
4284
|
return res.end(UI_HTML);
|
|
4377
4285
|
}
|
|
4378
4286
|
if (req.method === "GET" && pathname === "/events") {
|
|
4287
|
+
const capParam = reqUrl.searchParams.get("capabilities") ?? "";
|
|
4288
|
+
const capabilities = capParam ? capParam.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
4379
4289
|
res.writeHead(200, {
|
|
4380
4290
|
"Content-Type": "text/event-stream",
|
|
4381
4291
|
"Cache-Control": "no-cache",
|
|
@@ -4386,7 +4296,8 @@ function startDaemon() {
|
|
|
4386
4296
|
abandonTimer = null;
|
|
4387
4297
|
}
|
|
4388
4298
|
hadBrowserClient = true;
|
|
4389
|
-
|
|
4299
|
+
const sseClient = { res, capabilities };
|
|
4300
|
+
sseClients.add(sseClient);
|
|
4390
4301
|
res.write(
|
|
4391
4302
|
`event: init
|
|
4392
4303
|
data: ${JSON.stringify({
|
|
@@ -4401,7 +4312,7 @@ data: ${JSON.stringify({
|
|
|
4401
4312
|
mcpServer: e.mcpServer
|
|
4402
4313
|
})),
|
|
4403
4314
|
orgName: getOrgName(),
|
|
4404
|
-
autoDenyMs: AUTO_DENY_MS
|
|
4315
|
+
autoDenyMs: getConfig().settings.approvalTimeoutMs ?? AUTO_DENY_MS
|
|
4405
4316
|
})}
|
|
4406
4317
|
|
|
4407
4318
|
`
|
|
@@ -4423,6 +4334,10 @@ data: ${JSON.stringify({
|
|
|
4423
4334
|
|
|
4424
4335
|
`
|
|
4425
4336
|
);
|
|
4337
|
+
res.write(`event: csrf
|
|
4338
|
+
data: ${JSON.stringify({ token: csrfToken })}
|
|
4339
|
+
|
|
4340
|
+
`);
|
|
4426
4341
|
for (const item of activityRing) {
|
|
4427
4342
|
res.write(`event: ${item.event}
|
|
4428
4343
|
data: ${JSON.stringify(item.data)}
|
|
@@ -4430,7 +4345,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4430
4345
|
`);
|
|
4431
4346
|
}
|
|
4432
4347
|
return req.on("close", () => {
|
|
4433
|
-
sseClients.delete(
|
|
4348
|
+
sseClients.delete(sseClient);
|
|
4434
4349
|
if (sseClients.size === 0 && pending.size > 0) {
|
|
4435
4350
|
abandonTimer = setTimeout(abandonPending, hadBrowserClient ? 1e4 : 15e3);
|
|
4436
4351
|
}
|
|
@@ -4450,7 +4365,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
4450
4365
|
mcpServer,
|
|
4451
4366
|
riskMetadata,
|
|
4452
4367
|
fromCLI = false,
|
|
4453
|
-
activityId
|
|
4368
|
+
activityId,
|
|
4369
|
+
cwd
|
|
4454
4370
|
} = JSON.parse(body);
|
|
4455
4371
|
const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto3.randomUUID)();
|
|
4456
4372
|
const entry = {
|
|
@@ -4480,7 +4396,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4480
4396
|
pending.delete(id);
|
|
4481
4397
|
broadcast("remove", { id });
|
|
4482
4398
|
}
|
|
4483
|
-
}, AUTO_DENY_MS)
|
|
4399
|
+
}, getConfig().settings.approvalTimeoutMs ?? AUTO_DENY_MS)
|
|
4484
4400
|
};
|
|
4485
4401
|
pending.set(id, entry);
|
|
4486
4402
|
if (!fromCLI) {
|
|
@@ -4492,8 +4408,11 @@ data: ${JSON.stringify(item.data)}
|
|
|
4492
4408
|
status: "pending"
|
|
4493
4409
|
});
|
|
4494
4410
|
}
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4411
|
+
const projectCwd = typeof cwd === "string" && import_path7.default.isAbsolute(cwd) ? cwd : void 0;
|
|
4412
|
+
const projectConfig = getConfig(projectCwd);
|
|
4413
|
+
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
4414
|
+
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
4415
|
+
if (browserEnabled || terminalEnabled) {
|
|
4497
4416
|
broadcast("add", {
|
|
4498
4417
|
id,
|
|
4499
4418
|
toolName,
|
|
@@ -4501,17 +4420,21 @@ data: ${JSON.stringify(item.data)}
|
|
|
4501
4420
|
riskMetadata: entry.riskMetadata,
|
|
4502
4421
|
slackDelegated: entry.slackDelegated,
|
|
4503
4422
|
agent: entry.agent,
|
|
4504
|
-
mcpServer: entry.mcpServer
|
|
4423
|
+
mcpServer: entry.mcpServer,
|
|
4424
|
+
interactive: terminalEnabled
|
|
4505
4425
|
});
|
|
4506
|
-
|
|
4426
|
+
const browserAlreadyOpened = process.env.NODE9_BROWSER_OPENED === "1";
|
|
4427
|
+
if (browserEnabled && !browserOpened && !browserAlreadyOpened) {
|
|
4428
|
+
browserOpened = true;
|
|
4507
4429
|
openBrowser(`http://127.0.0.1:${DAEMON_PORT2}/`);
|
|
4430
|
+
}
|
|
4508
4431
|
}
|
|
4509
4432
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4510
4433
|
res.end(JSON.stringify({ id }));
|
|
4434
|
+
if (slackDelegated) return;
|
|
4511
4435
|
authorizeHeadless(
|
|
4512
4436
|
toolName,
|
|
4513
4437
|
args,
|
|
4514
|
-
false,
|
|
4515
4438
|
{
|
|
4516
4439
|
agent: typeof agent === "string" ? agent : void 0,
|
|
4517
4440
|
mcpServer: typeof mcpServer === "string" ? mcpServer : void 0
|
|
@@ -4521,6 +4444,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
4521
4444
|
const e = pending.get(id);
|
|
4522
4445
|
if (!e) return;
|
|
4523
4446
|
if (result.noApprovalMechanism) return;
|
|
4447
|
+
if (result.checkedBy === "audit") return;
|
|
4448
|
+
if (e.earlyDecision !== null) return;
|
|
4524
4449
|
broadcast("activity-result", {
|
|
4525
4450
|
id,
|
|
4526
4451
|
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
|
|
@@ -4561,18 +4486,31 @@ data: ${JSON.stringify(item.data)}
|
|
|
4561
4486
|
if (!entry) return res.writeHead(404).end();
|
|
4562
4487
|
if (entry.earlyDecision) {
|
|
4563
4488
|
clearTimeout(entry.timer);
|
|
4489
|
+
const source = entry.decisionSource;
|
|
4564
4490
|
pending.delete(id);
|
|
4565
4491
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4566
|
-
const body = {
|
|
4492
|
+
const body = {
|
|
4493
|
+
decision: entry.earlyDecision
|
|
4494
|
+
};
|
|
4567
4495
|
if (entry.earlyReason) body.reason = entry.earlyReason;
|
|
4496
|
+
if (source) body.source = source;
|
|
4568
4497
|
return res.end(JSON.stringify(body));
|
|
4569
4498
|
}
|
|
4570
4499
|
entry.waiter = (d, reason) => {
|
|
4571
4500
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4572
4501
|
const body = { decision: d };
|
|
4573
4502
|
if (reason) body.reason = reason;
|
|
4503
|
+
if (entry.decisionSource) body.source = entry.decisionSource;
|
|
4574
4504
|
res.end(JSON.stringify(body));
|
|
4575
4505
|
};
|
|
4506
|
+
req.on("close", () => {
|
|
4507
|
+
const e = pending.get(id);
|
|
4508
|
+
if (e && e.waiter && e.earlyDecision === null) {
|
|
4509
|
+
clearTimeout(e.timer);
|
|
4510
|
+
pending.delete(id);
|
|
4511
|
+
broadcast("remove", { id });
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4576
4514
|
return;
|
|
4577
4515
|
}
|
|
4578
4516
|
if (req.method === "POST" && pathname.startsWith("/decision/")) {
|
|
@@ -4581,7 +4519,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
4581
4519
|
const id = pathname.split("/").pop();
|
|
4582
4520
|
const entry = pending.get(id);
|
|
4583
4521
|
if (!entry) return res.writeHead(404).end();
|
|
4584
|
-
|
|
4522
|
+
if (entry.earlyDecision !== null) {
|
|
4523
|
+
res.writeHead(409, { "Content-Type": "application/json" });
|
|
4524
|
+
return res.end(JSON.stringify({ conflict: true, decision: entry.earlyDecision }));
|
|
4525
|
+
}
|
|
4526
|
+
const { decision, persist, trustDuration, reason, source } = JSON.parse(
|
|
4527
|
+
await readBody(req)
|
|
4528
|
+
);
|
|
4585
4529
|
if (decision === "trust" && trustDuration) {
|
|
4586
4530
|
const ms = TRUST_DURATIONS[trustDuration] ?? 60 * 6e4;
|
|
4587
4531
|
writeTrustEntry(entry.toolName, ms);
|
|
@@ -4611,6 +4555,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
4611
4555
|
decision: resolvedDecision
|
|
4612
4556
|
});
|
|
4613
4557
|
clearTimeout(entry.timer);
|
|
4558
|
+
const VALID_SOURCES = /* @__PURE__ */ new Set(["terminal", "browser", "native"]);
|
|
4559
|
+
if (source && VALID_SOURCES.has(source)) entry.decisionSource = source;
|
|
4614
4560
|
if (entry.waiter) {
|
|
4615
4561
|
entry.waiter(resolvedDecision, reason);
|
|
4616
4562
|
pending.delete(id);
|
|
@@ -4633,7 +4579,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4633
4579
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4634
4580
|
return res.end(JSON.stringify({ ...s, autoStarted }));
|
|
4635
4581
|
} catch (err) {
|
|
4636
|
-
console.error(
|
|
4582
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /settings failed:"), err);
|
|
4637
4583
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
4638
4584
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
4639
4585
|
}
|
|
@@ -4664,7 +4610,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4664
4610
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4665
4611
|
return res.end(JSON.stringify({ hasKey: hasStoredSlackKey(), enabled: s.slackEnabled }));
|
|
4666
4612
|
} catch (err) {
|
|
4667
|
-
console.error(
|
|
4613
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /slack-status failed:"), err);
|
|
4668
4614
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
4669
4615
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
4670
4616
|
}
|
|
@@ -4813,14 +4759,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4813
4759
|
});
|
|
4814
4760
|
return;
|
|
4815
4761
|
}
|
|
4816
|
-
console.error(
|
|
4762
|
+
console.error(import_chalk2.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
|
|
4817
4763
|
process.exit(1);
|
|
4818
4764
|
});
|
|
4819
4765
|
if (!daemonRejectionHandlerRegistered) {
|
|
4820
4766
|
daemonRejectionHandlerRegistered = true;
|
|
4821
4767
|
process.on("unhandledRejection", (reason) => {
|
|
4822
4768
|
const stack = reason instanceof Error ? reason.stack : String(reason);
|
|
4823
|
-
console.error(
|
|
4769
|
+
console.error(import_chalk2.default.red("[node9 daemon] unhandled rejection \u2014 keeping daemon alive:"), stack);
|
|
4824
4770
|
});
|
|
4825
4771
|
}
|
|
4826
4772
|
server.listen(DAEMON_PORT2, DAEMON_HOST2, () => {
|
|
@@ -4829,10 +4775,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
4829
4775
|
JSON.stringify({ pid: process.pid, port: DAEMON_PORT2, internalToken, autoStarted }),
|
|
4830
4776
|
{ mode: 384 }
|
|
4831
4777
|
);
|
|
4832
|
-
console.log(
|
|
4778
|
+
console.log(import_chalk2.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
|
|
4833
4779
|
});
|
|
4834
4780
|
if (watchMode) {
|
|
4835
|
-
console.log(
|
|
4781
|
+
console.log(import_chalk2.default.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
4836
4782
|
}
|
|
4837
4783
|
try {
|
|
4838
4784
|
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
@@ -4883,13 +4829,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
4883
4829
|
});
|
|
4884
4830
|
}
|
|
4885
4831
|
function stopDaemon() {
|
|
4886
|
-
if (!import_fs5.default.existsSync(DAEMON_PID_FILE)) return console.log(
|
|
4832
|
+
if (!import_fs5.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk2.default.yellow("Not running."));
|
|
4887
4833
|
try {
|
|
4888
4834
|
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4889
4835
|
process.kill(pid, "SIGTERM");
|
|
4890
|
-
console.log(
|
|
4836
|
+
console.log(import_chalk2.default.green("\u2705 Stopped."));
|
|
4891
4837
|
} catch {
|
|
4892
|
-
console.log(
|
|
4838
|
+
console.log(import_chalk2.default.gray("Cleaned up stale PID file."));
|
|
4893
4839
|
} finally {
|
|
4894
4840
|
try {
|
|
4895
4841
|
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
@@ -4902,10 +4848,10 @@ function daemonStatus() {
|
|
|
4902
4848
|
try {
|
|
4903
4849
|
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4904
4850
|
process.kill(pid, 0);
|
|
4905
|
-
console.log(
|
|
4851
|
+
console.log(import_chalk2.default.green("Node9 daemon: running"));
|
|
4906
4852
|
return;
|
|
4907
4853
|
} catch {
|
|
4908
|
-
console.log(
|
|
4854
|
+
console.log(import_chalk2.default.yellow("Node9 daemon: not running (stale PID)"));
|
|
4909
4855
|
return;
|
|
4910
4856
|
}
|
|
4911
4857
|
}
|
|
@@ -4914,12 +4860,12 @@ function daemonStatus() {
|
|
|
4914
4860
|
timeout: 500
|
|
4915
4861
|
});
|
|
4916
4862
|
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT2}`)) {
|
|
4917
|
-
console.log(
|
|
4863
|
+
console.log(import_chalk2.default.yellow("Node9 daemon: running (no PID file \u2014 orphaned)"));
|
|
4918
4864
|
} else {
|
|
4919
|
-
console.log(
|
|
4865
|
+
console.log(import_chalk2.default.yellow("Node9 daemon: not running"));
|
|
4920
4866
|
}
|
|
4921
4867
|
}
|
|
4922
|
-
var import_http, import_net2, import_fs5, import_path7, import_os4, import_child_process3, import_crypto3,
|
|
4868
|
+
var import_http, import_net2, import_fs5, import_path7, import_os4, import_child_process3, import_crypto3, import_chalk2, daemonRejectionHandlerRegistered, ACTIVITY_SOCKET_PATH2, DAEMON_PORT2, DAEMON_HOST2, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, TRUST_DURATIONS, SECRET_KEY_RE, AUTO_DENY_MS, autoStarted, pending, sseClients, abandonTimer, daemonServer, hadBrowserClient, ACTIVITY_RING_SIZE, activityRing;
|
|
4923
4869
|
var init_daemon = __esm({
|
|
4924
4870
|
"src/daemon/index.ts"() {
|
|
4925
4871
|
"use strict";
|
|
@@ -4931,7 +4877,7 @@ var init_daemon = __esm({
|
|
|
4931
4877
|
import_os4 = __toESM(require("os"));
|
|
4932
4878
|
import_child_process3 = require("child_process");
|
|
4933
4879
|
import_crypto3 = require("crypto");
|
|
4934
|
-
|
|
4880
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
4935
4881
|
init_core();
|
|
4936
4882
|
init_shields();
|
|
4937
4883
|
daemonRejectionHandlerRegistered = false;
|
|
@@ -4981,17 +4927,17 @@ function formatBase(activity) {
|
|
|
4981
4927
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
4982
4928
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
|
|
4983
4929
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
4984
|
-
return `${
|
|
4930
|
+
return `${import_chalk3.default.gray(time)} ${icon} ${import_chalk3.default.white.bold(toolName)} ${import_chalk3.default.dim(argsPreview)}`;
|
|
4985
4931
|
}
|
|
4986
4932
|
function renderResult(activity, result) {
|
|
4987
4933
|
const base = formatBase(activity);
|
|
4988
4934
|
let status;
|
|
4989
4935
|
if (result.status === "allow") {
|
|
4990
|
-
status =
|
|
4936
|
+
status = import_chalk3.default.green("\u2713 ALLOW");
|
|
4991
4937
|
} else if (result.status === "dlp") {
|
|
4992
|
-
status =
|
|
4938
|
+
status = import_chalk3.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
4993
4939
|
} else {
|
|
4994
|
-
status =
|
|
4940
|
+
status = import_chalk3.default.red("\u2717 BLOCK");
|
|
4995
4941
|
}
|
|
4996
4942
|
if (process.stdout.isTTY) {
|
|
4997
4943
|
import_readline.default.clearLine(process.stdout, 0);
|
|
@@ -5001,7 +4947,7 @@ function renderResult(activity, result) {
|
|
|
5001
4947
|
}
|
|
5002
4948
|
function renderPending(activity) {
|
|
5003
4949
|
if (!process.stdout.isTTY) return;
|
|
5004
|
-
process.stdout.write(`${formatBase(activity)} ${
|
|
4950
|
+
process.stdout.write(`${formatBase(activity)} ${import_chalk3.default.yellow("\u25CF \u2026")}\r`);
|
|
5005
4951
|
}
|
|
5006
4952
|
async function ensureDaemon() {
|
|
5007
4953
|
let pidPort = null;
|
|
@@ -5010,7 +4956,7 @@ async function ensureDaemon() {
|
|
|
5010
4956
|
const { port } = JSON.parse(import_fs7.default.readFileSync(PID_FILE, "utf-8"));
|
|
5011
4957
|
pidPort = port;
|
|
5012
4958
|
} catch {
|
|
5013
|
-
console.error(
|
|
4959
|
+
console.error(import_chalk3.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
5014
4960
|
}
|
|
5015
4961
|
}
|
|
5016
4962
|
const checkPort = pidPort ?? DAEMON_PORT2;
|
|
@@ -5021,7 +4967,7 @@ async function ensureDaemon() {
|
|
|
5021
4967
|
if (res.ok) return checkPort;
|
|
5022
4968
|
} catch {
|
|
5023
4969
|
}
|
|
5024
|
-
console.log(
|
|
4970
|
+
console.log(import_chalk3.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
5025
4971
|
const child = (0, import_child_process5.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
5026
4972
|
detached: true,
|
|
5027
4973
|
stdio: "ignore",
|
|
@@ -5038,9 +4984,51 @@ async function ensureDaemon() {
|
|
|
5038
4984
|
} catch {
|
|
5039
4985
|
}
|
|
5040
4986
|
}
|
|
5041
|
-
console.error(
|
|
4987
|
+
console.error(import_chalk3.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
5042
4988
|
process.exit(1);
|
|
5043
4989
|
}
|
|
4990
|
+
function postDecisionHttp(id, decision, csrfToken, port) {
|
|
4991
|
+
return new Promise((resolve, reject) => {
|
|
4992
|
+
const body = JSON.stringify({ decision, source: "terminal" });
|
|
4993
|
+
const req = import_http2.default.request(
|
|
4994
|
+
{
|
|
4995
|
+
hostname: "127.0.0.1",
|
|
4996
|
+
port,
|
|
4997
|
+
path: `/decision/${id}`,
|
|
4998
|
+
method: "POST",
|
|
4999
|
+
headers: {
|
|
5000
|
+
"Content-Type": "application/json",
|
|
5001
|
+
"Content-Length": Buffer.byteLength(body),
|
|
5002
|
+
"X-Node9-Token": csrfToken
|
|
5003
|
+
}
|
|
5004
|
+
},
|
|
5005
|
+
(res) => {
|
|
5006
|
+
res.resume();
|
|
5007
|
+
if (res.statusCode === 200 || res.statusCode === 409) resolve();
|
|
5008
|
+
else reject(new Error(`POST /decision returned ${res.statusCode}`));
|
|
5009
|
+
}
|
|
5010
|
+
);
|
|
5011
|
+
req.on("error", reject);
|
|
5012
|
+
req.end(body);
|
|
5013
|
+
});
|
|
5014
|
+
}
|
|
5015
|
+
function buildCardLines(req) {
|
|
5016
|
+
const argsStr = JSON.stringify(req.args ?? {}).replace(/\s+/g, " ");
|
|
5017
|
+
const argsPreview = argsStr.length > 60 ? argsStr.slice(0, 60) + "\u2026" : argsStr;
|
|
5018
|
+
const tierLabel = req.riskMetadata?.tier != null ? req.riskMetadata.tier <= 2 ? `${YELLOW}\u26A0 Tier ${req.riskMetadata.tier}` : `${RED}\u{1F6D1} Tier ${req.riskMetadata.tier}` : `${YELLOW}\u26A0 Review`;
|
|
5019
|
+
const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
|
|
5020
|
+
return [
|
|
5021
|
+
``,
|
|
5022
|
+
`${BOLD}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET}`,
|
|
5023
|
+
`${CYAN}\u2551${RESET} Tool: ${BOLD}${req.toolName}${RESET}`,
|
|
5024
|
+
`${CYAN}\u2551${RESET} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET}`,
|
|
5025
|
+
`${CYAN}\u2551${RESET} Args: ${GRAY}${argsPreview}${RESET}`,
|
|
5026
|
+
`${CYAN}\u255A${RESET}`,
|
|
5027
|
+
``,
|
|
5028
|
+
` ${BOLD}${GREEN}[A]${RESET} Allow ${BOLD}${RED}[D]${RESET} Deny`,
|
|
5029
|
+
``
|
|
5030
|
+
];
|
|
5031
|
+
}
|
|
5044
5032
|
async function startTail(options = {}) {
|
|
5045
5033
|
const port = await ensureDaemon();
|
|
5046
5034
|
if (options.clear) {
|
|
@@ -5067,7 +5055,7 @@ async function startTail(options = {}) {
|
|
|
5067
5055
|
req2.end();
|
|
5068
5056
|
});
|
|
5069
5057
|
if (result.ok) {
|
|
5070
|
-
console.log(
|
|
5058
|
+
console.log(import_chalk3.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
5071
5059
|
} else if (result.code === "ECONNREFUSED") {
|
|
5072
5060
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
5073
5061
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -5078,27 +5066,130 @@ async function startTail(options = {}) {
|
|
|
5078
5066
|
return;
|
|
5079
5067
|
}
|
|
5080
5068
|
const connectionTime = Date.now();
|
|
5081
|
-
const
|
|
5082
|
-
|
|
5083
|
-
|
|
5069
|
+
const activityPending = /* @__PURE__ */ new Map();
|
|
5070
|
+
let csrfToken = "";
|
|
5071
|
+
const approvalQueue = [];
|
|
5072
|
+
let cardActive = false;
|
|
5073
|
+
let cardLineCount = 0;
|
|
5074
|
+
let cancelActiveCard = null;
|
|
5075
|
+
const canApprove = process.stdout.isTTY && process.stdin.isTTY;
|
|
5076
|
+
if (canApprove) import_readline.default.emitKeypressEvents(process.stdin);
|
|
5077
|
+
function clearCard() {
|
|
5078
|
+
if (cardLineCount > 0) {
|
|
5079
|
+
process.stdout.write(RESTORE_CURSOR + ERASE_DOWN);
|
|
5080
|
+
cardLineCount = 0;
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
function printCard(req2) {
|
|
5084
|
+
process.stdout.write(HIDE_CURSOR + SAVE_CURSOR);
|
|
5085
|
+
const lines = buildCardLines(req2);
|
|
5086
|
+
for (const line of lines) process.stdout.write(line + "\n");
|
|
5087
|
+
cardLineCount = lines.length;
|
|
5088
|
+
}
|
|
5089
|
+
function showNextCard() {
|
|
5090
|
+
if (cardActive || approvalQueue.length === 0 || !canApprove) return;
|
|
5091
|
+
try {
|
|
5092
|
+
process.stdin.setRawMode(true);
|
|
5093
|
+
} catch {
|
|
5094
|
+
cardActive = false;
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
cardActive = true;
|
|
5098
|
+
const req2 = approvalQueue[0];
|
|
5099
|
+
printCard(req2);
|
|
5100
|
+
let settled = false;
|
|
5101
|
+
let onKeypress = null;
|
|
5102
|
+
const cleanup = () => {
|
|
5103
|
+
const handler = onKeypress;
|
|
5104
|
+
onKeypress = null;
|
|
5105
|
+
if (handler) process.stdin.removeListener("keypress", handler);
|
|
5106
|
+
try {
|
|
5107
|
+
process.stdin.setRawMode(false);
|
|
5108
|
+
} catch {
|
|
5109
|
+
}
|
|
5110
|
+
process.stdin.pause();
|
|
5111
|
+
cancelActiveCard = null;
|
|
5112
|
+
};
|
|
5113
|
+
const settle = (decision) => {
|
|
5114
|
+
if (settled) return;
|
|
5115
|
+
settled = true;
|
|
5116
|
+
cleanup();
|
|
5117
|
+
clearCard();
|
|
5118
|
+
process.stdout.write(SHOW_CURSOR);
|
|
5119
|
+
postDecisionHttp(req2.id, decision, csrfToken, port).catch((err) => {
|
|
5120
|
+
try {
|
|
5121
|
+
import_fs7.default.appendFileSync(
|
|
5122
|
+
import_path9.default.join(import_os6.default.homedir(), ".node9", "hook-debug.log"),
|
|
5123
|
+
`[tail] POST /decision failed: ${String(err)}
|
|
5124
|
+
`
|
|
5125
|
+
);
|
|
5126
|
+
} catch {
|
|
5127
|
+
}
|
|
5128
|
+
});
|
|
5129
|
+
const decisionLabel = decision === "allow" ? import_chalk3.default.green("\u2713 ALLOWED (terminal)") : import_chalk3.default.red("\u2717 DENIED (terminal)");
|
|
5130
|
+
console.log(`${import_chalk3.default.cyan("\u25C6")} ${import_chalk3.default.bold(req2.toolName.padEnd(16))} ${decisionLabel}`);
|
|
5131
|
+
approvalQueue.shift();
|
|
5132
|
+
cardActive = false;
|
|
5133
|
+
showNextCard();
|
|
5134
|
+
};
|
|
5135
|
+
cancelActiveCard = () => {
|
|
5136
|
+
if (settled) return;
|
|
5137
|
+
settled = true;
|
|
5138
|
+
cleanup();
|
|
5139
|
+
clearCard();
|
|
5140
|
+
process.stdout.write(SHOW_CURSOR);
|
|
5141
|
+
approvalQueue.shift();
|
|
5142
|
+
cardActive = false;
|
|
5143
|
+
showNextCard();
|
|
5144
|
+
};
|
|
5145
|
+
process.stdin.resume();
|
|
5146
|
+
onKeypress = (_str, key) => {
|
|
5147
|
+
const name = key?.name ?? "";
|
|
5148
|
+
if (name === "a") {
|
|
5149
|
+
settle("allow");
|
|
5150
|
+
} else if (name === "d" || name === "return" || name === "enter" || key?.ctrl && name === "c") {
|
|
5151
|
+
settle("deny");
|
|
5152
|
+
}
|
|
5153
|
+
};
|
|
5154
|
+
process.stdin.on("keypress", onKeypress);
|
|
5155
|
+
}
|
|
5156
|
+
const dashboardUrl = `http://127.0.0.1:${port}/`;
|
|
5157
|
+
try {
|
|
5158
|
+
const browserEnabled = getConfig().settings.approvers?.browser !== false;
|
|
5159
|
+
if (browserEnabled) {
|
|
5160
|
+
if (process.platform === "darwin") (0, import_child_process5.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
5161
|
+
else if (process.platform === "win32")
|
|
5162
|
+
(0, import_child_process5.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
|
|
5163
|
+
else (0, import_child_process5.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
5164
|
+
}
|
|
5165
|
+
} catch {
|
|
5166
|
+
}
|
|
5167
|
+
console.log(import_chalk3.default.cyan.bold(`
|
|
5168
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk3.default.dim(`\u2192 ${dashboardUrl}`));
|
|
5169
|
+
if (canApprove) {
|
|
5170
|
+
console.log(import_chalk3.default.dim("Interactive approvals enabled. [A] Allow [D] Deny"));
|
|
5171
|
+
}
|
|
5084
5172
|
if (options.history) {
|
|
5085
|
-
console.log(
|
|
5173
|
+
console.log(import_chalk3.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
5086
5174
|
} else {
|
|
5087
5175
|
console.log(
|
|
5088
|
-
|
|
5176
|
+
import_chalk3.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
|
|
5089
5177
|
);
|
|
5090
5178
|
}
|
|
5091
5179
|
process.on("SIGINT", () => {
|
|
5180
|
+
clearCard();
|
|
5181
|
+
process.stdout.write(SHOW_CURSOR);
|
|
5092
5182
|
if (process.stdout.isTTY) {
|
|
5093
5183
|
import_readline.default.clearLine(process.stdout, 0);
|
|
5094
5184
|
import_readline.default.cursorTo(process.stdout, 0);
|
|
5095
5185
|
}
|
|
5096
|
-
console.log(
|
|
5186
|
+
console.log(import_chalk3.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
5097
5187
|
process.exit(0);
|
|
5098
5188
|
});
|
|
5099
|
-
const
|
|
5189
|
+
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
5190
|
+
const req = import_http2.default.get(sseUrl, (res) => {
|
|
5100
5191
|
if (res.statusCode !== 200) {
|
|
5101
|
-
console.error(
|
|
5192
|
+
console.error(import_chalk3.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
5102
5193
|
process.exit(1);
|
|
5103
5194
|
}
|
|
5104
5195
|
let currentEvent = "";
|
|
@@ -5122,15 +5213,66 @@ async function startTail(options = {}) {
|
|
|
5122
5213
|
}
|
|
5123
5214
|
});
|
|
5124
5215
|
rl.on("close", () => {
|
|
5216
|
+
clearCard();
|
|
5217
|
+
process.stdout.write(SHOW_CURSOR);
|
|
5125
5218
|
if (process.stdout.isTTY) {
|
|
5126
5219
|
import_readline.default.clearLine(process.stdout, 0);
|
|
5127
5220
|
import_readline.default.cursorTo(process.stdout, 0);
|
|
5128
5221
|
}
|
|
5129
|
-
console.log(
|
|
5222
|
+
console.log(import_chalk3.default.red("\n\u274C Daemon disconnected."));
|
|
5130
5223
|
process.exit(1);
|
|
5131
5224
|
});
|
|
5132
5225
|
});
|
|
5133
5226
|
function handleMessage(event, rawData) {
|
|
5227
|
+
if (event === "csrf") {
|
|
5228
|
+
try {
|
|
5229
|
+
const parsed = JSON.parse(rawData);
|
|
5230
|
+
if (parsed.token) csrfToken = parsed.token;
|
|
5231
|
+
} catch {
|
|
5232
|
+
}
|
|
5233
|
+
return;
|
|
5234
|
+
}
|
|
5235
|
+
if (event === "init") {
|
|
5236
|
+
try {
|
|
5237
|
+
const parsed = JSON.parse(rawData);
|
|
5238
|
+
if (canApprove && Array.isArray(parsed.requests)) {
|
|
5239
|
+
for (const r of parsed.requests) {
|
|
5240
|
+
approvalQueue.push(r);
|
|
5241
|
+
}
|
|
5242
|
+
showNextCard();
|
|
5243
|
+
}
|
|
5244
|
+
} catch {
|
|
5245
|
+
}
|
|
5246
|
+
return;
|
|
5247
|
+
}
|
|
5248
|
+
if (event === "add") {
|
|
5249
|
+
if (canApprove) {
|
|
5250
|
+
try {
|
|
5251
|
+
const parsed = JSON.parse(rawData);
|
|
5252
|
+
if (parsed.interactive !== false) {
|
|
5253
|
+
approvalQueue.push(parsed);
|
|
5254
|
+
showNextCard();
|
|
5255
|
+
}
|
|
5256
|
+
} catch {
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
if (event === "remove") {
|
|
5262
|
+
try {
|
|
5263
|
+
const { id } = JSON.parse(rawData);
|
|
5264
|
+
const idx = approvalQueue.findIndex((r) => r.id === id);
|
|
5265
|
+
if (idx !== -1) {
|
|
5266
|
+
if (idx === 0 && cardActive && cancelActiveCard) {
|
|
5267
|
+
cancelActiveCard();
|
|
5268
|
+
} else {
|
|
5269
|
+
approvalQueue.splice(idx, 1);
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
} catch {
|
|
5273
|
+
}
|
|
5274
|
+
return;
|
|
5275
|
+
}
|
|
5134
5276
|
let data;
|
|
5135
5277
|
try {
|
|
5136
5278
|
data = JSON.parse(rawData);
|
|
@@ -5143,37 +5285,38 @@ async function startTail(options = {}) {
|
|
|
5143
5285
|
renderResult(data, data);
|
|
5144
5286
|
return;
|
|
5145
5287
|
}
|
|
5146
|
-
|
|
5288
|
+
activityPending.set(data.id, data);
|
|
5147
5289
|
const slowTool = /bash|shell|query|sql|agent/i.test(data.tool);
|
|
5148
5290
|
if (slowTool) renderPending(data);
|
|
5149
5291
|
}
|
|
5150
5292
|
if (event === "activity-result") {
|
|
5151
|
-
const original =
|
|
5293
|
+
const original = activityPending.get(data.id);
|
|
5152
5294
|
if (original) {
|
|
5153
5295
|
renderResult(original, data);
|
|
5154
|
-
|
|
5296
|
+
activityPending.delete(data.id);
|
|
5155
5297
|
}
|
|
5156
5298
|
}
|
|
5157
5299
|
}
|
|
5158
5300
|
req.on("error", (err) => {
|
|
5159
5301
|
const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
|
|
5160
|
-
console.error(
|
|
5302
|
+
console.error(import_chalk3.default.red(`
|
|
5161
5303
|
\u274C ${msg}`));
|
|
5162
5304
|
process.exit(1);
|
|
5163
5305
|
});
|
|
5164
5306
|
}
|
|
5165
|
-
var import_http2,
|
|
5307
|
+
var import_http2, import_chalk3, import_fs7, import_os6, import_path9, import_readline, import_child_process5, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, SAVE_CURSOR, RESTORE_CURSOR;
|
|
5166
5308
|
var init_tail = __esm({
|
|
5167
5309
|
"src/tui/tail.ts"() {
|
|
5168
5310
|
"use strict";
|
|
5169
5311
|
import_http2 = __toESM(require("http"));
|
|
5170
|
-
|
|
5312
|
+
import_chalk3 = __toESM(require("chalk"));
|
|
5171
5313
|
import_fs7 = __toESM(require("fs"));
|
|
5172
5314
|
import_os6 = __toESM(require("os"));
|
|
5173
5315
|
import_path9 = __toESM(require("path"));
|
|
5174
5316
|
import_readline = __toESM(require("readline"));
|
|
5175
5317
|
import_child_process5 = require("child_process");
|
|
5176
5318
|
init_daemon();
|
|
5319
|
+
init_core();
|
|
5177
5320
|
PID_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
|
|
5178
5321
|
ICONS = {
|
|
5179
5322
|
bash: "\u{1F4BB}",
|
|
@@ -5192,6 +5335,18 @@ var init_tail = __esm({
|
|
|
5192
5335
|
delete: "\u{1F5D1}\uFE0F",
|
|
5193
5336
|
web: "\u{1F310}"
|
|
5194
5337
|
};
|
|
5338
|
+
RESET = "\x1B[0m";
|
|
5339
|
+
BOLD = "\x1B[1m";
|
|
5340
|
+
RED = "\x1B[31m";
|
|
5341
|
+
YELLOW = "\x1B[33m";
|
|
5342
|
+
CYAN = "\x1B[36m";
|
|
5343
|
+
GRAY = "\x1B[90m";
|
|
5344
|
+
GREEN = "\x1B[32m";
|
|
5345
|
+
HIDE_CURSOR = "\x1B[?25l";
|
|
5346
|
+
SHOW_CURSOR = "\x1B[?25h";
|
|
5347
|
+
ERASE_DOWN = "\x1B[J";
|
|
5348
|
+
SAVE_CURSOR = "\x1B7";
|
|
5349
|
+
RESTORE_CURSOR = "\x1B8";
|
|
5195
5350
|
}
|
|
5196
5351
|
});
|
|
5197
5352
|
|
|
@@ -5203,11 +5358,11 @@ init_core();
|
|
|
5203
5358
|
var import_fs4 = __toESM(require("fs"));
|
|
5204
5359
|
var import_path6 = __toESM(require("path"));
|
|
5205
5360
|
var import_os3 = __toESM(require("os"));
|
|
5206
|
-
var
|
|
5207
|
-
var
|
|
5361
|
+
var import_chalk = __toESM(require("chalk"));
|
|
5362
|
+
var import_prompts = require("@inquirer/prompts");
|
|
5208
5363
|
function printDaemonTip() {
|
|
5209
5364
|
console.log(
|
|
5210
|
-
|
|
5365
|
+
import_chalk.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + import_chalk.default.white("\n To view your history or manage persistent rules, run:") + import_chalk.default.green("\n node9 daemon --openui")
|
|
5211
5366
|
);
|
|
5212
5367
|
}
|
|
5213
5368
|
function fullPathCommand(subcommand) {
|
|
@@ -5253,10 +5408,10 @@ function teardownClaude() {
|
|
|
5253
5408
|
if (changed) {
|
|
5254
5409
|
writeJson(hooksPath, settings);
|
|
5255
5410
|
console.log(
|
|
5256
|
-
|
|
5411
|
+
import_chalk.default.green(" \u2705 Removed PreToolUse / PostToolUse hooks from ~/.claude/settings.json")
|
|
5257
5412
|
);
|
|
5258
5413
|
} else {
|
|
5259
|
-
console.log(
|
|
5414
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9 hooks found in ~/.claude/settings.json"));
|
|
5260
5415
|
}
|
|
5261
5416
|
}
|
|
5262
5417
|
const claudeConfig = readJson(mcpPath);
|
|
@@ -5273,7 +5428,7 @@ function teardownClaude() {
|
|
|
5273
5428
|
mcpChanged = true;
|
|
5274
5429
|
} else if (server.command === "node9") {
|
|
5275
5430
|
console.warn(
|
|
5276
|
-
|
|
5431
|
+
import_chalk.default.yellow(
|
|
5277
5432
|
` \u26A0\uFE0F Cannot unwrap MCP server "${name}" in ~/.claude.json \u2014 args is empty. Remove it manually.`
|
|
5278
5433
|
)
|
|
5279
5434
|
);
|
|
@@ -5281,7 +5436,7 @@ function teardownClaude() {
|
|
|
5281
5436
|
}
|
|
5282
5437
|
if (mcpChanged) {
|
|
5283
5438
|
writeJson(mcpPath, claudeConfig);
|
|
5284
|
-
console.log(
|
|
5439
|
+
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.claude.json"));
|
|
5285
5440
|
}
|
|
5286
5441
|
}
|
|
5287
5442
|
}
|
|
@@ -5290,7 +5445,7 @@ function teardownGemini() {
|
|
|
5290
5445
|
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5291
5446
|
const settings = readJson(settingsPath);
|
|
5292
5447
|
if (!settings) {
|
|
5293
|
-
console.log(
|
|
5448
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
5294
5449
|
return;
|
|
5295
5450
|
}
|
|
5296
5451
|
let changed = false;
|
|
@@ -5319,9 +5474,9 @@ function teardownGemini() {
|
|
|
5319
5474
|
}
|
|
5320
5475
|
if (changed) {
|
|
5321
5476
|
writeJson(settingsPath, settings);
|
|
5322
|
-
console.log(
|
|
5477
|
+
console.log(import_chalk.default.green(" \u2705 Removed Node9 hooks from ~/.gemini/settings.json"));
|
|
5323
5478
|
} else {
|
|
5324
|
-
console.log(
|
|
5479
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9 hooks found in ~/.gemini/settings.json"));
|
|
5325
5480
|
}
|
|
5326
5481
|
}
|
|
5327
5482
|
function teardownCursor() {
|
|
@@ -5329,7 +5484,7 @@ function teardownCursor() {
|
|
|
5329
5484
|
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5330
5485
|
const mcpConfig = readJson(mcpPath);
|
|
5331
5486
|
if (!mcpConfig?.mcpServers) {
|
|
5332
|
-
console.log(
|
|
5487
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
5333
5488
|
return;
|
|
5334
5489
|
}
|
|
5335
5490
|
let changed = false;
|
|
@@ -5346,9 +5501,9 @@ function teardownCursor() {
|
|
|
5346
5501
|
}
|
|
5347
5502
|
if (changed) {
|
|
5348
5503
|
writeJson(mcpPath, mcpConfig);
|
|
5349
|
-
console.log(
|
|
5504
|
+
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.cursor/mcp.json"));
|
|
5350
5505
|
} else {
|
|
5351
|
-
console.log(
|
|
5506
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.cursor/mcp.json"));
|
|
5352
5507
|
}
|
|
5353
5508
|
}
|
|
5354
5509
|
async function setupClaude() {
|
|
@@ -5369,7 +5524,7 @@ async function setupClaude() {
|
|
|
5369
5524
|
matcher: ".*",
|
|
5370
5525
|
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 60 }]
|
|
5371
5526
|
});
|
|
5372
|
-
console.log(
|
|
5527
|
+
console.log(import_chalk.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
|
|
5373
5528
|
anythingChanged = true;
|
|
5374
5529
|
}
|
|
5375
5530
|
const hasPostHook = settings.hooks.PostToolUse?.some(
|
|
@@ -5381,7 +5536,7 @@ async function setupClaude() {
|
|
|
5381
5536
|
matcher: ".*",
|
|
5382
5537
|
hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
|
|
5383
5538
|
});
|
|
5384
|
-
console.log(
|
|
5539
|
+
console.log(import_chalk.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
|
|
5385
5540
|
anythingChanged = true;
|
|
5386
5541
|
}
|
|
5387
5542
|
if (anythingChanged) {
|
|
@@ -5395,35 +5550,35 @@ async function setupClaude() {
|
|
|
5395
5550
|
serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
|
|
5396
5551
|
}
|
|
5397
5552
|
if (serversToWrap.length > 0) {
|
|
5398
|
-
console.log(
|
|
5399
|
-
console.log(
|
|
5553
|
+
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
5554
|
+
console.log(import_chalk.default.white(` ${mcpPath}`));
|
|
5400
5555
|
for (const { name, originalCmd } of serversToWrap) {
|
|
5401
|
-
console.log(
|
|
5556
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
|
|
5402
5557
|
}
|
|
5403
5558
|
console.log("");
|
|
5404
|
-
const proceed = await (0,
|
|
5559
|
+
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
5405
5560
|
if (proceed) {
|
|
5406
5561
|
for (const { name, parts } of serversToWrap) {
|
|
5407
5562
|
servers[name] = { ...servers[name], command: "node9", args: parts };
|
|
5408
5563
|
}
|
|
5409
5564
|
claudeConfig.mcpServers = servers;
|
|
5410
5565
|
writeJson(mcpPath, claudeConfig);
|
|
5411
|
-
console.log(
|
|
5566
|
+
console.log(import_chalk.default.green(`
|
|
5412
5567
|
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
5413
5568
|
anythingChanged = true;
|
|
5414
5569
|
} else {
|
|
5415
|
-
console.log(
|
|
5570
|
+
console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
|
|
5416
5571
|
}
|
|
5417
5572
|
console.log("");
|
|
5418
5573
|
}
|
|
5419
5574
|
if (!anythingChanged && serversToWrap.length === 0) {
|
|
5420
|
-
console.log(
|
|
5575
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
|
|
5421
5576
|
printDaemonTip();
|
|
5422
5577
|
return;
|
|
5423
5578
|
}
|
|
5424
5579
|
if (anythingChanged) {
|
|
5425
|
-
console.log(
|
|
5426
|
-
console.log(
|
|
5580
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
|
|
5581
|
+
console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
|
|
5427
5582
|
printDaemonTip();
|
|
5428
5583
|
}
|
|
5429
5584
|
}
|
|
@@ -5451,7 +5606,7 @@ async function setupGemini() {
|
|
|
5451
5606
|
}
|
|
5452
5607
|
]
|
|
5453
5608
|
});
|
|
5454
|
-
console.log(
|
|
5609
|
+
console.log(import_chalk.default.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
|
|
5455
5610
|
anythingChanged = true;
|
|
5456
5611
|
}
|
|
5457
5612
|
const hasAfterHook = Array.isArray(settings.hooks.AfterTool) && settings.hooks.AfterTool.some(
|
|
@@ -5464,7 +5619,7 @@ async function setupGemini() {
|
|
|
5464
5619
|
matcher: ".*",
|
|
5465
5620
|
hooks: [{ name: "node9-log", type: "command", command: fullPathCommand("log") }]
|
|
5466
5621
|
});
|
|
5467
|
-
console.log(
|
|
5622
|
+
console.log(import_chalk.default.green(" \u2705 AfterTool hook added \u2192 node9 log"));
|
|
5468
5623
|
anythingChanged = true;
|
|
5469
5624
|
}
|
|
5470
5625
|
if (anythingChanged) {
|
|
@@ -5478,35 +5633,35 @@ async function setupGemini() {
|
|
|
5478
5633
|
serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
|
|
5479
5634
|
}
|
|
5480
5635
|
if (serversToWrap.length > 0) {
|
|
5481
|
-
console.log(
|
|
5482
|
-
console.log(
|
|
5636
|
+
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
5637
|
+
console.log(import_chalk.default.white(` ${settingsPath} (mcpServers)`));
|
|
5483
5638
|
for (const { name, originalCmd } of serversToWrap) {
|
|
5484
|
-
console.log(
|
|
5639
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
|
|
5485
5640
|
}
|
|
5486
5641
|
console.log("");
|
|
5487
|
-
const proceed = await (0,
|
|
5642
|
+
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
5488
5643
|
if (proceed) {
|
|
5489
5644
|
for (const { name, parts } of serversToWrap) {
|
|
5490
5645
|
servers[name] = { ...servers[name], command: "node9", args: parts };
|
|
5491
5646
|
}
|
|
5492
5647
|
settings.mcpServers = servers;
|
|
5493
5648
|
writeJson(settingsPath, settings);
|
|
5494
|
-
console.log(
|
|
5649
|
+
console.log(import_chalk.default.green(`
|
|
5495
5650
|
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
5496
5651
|
anythingChanged = true;
|
|
5497
5652
|
} else {
|
|
5498
|
-
console.log(
|
|
5653
|
+
console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
|
|
5499
5654
|
}
|
|
5500
5655
|
console.log("");
|
|
5501
5656
|
}
|
|
5502
5657
|
if (!anythingChanged && serversToWrap.length === 0) {
|
|
5503
|
-
console.log(
|
|
5658
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
|
|
5504
5659
|
printDaemonTip();
|
|
5505
5660
|
return;
|
|
5506
5661
|
}
|
|
5507
5662
|
if (anythingChanged) {
|
|
5508
|
-
console.log(
|
|
5509
|
-
console.log(
|
|
5663
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Gemini CLI!"));
|
|
5664
|
+
console.log(import_chalk.default.gray(" Restart Gemini CLI for changes to take effect."));
|
|
5510
5665
|
printDaemonTip();
|
|
5511
5666
|
}
|
|
5512
5667
|
}
|
|
@@ -5523,36 +5678,36 @@ async function setupCursor() {
|
|
|
5523
5678
|
serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
|
|
5524
5679
|
}
|
|
5525
5680
|
if (serversToWrap.length > 0) {
|
|
5526
|
-
console.log(
|
|
5527
|
-
console.log(
|
|
5681
|
+
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
5682
|
+
console.log(import_chalk.default.white(` ${mcpPath}`));
|
|
5528
5683
|
for (const { name, originalCmd } of serversToWrap) {
|
|
5529
|
-
console.log(
|
|
5684
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
|
|
5530
5685
|
}
|
|
5531
5686
|
console.log("");
|
|
5532
|
-
const proceed = await (0,
|
|
5687
|
+
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
5533
5688
|
if (proceed) {
|
|
5534
5689
|
for (const { name, parts } of serversToWrap) {
|
|
5535
5690
|
servers[name] = { ...servers[name], command: "node9", args: parts };
|
|
5536
5691
|
}
|
|
5537
5692
|
mcpConfig.mcpServers = servers;
|
|
5538
5693
|
writeJson(mcpPath, mcpConfig);
|
|
5539
|
-
console.log(
|
|
5694
|
+
console.log(import_chalk.default.green(`
|
|
5540
5695
|
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
5541
5696
|
anythingChanged = true;
|
|
5542
5697
|
} else {
|
|
5543
|
-
console.log(
|
|
5698
|
+
console.log(import_chalk.default.yellow(" Skipped MCP server wrapping."));
|
|
5544
5699
|
}
|
|
5545
5700
|
console.log("");
|
|
5546
5701
|
}
|
|
5547
5702
|
console.log(
|
|
5548
|
-
|
|
5703
|
+
import_chalk.default.yellow(
|
|
5549
5704
|
" \u26A0\uFE0F Note: Cursor does not yet support native pre-execution hooks.\n MCP proxy wrapping is the only supported protection mode for Cursor."
|
|
5550
5705
|
)
|
|
5551
5706
|
);
|
|
5552
5707
|
console.log("");
|
|
5553
5708
|
if (!anythingChanged && serversToWrap.length === 0) {
|
|
5554
5709
|
console.log(
|
|
5555
|
-
|
|
5710
|
+
import_chalk.default.blue(
|
|
5556
5711
|
"\u2139\uFE0F No MCP servers found to wrap. Add MCP servers to ~/.cursor/mcp.json and re-run."
|
|
5557
5712
|
)
|
|
5558
5713
|
);
|
|
@@ -5560,8 +5715,8 @@ async function setupCursor() {
|
|
|
5560
5715
|
return;
|
|
5561
5716
|
}
|
|
5562
5717
|
if (anythingChanged) {
|
|
5563
|
-
console.log(
|
|
5564
|
-
console.log(
|
|
5718
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Cursor via MCP proxy!"));
|
|
5719
|
+
console.log(import_chalk.default.gray(" Restart Cursor for changes to take effect."));
|
|
5565
5720
|
printDaemonTip();
|
|
5566
5721
|
}
|
|
5567
5722
|
}
|
|
@@ -5571,7 +5726,7 @@ init_daemon();
|
|
|
5571
5726
|
var import_child_process6 = require("child_process");
|
|
5572
5727
|
var import_execa = require("execa");
|
|
5573
5728
|
var import_execa2 = require("execa");
|
|
5574
|
-
var
|
|
5729
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
5575
5730
|
var import_readline2 = __toESM(require("readline"));
|
|
5576
5731
|
var import_fs8 = __toESM(require("fs"));
|
|
5577
5732
|
var import_path10 = __toESM(require("path"));
|
|
@@ -5838,7 +5993,7 @@ function applyUndo(hash, cwd) {
|
|
|
5838
5993
|
|
|
5839
5994
|
// src/cli.ts
|
|
5840
5995
|
init_shields();
|
|
5841
|
-
var
|
|
5996
|
+
var import_prompts2 = require("@inquirer/prompts");
|
|
5842
5997
|
var { version } = JSON.parse(
|
|
5843
5998
|
import_fs8.default.readFileSync(import_path10.default.join(__dirname, "../package.json"), "utf-8")
|
|
5844
5999
|
);
|
|
@@ -5939,7 +6094,9 @@ async function autoStartDaemonAndWait() {
|
|
|
5939
6094
|
const child = (0, import_child_process6.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
5940
6095
|
detached: true,
|
|
5941
6096
|
stdio: "ignore",
|
|
5942
|
-
|
|
6097
|
+
// NODE9_BROWSER_OPENED=1 tells the daemon we will open the browser ourselves
|
|
6098
|
+
// (openBrowserLocal below), so it must not open a duplicate tab on first approval.
|
|
6099
|
+
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
5943
6100
|
});
|
|
5944
6101
|
child.unref();
|
|
5945
6102
|
for (let i = 0; i < 20; i++) {
|
|
@@ -5972,7 +6129,7 @@ async function runProxy(targetCommand) {
|
|
|
5972
6129
|
if (stdout) executable = stdout.trim();
|
|
5973
6130
|
} catch {
|
|
5974
6131
|
}
|
|
5975
|
-
console.error(
|
|
6132
|
+
console.error(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
|
|
5976
6133
|
const child = (0, import_child_process6.spawn)(executable, args, {
|
|
5977
6134
|
stdio: ["pipe", "pipe", "inherit"],
|
|
5978
6135
|
// We control STDIN and STDOUT
|
|
@@ -5993,14 +6150,14 @@ async function runProxy(targetCommand) {
|
|
|
5993
6150
|
try {
|
|
5994
6151
|
const name = message.params?.name || message.params?.tool_name || "unknown";
|
|
5995
6152
|
const toolArgs = message.params?.arguments || message.params?.tool_input || {};
|
|
5996
|
-
const result = await authorizeHeadless(sanitize(name), toolArgs,
|
|
6153
|
+
const result = await authorizeHeadless(sanitize(name), toolArgs, {
|
|
5997
6154
|
agent: "Proxy/MCP"
|
|
5998
6155
|
});
|
|
5999
6156
|
if (!result.approved) {
|
|
6000
|
-
console.error(
|
|
6157
|
+
console.error(import_chalk4.default.red(`
|
|
6001
6158
|
\u{1F6D1} Node9 Sudo: Action Blocked`));
|
|
6002
|
-
console.error(
|
|
6003
|
-
console.error(
|
|
6159
|
+
console.error(import_chalk4.default.gray(` Tool: ${name}`));
|
|
6160
|
+
console.error(import_chalk4.default.gray(` Reason: ${result.reason || "Security Policy"}
|
|
6004
6161
|
`));
|
|
6005
6162
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
6006
6163
|
const isHuman = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -6087,31 +6244,31 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
6087
6244
|
import_fs8.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
6088
6245
|
}
|
|
6089
6246
|
if (options.profile && profileName !== "default") {
|
|
6090
|
-
console.log(
|
|
6091
|
-
console.log(
|
|
6247
|
+
console.log(import_chalk4.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
6248
|
+
console.log(import_chalk4.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
6092
6249
|
} else if (options.local) {
|
|
6093
|
-
console.log(
|
|
6094
|
-
console.log(
|
|
6250
|
+
console.log(import_chalk4.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
6251
|
+
console.log(import_chalk4.default.gray(` All decisions stay on this machine.`));
|
|
6095
6252
|
} else {
|
|
6096
|
-
console.log(
|
|
6097
|
-
console.log(
|
|
6253
|
+
console.log(import_chalk4.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
6254
|
+
console.log(import_chalk4.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
6098
6255
|
}
|
|
6099
6256
|
});
|
|
6100
6257
|
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) => {
|
|
6101
6258
|
if (target === "gemini") return await setupGemini();
|
|
6102
6259
|
if (target === "claude") return await setupClaude();
|
|
6103
6260
|
if (target === "cursor") return await setupCursor();
|
|
6104
|
-
console.error(
|
|
6261
|
+
console.error(import_chalk4.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
6105
6262
|
process.exit(1);
|
|
6106
6263
|
});
|
|
6107
6264
|
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) => {
|
|
6108
6265
|
if (!target) {
|
|
6109
|
-
console.log(
|
|
6110
|
-
console.log(" Usage: " +
|
|
6266
|
+
console.log(import_chalk4.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
6267
|
+
console.log(" Usage: " + import_chalk4.default.white("node9 setup <target>") + "\n");
|
|
6111
6268
|
console.log(" Targets:");
|
|
6112
|
-
console.log(" " +
|
|
6113
|
-
console.log(" " +
|
|
6114
|
-
console.log(" " +
|
|
6269
|
+
console.log(" " + import_chalk4.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
6270
|
+
console.log(" " + import_chalk4.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
6271
|
+
console.log(" " + import_chalk4.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
6115
6272
|
console.log("");
|
|
6116
6273
|
return;
|
|
6117
6274
|
}
|
|
@@ -6119,7 +6276,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
6119
6276
|
if (t === "gemini") return await setupGemini();
|
|
6120
6277
|
if (t === "claude") return await setupClaude();
|
|
6121
6278
|
if (t === "cursor") return await setupCursor();
|
|
6122
|
-
console.error(
|
|
6279
|
+
console.error(import_chalk4.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
6123
6280
|
process.exit(1);
|
|
6124
6281
|
});
|
|
6125
6282
|
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) => {
|
|
@@ -6128,30 +6285,30 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
6128
6285
|
else if (target === "gemini") fn = teardownGemini;
|
|
6129
6286
|
else if (target === "cursor") fn = teardownCursor;
|
|
6130
6287
|
else {
|
|
6131
|
-
console.error(
|
|
6288
|
+
console.error(import_chalk4.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
|
|
6132
6289
|
process.exit(1);
|
|
6133
6290
|
}
|
|
6134
|
-
console.log(
|
|
6291
|
+
console.log(import_chalk4.default.cyan(`
|
|
6135
6292
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
6136
6293
|
`));
|
|
6137
6294
|
try {
|
|
6138
6295
|
fn();
|
|
6139
6296
|
} catch (err) {
|
|
6140
|
-
console.error(
|
|
6297
|
+
console.error(import_chalk4.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
6141
6298
|
process.exit(1);
|
|
6142
6299
|
}
|
|
6143
|
-
console.log(
|
|
6300
|
+
console.log(import_chalk4.default.gray("\n Restart the agent for changes to take effect."));
|
|
6144
6301
|
});
|
|
6145
6302
|
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) => {
|
|
6146
|
-
console.log(
|
|
6147
|
-
console.log(
|
|
6303
|
+
console.log(import_chalk4.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
6304
|
+
console.log(import_chalk4.default.bold("Stopping daemon..."));
|
|
6148
6305
|
try {
|
|
6149
6306
|
stopDaemon();
|
|
6150
|
-
console.log(
|
|
6307
|
+
console.log(import_chalk4.default.green(" \u2705 Daemon stopped"));
|
|
6151
6308
|
} catch {
|
|
6152
|
-
console.log(
|
|
6309
|
+
console.log(import_chalk4.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
6153
6310
|
}
|
|
6154
|
-
console.log(
|
|
6311
|
+
console.log(import_chalk4.default.bold("\nRemoving hooks..."));
|
|
6155
6312
|
let teardownFailed = false;
|
|
6156
6313
|
for (const [label, fn] of [
|
|
6157
6314
|
["Claude", teardownClaude],
|
|
@@ -6163,7 +6320,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
6163
6320
|
} catch (err) {
|
|
6164
6321
|
teardownFailed = true;
|
|
6165
6322
|
console.error(
|
|
6166
|
-
|
|
6323
|
+
import_chalk4.default.red(
|
|
6167
6324
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
6168
6325
|
)
|
|
6169
6326
|
);
|
|
@@ -6172,7 +6329,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
6172
6329
|
if (options.purge) {
|
|
6173
6330
|
const node9Dir = import_path10.default.join(import_os7.default.homedir(), ".node9");
|
|
6174
6331
|
if (import_fs8.default.existsSync(node9Dir)) {
|
|
6175
|
-
const confirmed = await (0,
|
|
6332
|
+
const confirmed = await (0, import_prompts2.confirm)({
|
|
6176
6333
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
6177
6334
|
default: false
|
|
6178
6335
|
});
|
|
@@ -6180,48 +6337,48 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
6180
6337
|
import_fs8.default.rmSync(node9Dir, { recursive: true });
|
|
6181
6338
|
if (import_fs8.default.existsSync(node9Dir)) {
|
|
6182
6339
|
console.error(
|
|
6183
|
-
|
|
6340
|
+
import_chalk4.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
6184
6341
|
);
|
|
6185
6342
|
} else {
|
|
6186
|
-
console.log(
|
|
6343
|
+
console.log(import_chalk4.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
6187
6344
|
}
|
|
6188
6345
|
} else {
|
|
6189
|
-
console.log(
|
|
6346
|
+
console.log(import_chalk4.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
6190
6347
|
}
|
|
6191
6348
|
} else {
|
|
6192
|
-
console.log(
|
|
6349
|
+
console.log(import_chalk4.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
6193
6350
|
}
|
|
6194
6351
|
} else {
|
|
6195
6352
|
console.log(
|
|
6196
|
-
|
|
6353
|
+
import_chalk4.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
6197
6354
|
);
|
|
6198
6355
|
}
|
|
6199
6356
|
if (teardownFailed) {
|
|
6200
|
-
console.error(
|
|
6357
|
+
console.error(import_chalk4.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
6201
6358
|
process.exit(1);
|
|
6202
6359
|
}
|
|
6203
|
-
console.log(
|
|
6204
|
-
console.log(
|
|
6360
|
+
console.log(import_chalk4.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
6361
|
+
console.log(import_chalk4.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
6205
6362
|
});
|
|
6206
6363
|
program.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
6207
6364
|
const homeDir2 = import_os7.default.homedir();
|
|
6208
6365
|
let failures = 0;
|
|
6209
6366
|
function pass(msg) {
|
|
6210
|
-
console.log(
|
|
6367
|
+
console.log(import_chalk4.default.green(" \u2705 ") + msg);
|
|
6211
6368
|
}
|
|
6212
6369
|
function fail(msg, hint) {
|
|
6213
|
-
console.log(
|
|
6214
|
-
if (hint) console.log(
|
|
6370
|
+
console.log(import_chalk4.default.red(" \u274C ") + msg);
|
|
6371
|
+
if (hint) console.log(import_chalk4.default.gray(" " + hint));
|
|
6215
6372
|
failures++;
|
|
6216
6373
|
}
|
|
6217
6374
|
function warn(msg, hint) {
|
|
6218
|
-
console.log(
|
|
6219
|
-
if (hint) console.log(
|
|
6375
|
+
console.log(import_chalk4.default.yellow(" \u26A0\uFE0F ") + msg);
|
|
6376
|
+
if (hint) console.log(import_chalk4.default.gray(" " + hint));
|
|
6220
6377
|
}
|
|
6221
6378
|
function section(title) {
|
|
6222
|
-
console.log("\n" +
|
|
6379
|
+
console.log("\n" + import_chalk4.default.bold(title));
|
|
6223
6380
|
}
|
|
6224
|
-
console.log(
|
|
6381
|
+
console.log(import_chalk4.default.cyan.bold(`
|
|
6225
6382
|
\u{1F6E1}\uFE0F Node9 Doctor v${version}
|
|
6226
6383
|
`));
|
|
6227
6384
|
section("Binary");
|
|
@@ -6336,9 +6493,9 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
6336
6493
|
}
|
|
6337
6494
|
console.log("");
|
|
6338
6495
|
if (failures === 0) {
|
|
6339
|
-
console.log(
|
|
6496
|
+
console.log(import_chalk4.default.green.bold(" All checks passed. Node9 is ready.\n"));
|
|
6340
6497
|
} else {
|
|
6341
|
-
console.log(
|
|
6498
|
+
console.log(import_chalk4.default.red.bold(` ${failures} check(s) failed. See hints above.
|
|
6342
6499
|
`));
|
|
6343
6500
|
process.exit(1);
|
|
6344
6501
|
}
|
|
@@ -6353,7 +6510,7 @@ program.command("explain").description(
|
|
|
6353
6510
|
try {
|
|
6354
6511
|
args = JSON.parse(trimmed);
|
|
6355
6512
|
} catch {
|
|
6356
|
-
console.error(
|
|
6513
|
+
console.error(import_chalk4.default.red(`
|
|
6357
6514
|
\u274C Invalid JSON: ${trimmed}
|
|
6358
6515
|
`));
|
|
6359
6516
|
process.exit(1);
|
|
@@ -6364,54 +6521,54 @@ program.command("explain").description(
|
|
|
6364
6521
|
}
|
|
6365
6522
|
const result = await explainPolicy(tool, args);
|
|
6366
6523
|
console.log("");
|
|
6367
|
-
console.log(
|
|
6524
|
+
console.log(import_chalk4.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
6368
6525
|
console.log("");
|
|
6369
|
-
console.log(` ${
|
|
6526
|
+
console.log(` ${import_chalk4.default.bold("Tool:")} ${import_chalk4.default.white(result.tool)}`);
|
|
6370
6527
|
if (argsRaw) {
|
|
6371
6528
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
6372
|
-
console.log(` ${
|
|
6529
|
+
console.log(` ${import_chalk4.default.bold("Input:")} ${import_chalk4.default.gray(preview)}`);
|
|
6373
6530
|
}
|
|
6374
6531
|
console.log("");
|
|
6375
|
-
console.log(
|
|
6532
|
+
console.log(import_chalk4.default.bold("Config Sources (Waterfall):"));
|
|
6376
6533
|
for (const tier of result.waterfall) {
|
|
6377
|
-
const num =
|
|
6534
|
+
const num = import_chalk4.default.gray(` ${tier.tier}.`);
|
|
6378
6535
|
const label = tier.label.padEnd(16);
|
|
6379
6536
|
let statusStr;
|
|
6380
6537
|
if (tier.tier === 1) {
|
|
6381
|
-
statusStr =
|
|
6538
|
+
statusStr = import_chalk4.default.gray(tier.note ?? "");
|
|
6382
6539
|
} else if (tier.status === "active") {
|
|
6383
|
-
const loc = tier.path ?
|
|
6384
|
-
const note = tier.note ?
|
|
6385
|
-
statusStr =
|
|
6540
|
+
const loc = tier.path ? import_chalk4.default.gray(tier.path) : "";
|
|
6541
|
+
const note = tier.note ? import_chalk4.default.gray(`(${tier.note})`) : "";
|
|
6542
|
+
statusStr = import_chalk4.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
6386
6543
|
} else {
|
|
6387
|
-
statusStr =
|
|
6544
|
+
statusStr = import_chalk4.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
6388
6545
|
}
|
|
6389
|
-
console.log(`${num} ${
|
|
6546
|
+
console.log(`${num} ${import_chalk4.default.white(label)} ${statusStr}`);
|
|
6390
6547
|
}
|
|
6391
6548
|
console.log("");
|
|
6392
|
-
console.log(
|
|
6549
|
+
console.log(import_chalk4.default.bold("Policy Evaluation:"));
|
|
6393
6550
|
for (const step of result.steps) {
|
|
6394
6551
|
const isFinal = step.isFinal;
|
|
6395
6552
|
let icon;
|
|
6396
|
-
if (step.outcome === "allow") icon =
|
|
6397
|
-
else if (step.outcome === "review") icon =
|
|
6398
|
-
else if (step.outcome === "skip") icon =
|
|
6399
|
-
else icon =
|
|
6553
|
+
if (step.outcome === "allow") icon = import_chalk4.default.green(" \u2705");
|
|
6554
|
+
else if (step.outcome === "review") icon = import_chalk4.default.red(" \u{1F534}");
|
|
6555
|
+
else if (step.outcome === "skip") icon = import_chalk4.default.gray(" \u2500 ");
|
|
6556
|
+
else icon = import_chalk4.default.gray(" \u25CB ");
|
|
6400
6557
|
const name = step.name.padEnd(18);
|
|
6401
|
-
const nameStr = isFinal ?
|
|
6402
|
-
const detail = isFinal ?
|
|
6403
|
-
const arrow = isFinal ?
|
|
6558
|
+
const nameStr = isFinal ? import_chalk4.default.white.bold(name) : import_chalk4.default.white(name);
|
|
6559
|
+
const detail = isFinal ? import_chalk4.default.white(step.detail) : import_chalk4.default.gray(step.detail);
|
|
6560
|
+
const arrow = isFinal ? import_chalk4.default.yellow(" \u2190 STOP") : "";
|
|
6404
6561
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
6405
6562
|
}
|
|
6406
6563
|
console.log("");
|
|
6407
6564
|
if (result.decision === "allow") {
|
|
6408
|
-
console.log(
|
|
6565
|
+
console.log(import_chalk4.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk4.default.gray(" \u2014 no approval needed"));
|
|
6409
6566
|
} else {
|
|
6410
6567
|
console.log(
|
|
6411
|
-
|
|
6568
|
+
import_chalk4.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk4.default.gray(" \u2014 human approval required")
|
|
6412
6569
|
);
|
|
6413
6570
|
if (result.blockedByLabel) {
|
|
6414
|
-
console.log(
|
|
6571
|
+
console.log(import_chalk4.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
6415
6572
|
}
|
|
6416
6573
|
}
|
|
6417
6574
|
console.log("");
|
|
@@ -6419,8 +6576,8 @@ program.command("explain").description(
|
|
|
6419
6576
|
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) => {
|
|
6420
6577
|
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6421
6578
|
if (import_fs8.default.existsSync(configPath) && !options.force) {
|
|
6422
|
-
console.log(
|
|
6423
|
-
console.log(
|
|
6579
|
+
console.log(import_chalk4.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
6580
|
+
console.log(import_chalk4.default.gray(` Run with --force to overwrite.`));
|
|
6424
6581
|
return;
|
|
6425
6582
|
}
|
|
6426
6583
|
const requestedMode = options.mode.toLowerCase();
|
|
@@ -6435,10 +6592,10 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
6435
6592
|
const dir = import_path10.default.dirname(configPath);
|
|
6436
6593
|
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
6437
6594
|
import_fs8.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
6438
|
-
console.log(
|
|
6439
|
-
console.log(
|
|
6595
|
+
console.log(import_chalk4.default.green(`\u2705 Global config created: ${configPath}`));
|
|
6596
|
+
console.log(import_chalk4.default.cyan(` Mode set to: ${safeMode}`));
|
|
6440
6597
|
console.log(
|
|
6441
|
-
|
|
6598
|
+
import_chalk4.default.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
|
|
6442
6599
|
);
|
|
6443
6600
|
});
|
|
6444
6601
|
function formatRelativeTime(timestamp) {
|
|
@@ -6455,7 +6612,7 @@ program.command("audit").description("View local execution audit log").option("-
|
|
|
6455
6612
|
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "audit.log");
|
|
6456
6613
|
if (!import_fs8.default.existsSync(logPath)) {
|
|
6457
6614
|
console.log(
|
|
6458
|
-
|
|
6615
|
+
import_chalk4.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
6459
6616
|
);
|
|
6460
6617
|
return;
|
|
6461
6618
|
}
|
|
@@ -6481,31 +6638,31 @@ program.command("audit").description("View local execution audit log").option("-
|
|
|
6481
6638
|
return;
|
|
6482
6639
|
}
|
|
6483
6640
|
if (entries.length === 0) {
|
|
6484
|
-
console.log(
|
|
6641
|
+
console.log(import_chalk4.default.yellow("No matching audit entries."));
|
|
6485
6642
|
return;
|
|
6486
6643
|
}
|
|
6487
6644
|
console.log(
|
|
6488
6645
|
`
|
|
6489
|
-
${
|
|
6646
|
+
${import_chalk4.default.bold("Node9 Audit Log")} ${import_chalk4.default.dim(`(${entries.length} entries)`)}`
|
|
6490
6647
|
);
|
|
6491
|
-
console.log(
|
|
6648
|
+
console.log(import_chalk4.default.dim(" " + "\u2500".repeat(65)));
|
|
6492
6649
|
console.log(
|
|
6493
6650
|
` ${"Time".padEnd(12)} ${"Tool".padEnd(18)} ${"Result".padEnd(10)} ${"By".padEnd(15)} Agent`
|
|
6494
6651
|
);
|
|
6495
|
-
console.log(
|
|
6652
|
+
console.log(import_chalk4.default.dim(" " + "\u2500".repeat(65)));
|
|
6496
6653
|
for (const e of entries) {
|
|
6497
6654
|
const time = formatRelativeTime(String(e.ts)).padEnd(12);
|
|
6498
6655
|
const tool = String(e.tool).slice(0, 17).padEnd(18);
|
|
6499
|
-
const result = e.decision === "allow" ?
|
|
6656
|
+
const result = e.decision === "allow" ? import_chalk4.default.green("ALLOW".padEnd(10)) : import_chalk4.default.red("DENY".padEnd(10));
|
|
6500
6657
|
const checker = String(e.checkedBy || "unknown").slice(0, 14).padEnd(15);
|
|
6501
6658
|
const agent = String(e.agent || "unknown");
|
|
6502
6659
|
console.log(` ${time} ${tool} ${result} ${checker} ${agent}`);
|
|
6503
6660
|
}
|
|
6504
6661
|
const allowed = entries.filter((e) => e.decision === "allow").length;
|
|
6505
6662
|
const denied = entries.filter((e) => e.decision === "deny").length;
|
|
6506
|
-
console.log(
|
|
6663
|
+
console.log(import_chalk4.default.dim(" " + "\u2500".repeat(65)));
|
|
6507
6664
|
console.log(
|
|
6508
|
-
` ${entries.length} entries | ${
|
|
6665
|
+
` ${entries.length} entries | ${import_chalk4.default.green(allowed + " allowed")} | ${import_chalk4.default.red(denied + " denied")}
|
|
6509
6666
|
`
|
|
6510
6667
|
);
|
|
6511
6668
|
});
|
|
@@ -6516,43 +6673,43 @@ program.command("status").description("Show current Node9 mode, policy source, a
|
|
|
6516
6673
|
const settings = mergedConfig.settings;
|
|
6517
6674
|
console.log("");
|
|
6518
6675
|
if (creds && settings.approvers.cloud) {
|
|
6519
|
-
console.log(
|
|
6676
|
+
console.log(import_chalk4.default.green(" \u25CF Agent mode") + import_chalk4.default.gray(" \u2014 cloud team policy enforced"));
|
|
6520
6677
|
} else if (creds && !settings.approvers.cloud) {
|
|
6521
6678
|
console.log(
|
|
6522
|
-
|
|
6679
|
+
import_chalk4.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk4.default.gray(" \u2014 all decisions stay on this machine")
|
|
6523
6680
|
);
|
|
6524
6681
|
} else {
|
|
6525
6682
|
console.log(
|
|
6526
|
-
|
|
6683
|
+
import_chalk4.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk4.default.gray(" \u2014 no API key (Local rules only)")
|
|
6527
6684
|
);
|
|
6528
6685
|
}
|
|
6529
6686
|
console.log("");
|
|
6530
6687
|
if (daemonRunning) {
|
|
6531
6688
|
console.log(
|
|
6532
|
-
|
|
6689
|
+
import_chalk4.default.green(" \u25CF Daemon running") + import_chalk4.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
|
|
6533
6690
|
);
|
|
6534
6691
|
} else {
|
|
6535
|
-
console.log(
|
|
6692
|
+
console.log(import_chalk4.default.gray(" \u25CB Daemon stopped"));
|
|
6536
6693
|
}
|
|
6537
6694
|
if (settings.enableUndo) {
|
|
6538
6695
|
console.log(
|
|
6539
|
-
|
|
6696
|
+
import_chalk4.default.magenta(" \u25CF Undo Engine") + import_chalk4.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
|
|
6540
6697
|
);
|
|
6541
6698
|
}
|
|
6542
6699
|
console.log("");
|
|
6543
|
-
const modeLabel = settings.mode === "audit" ?
|
|
6700
|
+
const modeLabel = settings.mode === "audit" ? import_chalk4.default.blue("audit") : settings.mode === "strict" ? import_chalk4.default.red("strict") : import_chalk4.default.white("standard");
|
|
6544
6701
|
console.log(` Mode: ${modeLabel}`);
|
|
6545
6702
|
const projectConfig = import_path10.default.join(process.cwd(), "node9.config.json");
|
|
6546
6703
|
const globalConfig = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6547
6704
|
console.log(
|
|
6548
|
-
` Local: ${import_fs8.default.existsSync(projectConfig) ?
|
|
6705
|
+
` Local: ${import_fs8.default.existsSync(projectConfig) ? import_chalk4.default.green("Active (node9.config.json)") : import_chalk4.default.gray("Not present")}`
|
|
6549
6706
|
);
|
|
6550
6707
|
console.log(
|
|
6551
|
-
` Global: ${import_fs8.default.existsSync(globalConfig) ?
|
|
6708
|
+
` Global: ${import_fs8.default.existsSync(globalConfig) ? import_chalk4.default.green("Active (~/.node9/config.json)") : import_chalk4.default.gray("Not present")}`
|
|
6552
6709
|
);
|
|
6553
6710
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
6554
6711
|
console.log(
|
|
6555
|
-
` Sandbox: ${
|
|
6712
|
+
` Sandbox: ${import_chalk4.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
6556
6713
|
);
|
|
6557
6714
|
}
|
|
6558
6715
|
const pauseState = checkPause();
|
|
@@ -6560,7 +6717,7 @@ program.command("status").description("Show current Node9 mode, policy source, a
|
|
|
6560
6717
|
const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
|
|
6561
6718
|
console.log("");
|
|
6562
6719
|
console.log(
|
|
6563
|
-
|
|
6720
|
+
import_chalk4.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk4.default.gray(" \u2014 all tool calls allowed")
|
|
6564
6721
|
);
|
|
6565
6722
|
}
|
|
6566
6723
|
console.log("");
|
|
@@ -6574,14 +6731,14 @@ program.command("daemon").description("Run the local approval server").argument(
|
|
|
6574
6731
|
if (cmd === "stop") return stopDaemon();
|
|
6575
6732
|
if (cmd === "status") return daemonStatus();
|
|
6576
6733
|
if (cmd !== "start" && action !== void 0) {
|
|
6577
|
-
console.error(
|
|
6734
|
+
console.error(import_chalk4.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
|
|
6578
6735
|
process.exit(1);
|
|
6579
6736
|
}
|
|
6580
6737
|
if (options.watch) {
|
|
6581
6738
|
process.env.NODE9_WATCH_MODE = "1";
|
|
6582
6739
|
setTimeout(() => {
|
|
6583
6740
|
openBrowserLocal();
|
|
6584
|
-
console.log(
|
|
6741
|
+
console.log(import_chalk4.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
|
|
6585
6742
|
}, 600);
|
|
6586
6743
|
startDaemon();
|
|
6587
6744
|
return;
|
|
@@ -6589,7 +6746,7 @@ program.command("daemon").description("Run the local approval server").argument(
|
|
|
6589
6746
|
if (options.openui) {
|
|
6590
6747
|
if (isDaemonRunning()) {
|
|
6591
6748
|
openBrowserLocal();
|
|
6592
|
-
console.log(
|
|
6749
|
+
console.log(import_chalk4.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
|
|
6593
6750
|
process.exit(0);
|
|
6594
6751
|
}
|
|
6595
6752
|
const child = (0, import_child_process6.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
@@ -6602,7 +6759,7 @@ program.command("daemon").description("Run the local approval server").argument(
|
|
|
6602
6759
|
if (isDaemonRunning()) break;
|
|
6603
6760
|
}
|
|
6604
6761
|
openBrowserLocal();
|
|
6605
|
-
console.log(
|
|
6762
|
+
console.log(import_chalk4.default.green(`
|
|
6606
6763
|
\u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
|
|
6607
6764
|
process.exit(0);
|
|
6608
6765
|
}
|
|
@@ -6612,7 +6769,7 @@ program.command("daemon").description("Run the local approval server").argument(
|
|
|
6612
6769
|
stdio: "ignore"
|
|
6613
6770
|
});
|
|
6614
6771
|
child.unref();
|
|
6615
|
-
console.log(
|
|
6772
|
+
console.log(import_chalk4.default.green(`
|
|
6616
6773
|
\u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
|
|
6617
6774
|
process.exit(0);
|
|
6618
6775
|
}
|
|
@@ -6624,10 +6781,64 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
6624
6781
|
try {
|
|
6625
6782
|
await startTail2(options);
|
|
6626
6783
|
} catch (err) {
|
|
6627
|
-
console.error(
|
|
6784
|
+
console.error(import_chalk4.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
6628
6785
|
process.exit(1);
|
|
6629
6786
|
}
|
|
6630
6787
|
});
|
|
6788
|
+
program.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
|
|
6789
|
+
let port = DAEMON_PORT2;
|
|
6790
|
+
try {
|
|
6791
|
+
const res = await fetch(`http://127.0.0.1:${DAEMON_PORT2}/settings`, {
|
|
6792
|
+
signal: AbortSignal.timeout(500)
|
|
6793
|
+
});
|
|
6794
|
+
if (res.ok) {
|
|
6795
|
+
const data = await res.json();
|
|
6796
|
+
if (typeof data.port === "number") port = data.port;
|
|
6797
|
+
} else {
|
|
6798
|
+
throw new Error("not running");
|
|
6799
|
+
}
|
|
6800
|
+
} catch {
|
|
6801
|
+
console.error(import_chalk4.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
6802
|
+
const child = (0, import_child_process6.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6803
|
+
detached: true,
|
|
6804
|
+
stdio: "ignore",
|
|
6805
|
+
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
6806
|
+
});
|
|
6807
|
+
child.unref();
|
|
6808
|
+
let ready = false;
|
|
6809
|
+
for (let i = 0; i < 20; i++) {
|
|
6810
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
6811
|
+
try {
|
|
6812
|
+
const r = await fetch(`http://127.0.0.1:${DAEMON_PORT2}/settings`, {
|
|
6813
|
+
signal: AbortSignal.timeout(500)
|
|
6814
|
+
});
|
|
6815
|
+
if (r.ok) {
|
|
6816
|
+
ready = true;
|
|
6817
|
+
break;
|
|
6818
|
+
}
|
|
6819
|
+
} catch {
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
if (!ready) {
|
|
6823
|
+
console.error(import_chalk4.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6824
|
+
process.exit(1);
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
console.error(
|
|
6828
|
+
import_chalk4.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk4.default.dim(` \u2192 localhost:${port}`) + import_chalk4.default.dim(
|
|
6829
|
+
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
6830
|
+
)
|
|
6831
|
+
);
|
|
6832
|
+
const result = (0, import_child_process6.spawnSync)(cmd, args, {
|
|
6833
|
+
stdio: "inherit",
|
|
6834
|
+
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
6835
|
+
});
|
|
6836
|
+
if (result.error) {
|
|
6837
|
+
console.error(import_chalk4.default.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
6838
|
+
process.exit(1);
|
|
6839
|
+
}
|
|
6840
|
+
process.exit(result.status ?? 0);
|
|
6841
|
+
});
|
|
6631
6842
|
program.command("check").description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
6632
6843
|
const processPayload = async (raw) => {
|
|
6633
6844
|
try {
|
|
@@ -6665,19 +6876,30 @@ RAW: ${raw}
|
|
|
6665
6876
|
const sendBlock = (msg, result2) => {
|
|
6666
6877
|
const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || "Local Security Policy";
|
|
6667
6878
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
6668
|
-
|
|
6669
|
-
|
|
6879
|
+
let ttyFd = null;
|
|
6880
|
+
try {
|
|
6881
|
+
ttyFd = import_fs8.default.openSync("/dev/tty", "w");
|
|
6882
|
+
const writeTty = (line) => import_fs8.default.writeSync(ttyFd, line + "\n");
|
|
6883
|
+
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
6884
|
+
writeTty(import_chalk4.default.bgRed.white.bold(`
|
|
6670
6885
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6886
|
+
writeTty(import_chalk4.default.red.bold(` A sensitive secret was found in the tool arguments!`));
|
|
6887
|
+
} else {
|
|
6888
|
+
writeTty(import_chalk4.default.red(`
|
|
6674
6889
|
\u{1F6D1} Node9 blocked "${toolName}"`));
|
|
6890
|
+
}
|
|
6891
|
+
writeTty(import_chalk4.default.gray(` Triggered by: ${blockedByContext}`));
|
|
6892
|
+
if (result2?.changeHint) writeTty(import_chalk4.default.cyan(` To change: ${result2.changeHint}`));
|
|
6893
|
+
writeTty("");
|
|
6894
|
+
} catch {
|
|
6895
|
+
} finally {
|
|
6896
|
+
if (ttyFd !== null)
|
|
6897
|
+
try {
|
|
6898
|
+
import_fs8.default.closeSync(ttyFd);
|
|
6899
|
+
} catch {
|
|
6900
|
+
}
|
|
6675
6901
|
}
|
|
6676
|
-
console.error(import_chalk6.default.gray(` Triggered by: ${blockedByContext}`));
|
|
6677
|
-
if (result2?.changeHint) console.error(import_chalk6.default.cyan(` To change: ${result2.changeHint}`));
|
|
6678
|
-
console.error("");
|
|
6679
6902
|
const aiFeedbackMessage = buildNegotiationMessage(blockedByContext, isHumanDecision, msg);
|
|
6680
|
-
console.error(import_chalk6.default.dim(` (Detailed instructions sent to AI agent)`));
|
|
6681
6903
|
process.stdout.write(
|
|
6682
6904
|
JSON.stringify({
|
|
6683
6905
|
decision: "block",
|
|
@@ -6700,7 +6922,10 @@ RAW: ${raw}
|
|
|
6700
6922
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6701
6923
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6702
6924
|
}
|
|
6703
|
-
const
|
|
6925
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path10.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6926
|
+
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
6927
|
+
cwd: safeCwdForAuth
|
|
6928
|
+
});
|
|
6704
6929
|
if (result.approved) {
|
|
6705
6930
|
if (result.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
6706
6931
|
process.stderr.write(`\u2713 node9 [${result.checkedBy}]: "${toolName}" allowed
|
|
@@ -6708,10 +6933,20 @@ RAW: ${raw}
|
|
|
6708
6933
|
process.exit(0);
|
|
6709
6934
|
}
|
|
6710
6935
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
6711
|
-
|
|
6936
|
+
try {
|
|
6937
|
+
const tty = import_fs8.default.openSync("/dev/tty", "w");
|
|
6938
|
+
import_fs8.default.writeSync(
|
|
6939
|
+
tty,
|
|
6940
|
+
import_chalk4.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
6941
|
+
);
|
|
6942
|
+
import_fs8.default.closeSync(tty);
|
|
6943
|
+
} catch {
|
|
6944
|
+
}
|
|
6712
6945
|
const daemonReady = await autoStartDaemonAndWait();
|
|
6713
6946
|
if (daemonReady) {
|
|
6714
|
-
const retry = await authorizeHeadless(toolName, toolInput,
|
|
6947
|
+
const retry = await authorizeHeadless(toolName, toolInput, meta, {
|
|
6948
|
+
cwd: safeCwdForAuth
|
|
6949
|
+
});
|
|
6715
6950
|
if (retry.approved) {
|
|
6716
6951
|
if (retry.checkedBy && process.env.NODE9_DEBUG === "1")
|
|
6717
6952
|
process.stderr.write(`\u2713 node9 [${retry.checkedBy}]: "${toolName}" allowed
|
|
@@ -6818,7 +7053,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
6818
7053
|
const ms = parseDuration(options.duration);
|
|
6819
7054
|
if (ms === null) {
|
|
6820
7055
|
console.error(
|
|
6821
|
-
|
|
7056
|
+
import_chalk4.default.red(`
|
|
6822
7057
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
6823
7058
|
`)
|
|
6824
7059
|
);
|
|
@@ -6826,20 +7061,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
6826
7061
|
}
|
|
6827
7062
|
pauseNode9(ms, options.duration);
|
|
6828
7063
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
6829
|
-
console.log(
|
|
7064
|
+
console.log(import_chalk4.default.yellow(`
|
|
6830
7065
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
6831
|
-
console.log(
|
|
6832
|
-
console.log(
|
|
7066
|
+
console.log(import_chalk4.default.gray(` All tool calls will be allowed without review.`));
|
|
7067
|
+
console.log(import_chalk4.default.gray(` Run "node9 resume" to re-enable early.
|
|
6833
7068
|
`));
|
|
6834
7069
|
});
|
|
6835
7070
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
6836
7071
|
const { paused } = checkPause();
|
|
6837
7072
|
if (!paused) {
|
|
6838
|
-
console.log(
|
|
7073
|
+
console.log(import_chalk4.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
6839
7074
|
return;
|
|
6840
7075
|
}
|
|
6841
7076
|
resumeNode9();
|
|
6842
|
-
console.log(
|
|
7077
|
+
console.log(import_chalk4.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
6843
7078
|
});
|
|
6844
7079
|
var HOOK_BASED_AGENTS = {
|
|
6845
7080
|
claude: "claude",
|
|
@@ -6852,37 +7087,50 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
6852
7087
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
6853
7088
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
6854
7089
|
console.error(
|
|
6855
|
-
|
|
7090
|
+
import_chalk4.default.yellow(`
|
|
6856
7091
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
6857
7092
|
);
|
|
6858
|
-
console.error(
|
|
7093
|
+
console.error(import_chalk4.default.white(`
|
|
6859
7094
|
"${target}" uses its own hook system. Use:`));
|
|
6860
7095
|
console.error(
|
|
6861
|
-
|
|
7096
|
+
import_chalk4.default.green(` node9 addto ${target} `) + import_chalk4.default.gray("# one-time setup")
|
|
6862
7097
|
);
|
|
6863
|
-
console.error(
|
|
7098
|
+
console.error(import_chalk4.default.green(` ${target} `) + import_chalk4.default.gray("# run normally"));
|
|
6864
7099
|
process.exit(1);
|
|
6865
7100
|
}
|
|
6866
|
-
const
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
7101
|
+
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
7102
|
+
if (runArgs.length === 0) {
|
|
7103
|
+
program.help();
|
|
7104
|
+
return;
|
|
7105
|
+
}
|
|
7106
|
+
const fullCommand = runArgs.join(" ");
|
|
7107
|
+
let result = await authorizeHeadless(
|
|
7108
|
+
"shell",
|
|
7109
|
+
{ command: fullCommand },
|
|
7110
|
+
{
|
|
7111
|
+
agent: "Terminal"
|
|
7112
|
+
}
|
|
7113
|
+
);
|
|
6870
7114
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
6871
|
-
console.error(
|
|
7115
|
+
console.error(import_chalk4.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
6872
7116
|
const daemonReady = await autoStartDaemonAndWait();
|
|
6873
7117
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
6874
7118
|
}
|
|
6875
7119
|
if (result.noApprovalMechanism && process.stdout.isTTY) {
|
|
6876
|
-
|
|
7120
|
+
const approved = await (0, import_prompts2.confirm)({
|
|
7121
|
+
message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
|
|
7122
|
+
default: false
|
|
7123
|
+
});
|
|
7124
|
+
result = { approved, reason: approved ? void 0 : "Denied by user at terminal." };
|
|
6877
7125
|
}
|
|
6878
7126
|
if (!result.approved) {
|
|
6879
7127
|
console.error(
|
|
6880
|
-
|
|
7128
|
+
import_chalk4.default.red(`
|
|
6881
7129
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
6882
7130
|
);
|
|
6883
7131
|
process.exit(1);
|
|
6884
7132
|
}
|
|
6885
|
-
console.error(
|
|
7133
|
+
console.error(import_chalk4.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
6886
7134
|
await runProxy(fullCommand);
|
|
6887
7135
|
} else {
|
|
6888
7136
|
program.help();
|
|
@@ -6897,22 +7145,22 @@ program.command("undo").description(
|
|
|
6897
7145
|
if (history.length === 0) {
|
|
6898
7146
|
if (!options.all && allHistory.length > 0) {
|
|
6899
7147
|
console.log(
|
|
6900
|
-
|
|
7148
|
+
import_chalk4.default.yellow(
|
|
6901
7149
|
`
|
|
6902
7150
|
\u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
|
|
6903
|
-
Run ${
|
|
7151
|
+
Run ${import_chalk4.default.cyan("node9 undo --all")} to see snapshots from all projects.
|
|
6904
7152
|
`
|
|
6905
7153
|
)
|
|
6906
7154
|
);
|
|
6907
7155
|
} else {
|
|
6908
|
-
console.log(
|
|
7156
|
+
console.log(import_chalk4.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
|
|
6909
7157
|
}
|
|
6910
7158
|
return;
|
|
6911
7159
|
}
|
|
6912
7160
|
const idx = history.length - steps;
|
|
6913
7161
|
if (idx < 0) {
|
|
6914
7162
|
console.log(
|
|
6915
|
-
|
|
7163
|
+
import_chalk4.default.yellow(
|
|
6916
7164
|
`
|
|
6917
7165
|
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
6918
7166
|
`
|
|
@@ -6923,18 +7171,18 @@ program.command("undo").description(
|
|
|
6923
7171
|
const snapshot = history[idx];
|
|
6924
7172
|
const age = Math.round((Date.now() - snapshot.timestamp) / 1e3);
|
|
6925
7173
|
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.round(age / 60)}m ago` : `${Math.round(age / 3600)}h ago`;
|
|
6926
|
-
console.log(
|
|
7174
|
+
console.log(import_chalk4.default.magenta.bold(`
|
|
6927
7175
|
\u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`));
|
|
6928
7176
|
console.log(
|
|
6929
|
-
|
|
6930
|
-
` Tool: ${
|
|
7177
|
+
import_chalk4.default.white(
|
|
7178
|
+
` Tool: ${import_chalk4.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk4.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
6931
7179
|
)
|
|
6932
7180
|
);
|
|
6933
|
-
console.log(
|
|
6934
|
-
console.log(
|
|
7181
|
+
console.log(import_chalk4.default.white(` When: ${import_chalk4.default.gray(ageStr)}`));
|
|
7182
|
+
console.log(import_chalk4.default.white(` Dir: ${import_chalk4.default.gray(snapshot.cwd)}`));
|
|
6935
7183
|
if (steps > 1)
|
|
6936
7184
|
console.log(
|
|
6937
|
-
|
|
7185
|
+
import_chalk4.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
|
|
6938
7186
|
);
|
|
6939
7187
|
console.log("");
|
|
6940
7188
|
const diff = computeUndoDiff(snapshot.hash, snapshot.cwd);
|
|
@@ -6942,65 +7190,65 @@ program.command("undo").description(
|
|
|
6942
7190
|
const lines = diff.split("\n");
|
|
6943
7191
|
for (const line of lines) {
|
|
6944
7192
|
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
6945
|
-
console.log(
|
|
7193
|
+
console.log(import_chalk4.default.bold(line));
|
|
6946
7194
|
} else if (line.startsWith("+")) {
|
|
6947
|
-
console.log(
|
|
7195
|
+
console.log(import_chalk4.default.green(line));
|
|
6948
7196
|
} else if (line.startsWith("-")) {
|
|
6949
|
-
console.log(
|
|
7197
|
+
console.log(import_chalk4.default.red(line));
|
|
6950
7198
|
} else if (line.startsWith("@@")) {
|
|
6951
|
-
console.log(
|
|
7199
|
+
console.log(import_chalk4.default.cyan(line));
|
|
6952
7200
|
} else {
|
|
6953
|
-
console.log(
|
|
7201
|
+
console.log(import_chalk4.default.gray(line));
|
|
6954
7202
|
}
|
|
6955
7203
|
}
|
|
6956
7204
|
console.log("");
|
|
6957
7205
|
} else {
|
|
6958
7206
|
console.log(
|
|
6959
|
-
|
|
7207
|
+
import_chalk4.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
|
|
6960
7208
|
);
|
|
6961
7209
|
}
|
|
6962
|
-
const proceed = await (0,
|
|
7210
|
+
const proceed = await (0, import_prompts2.confirm)({
|
|
6963
7211
|
message: `Revert to this snapshot?`,
|
|
6964
7212
|
default: false
|
|
6965
7213
|
});
|
|
6966
7214
|
if (proceed) {
|
|
6967
7215
|
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
6968
|
-
console.log(
|
|
7216
|
+
console.log(import_chalk4.default.green("\n\u2705 Reverted successfully.\n"));
|
|
6969
7217
|
} else {
|
|
6970
|
-
console.error(
|
|
7218
|
+
console.error(import_chalk4.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
6971
7219
|
}
|
|
6972
7220
|
} else {
|
|
6973
|
-
console.log(
|
|
7221
|
+
console.log(import_chalk4.default.gray("\nCancelled.\n"));
|
|
6974
7222
|
}
|
|
6975
7223
|
});
|
|
6976
7224
|
var shieldCmd = program.command("shield").description("Manage pre-packaged security shield templates");
|
|
6977
7225
|
shieldCmd.command("enable <service>").description("Enable a security shield for a specific service").action((service) => {
|
|
6978
7226
|
const name = resolveShieldName(service);
|
|
6979
7227
|
if (!name) {
|
|
6980
|
-
console.error(
|
|
7228
|
+
console.error(import_chalk4.default.red(`
|
|
6981
7229
|
\u274C Unknown shield: "${service}"
|
|
6982
7230
|
`));
|
|
6983
|
-
console.log(`Run ${
|
|
7231
|
+
console.log(`Run ${import_chalk4.default.cyan("node9 shield list")} to see available shields.
|
|
6984
7232
|
`);
|
|
6985
7233
|
process.exit(1);
|
|
6986
7234
|
}
|
|
6987
7235
|
const shield = getShield(name);
|
|
6988
7236
|
const active = readActiveShields();
|
|
6989
7237
|
if (active.includes(name)) {
|
|
6990
|
-
console.log(
|
|
7238
|
+
console.log(import_chalk4.default.yellow(`
|
|
6991
7239
|
\u2139\uFE0F Shield "${name}" is already active.
|
|
6992
7240
|
`));
|
|
6993
7241
|
return;
|
|
6994
7242
|
}
|
|
6995
7243
|
writeActiveShields([...active, name]);
|
|
6996
|
-
console.log(
|
|
7244
|
+
console.log(import_chalk4.default.green(`
|
|
6997
7245
|
\u{1F6E1}\uFE0F Shield "${name}" enabled.`));
|
|
6998
|
-
console.log(
|
|
7246
|
+
console.log(import_chalk4.default.gray(` ${shield.smartRules.length} smart rules now active.`));
|
|
6999
7247
|
if (shield.dangerousWords.length > 0)
|
|
7000
|
-
console.log(
|
|
7248
|
+
console.log(import_chalk4.default.gray(` ${shield.dangerousWords.length} dangerous words now active.`));
|
|
7001
7249
|
if (name === "filesystem") {
|
|
7002
7250
|
console.log(
|
|
7003
|
-
|
|
7251
|
+
import_chalk4.default.yellow(
|
|
7004
7252
|
`
|
|
7005
7253
|
\u26A0\uFE0F Note: filesystem rules cover common rm -rf patterns but not all variants.
|
|
7006
7254
|
Tools like unlink, find -delete, or language-level file ops are not intercepted.`
|
|
@@ -7012,70 +7260,70 @@ shieldCmd.command("enable <service>").description("Enable a security shield for
|
|
|
7012
7260
|
shieldCmd.command("disable <service>").description("Disable a security shield").action((service) => {
|
|
7013
7261
|
const name = resolveShieldName(service);
|
|
7014
7262
|
if (!name) {
|
|
7015
|
-
console.error(
|
|
7263
|
+
console.error(import_chalk4.default.red(`
|
|
7016
7264
|
\u274C Unknown shield: "${service}"
|
|
7017
7265
|
`));
|
|
7018
|
-
console.log(`Run ${
|
|
7266
|
+
console.log(`Run ${import_chalk4.default.cyan("node9 shield list")} to see available shields.
|
|
7019
7267
|
`);
|
|
7020
7268
|
process.exit(1);
|
|
7021
7269
|
}
|
|
7022
7270
|
const active = readActiveShields();
|
|
7023
7271
|
if (!active.includes(name)) {
|
|
7024
|
-
console.log(
|
|
7272
|
+
console.log(import_chalk4.default.yellow(`
|
|
7025
7273
|
\u2139\uFE0F Shield "${name}" is not active.
|
|
7026
7274
|
`));
|
|
7027
7275
|
return;
|
|
7028
7276
|
}
|
|
7029
7277
|
writeActiveShields(active.filter((s) => s !== name));
|
|
7030
|
-
console.log(
|
|
7278
|
+
console.log(import_chalk4.default.green(`
|
|
7031
7279
|
\u{1F6E1}\uFE0F Shield "${name}" disabled.
|
|
7032
7280
|
`));
|
|
7033
7281
|
});
|
|
7034
7282
|
shieldCmd.command("list").description("Show all available shields").action(() => {
|
|
7035
7283
|
const active = new Set(readActiveShields());
|
|
7036
|
-
console.log(
|
|
7284
|
+
console.log(import_chalk4.default.bold("\n\u{1F6E1}\uFE0F Available Shields\n"));
|
|
7037
7285
|
for (const shield of listShields()) {
|
|
7038
|
-
const status = active.has(shield.name) ?
|
|
7039
|
-
console.log(` ${status} ${
|
|
7286
|
+
const status = active.has(shield.name) ? import_chalk4.default.green("\u25CF enabled") : import_chalk4.default.gray("\u25CB disabled");
|
|
7287
|
+
console.log(` ${status} ${import_chalk4.default.cyan(shield.name.padEnd(12))} ${shield.description}`);
|
|
7040
7288
|
if (shield.aliases.length > 0)
|
|
7041
|
-
console.log(
|
|
7289
|
+
console.log(import_chalk4.default.gray(` aliases: ${shield.aliases.join(", ")}`));
|
|
7042
7290
|
}
|
|
7043
7291
|
console.log("");
|
|
7044
7292
|
});
|
|
7045
7293
|
shieldCmd.command("status").description("Show active shields and their individual rules with verdicts").action(() => {
|
|
7046
7294
|
const active = readActiveShields();
|
|
7047
7295
|
if (active.length === 0) {
|
|
7048
|
-
console.error(
|
|
7049
|
-
console.error(`Run ${
|
|
7296
|
+
console.error(import_chalk4.default.yellow("\n\u2139\uFE0F No shields are active.\n"));
|
|
7297
|
+
console.error(`Run ${import_chalk4.default.cyan("node9 shield list")} to see available shields.
|
|
7050
7298
|
`);
|
|
7051
7299
|
return;
|
|
7052
7300
|
}
|
|
7053
7301
|
const overrides = readShieldOverrides();
|
|
7054
|
-
console.error(
|
|
7302
|
+
console.error(import_chalk4.default.bold("\n\u{1F6E1}\uFE0F Active Shields\n"));
|
|
7055
7303
|
for (const name of active) {
|
|
7056
7304
|
const shield = getShield(name);
|
|
7057
7305
|
if (!shield) continue;
|
|
7058
|
-
console.error(` ${
|
|
7306
|
+
console.error(` ${import_chalk4.default.green("\u25CF")} ${import_chalk4.default.cyan(name)} \u2014 ${shield.description}`);
|
|
7059
7307
|
const ruleOverrides = overrides[name] ?? {};
|
|
7060
7308
|
for (const rule of shield.smartRules) {
|
|
7061
7309
|
const shortName = rule.name ? rule.name.replace(`shield:${name}:`, "") : "(unnamed)";
|
|
7062
7310
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
7063
7311
|
const effectiveVerdict = overrideVerdict ?? rule.verdict;
|
|
7064
|
-
const verdictLabel = effectiveVerdict === "block" ?
|
|
7065
|
-
const overrideNote = overrideVerdict ?
|
|
7312
|
+
const verdictLabel = effectiveVerdict === "block" ? import_chalk4.default.red("block ") : effectiveVerdict === "review" ? import_chalk4.default.yellow("review") : import_chalk4.default.green("allow ");
|
|
7313
|
+
const overrideNote = overrideVerdict ? import_chalk4.default.gray(` \u2190 overridden (was: ${rule.verdict})`) : "";
|
|
7066
7314
|
console.error(
|
|
7067
|
-
` ${verdictLabel} ${shortName.padEnd(24)} ${
|
|
7315
|
+
` ${verdictLabel} ${shortName.padEnd(24)} ${import_chalk4.default.gray(rule.reason ?? "")}${overrideNote}`
|
|
7068
7316
|
);
|
|
7069
7317
|
}
|
|
7070
7318
|
if (shield.dangerousWords.length > 0) {
|
|
7071
|
-
console.error(
|
|
7319
|
+
console.error(import_chalk4.default.gray(` words: ${shield.dangerousWords.join(", ")}`));
|
|
7072
7320
|
}
|
|
7073
7321
|
console.error("");
|
|
7074
7322
|
}
|
|
7075
7323
|
if (Object.keys(overrides).length > 0) {
|
|
7076
7324
|
console.error(
|
|
7077
|
-
|
|
7078
|
-
` Tip: run ${
|
|
7325
|
+
import_chalk4.default.gray(
|
|
7326
|
+
` Tip: run ${import_chalk4.default.cyan("node9 shield unset <shield> <rule>")} to remove an override.
|
|
7079
7327
|
`
|
|
7080
7328
|
)
|
|
7081
7329
|
);
|
|
@@ -7084,35 +7332,35 @@ shieldCmd.command("status").description("Show active shields and their individua
|
|
|
7084
7332
|
shieldCmd.command("set <service> <rule> <verdict>").description("Override the verdict for a specific shield rule (block, review, or allow)").option("--force", "Required when setting verdict to allow (silences a block rule)").action((service, rule, verdict, opts) => {
|
|
7085
7333
|
const name = resolveShieldName(service);
|
|
7086
7334
|
if (!name) {
|
|
7087
|
-
console.error(
|
|
7335
|
+
console.error(import_chalk4.default.red(`
|
|
7088
7336
|
\u274C Unknown shield: "${service}"
|
|
7089
7337
|
`));
|
|
7090
|
-
console.error(`Run ${
|
|
7338
|
+
console.error(`Run ${import_chalk4.default.cyan("node9 shield list")} to see available shields.
|
|
7091
7339
|
`);
|
|
7092
7340
|
process.exit(1);
|
|
7093
7341
|
}
|
|
7094
7342
|
if (!readActiveShields().includes(name)) {
|
|
7095
|
-
console.error(
|
|
7343
|
+
console.error(import_chalk4.default.red(`
|
|
7096
7344
|
\u274C Shield "${name}" is not active. Enable it first:
|
|
7097
7345
|
`));
|
|
7098
|
-
console.error(` ${
|
|
7346
|
+
console.error(` ${import_chalk4.default.cyan(`node9 shield enable ${name}`)}
|
|
7099
7347
|
`);
|
|
7100
7348
|
process.exit(1);
|
|
7101
7349
|
}
|
|
7102
7350
|
if (!isShieldVerdict(verdict)) {
|
|
7103
|
-
console.error(
|
|
7351
|
+
console.error(import_chalk4.default.red(`
|
|
7104
7352
|
\u274C Invalid verdict "${verdict}". Use: block, review, or allow
|
|
7105
7353
|
`));
|
|
7106
7354
|
process.exit(1);
|
|
7107
7355
|
}
|
|
7108
7356
|
if (verdict === "allow" && !opts.force) {
|
|
7109
7357
|
console.error(
|
|
7110
|
-
|
|
7358
|
+
import_chalk4.default.red(`
|
|
7111
7359
|
\u26A0\uFE0F Setting a verdict to "allow" silences the rule entirely.
|
|
7112
|
-
`) +
|
|
7360
|
+
`) + import_chalk4.default.yellow(
|
|
7113
7361
|
` This disables a shield protection. If you are sure, re-run with --force:
|
|
7114
7362
|
`
|
|
7115
|
-
) +
|
|
7363
|
+
) + import_chalk4.default.cyan(`
|
|
7116
7364
|
node9 shield set ${service} ${rule} allow --force
|
|
7117
7365
|
`)
|
|
7118
7366
|
);
|
|
@@ -7121,13 +7369,13 @@ shieldCmd.command("set <service> <rule> <verdict>").description("Override the ve
|
|
|
7121
7369
|
const ruleName = resolveShieldRule(name, rule);
|
|
7122
7370
|
if (!ruleName) {
|
|
7123
7371
|
const shield = getShield(name);
|
|
7124
|
-
console.error(
|
|
7372
|
+
console.error(import_chalk4.default.red(`
|
|
7125
7373
|
\u274C Unknown rule "${rule}" for shield "${name}".
|
|
7126
7374
|
`));
|
|
7127
7375
|
console.error(" Available rules:");
|
|
7128
7376
|
for (const r of shield?.smartRules ?? []) {
|
|
7129
7377
|
const short = r.name ? r.name.replace(`shield:${name}:`, "") : "";
|
|
7130
|
-
console.error(` ${
|
|
7378
|
+
console.error(` ${import_chalk4.default.cyan(short)}`);
|
|
7131
7379
|
}
|
|
7132
7380
|
console.error("");
|
|
7133
7381
|
process.exit(1);
|
|
@@ -7137,33 +7385,33 @@ shieldCmd.command("set <service> <rule> <verdict>").description("Override the ve
|
|
|
7137
7385
|
appendConfigAudit({ event: "shield-override-allow", shield: name, rule: ruleName });
|
|
7138
7386
|
}
|
|
7139
7387
|
const shortName = ruleName.replace(`shield:${name}:`, "");
|
|
7140
|
-
const verdictLabel = verdict === "block" ?
|
|
7388
|
+
const verdictLabel = verdict === "block" ? import_chalk4.default.red("block") : verdict === "review" ? import_chalk4.default.yellow("review") : import_chalk4.default.green("allow");
|
|
7141
7389
|
if (verdict === "allow") {
|
|
7142
7390
|
console.error(
|
|
7143
|
-
|
|
7144
|
-
\u26A0\uFE0F ${name}/${shortName} \u2192 ${verdictLabel}`) +
|
|
7391
|
+
import_chalk4.default.yellow(`
|
|
7392
|
+
\u26A0\uFE0F ${name}/${shortName} \u2192 ${verdictLabel}`) + import_chalk4.default.gray(" (rule silenced \u2014 use `node9 shield unset` to restore)\n")
|
|
7145
7393
|
);
|
|
7146
7394
|
} else {
|
|
7147
|
-
console.error(
|
|
7395
|
+
console.error(import_chalk4.default.green(`
|
|
7148
7396
|
\u2705 ${name}/${shortName} \u2192 ${verdictLabel}
|
|
7149
7397
|
`));
|
|
7150
7398
|
}
|
|
7151
7399
|
console.error(
|
|
7152
|
-
|
|
7400
|
+
import_chalk4.default.gray(` Run ${import_chalk4.default.cyan("node9 shield status")} to see all active rules.
|
|
7153
7401
|
`)
|
|
7154
7402
|
);
|
|
7155
7403
|
});
|
|
7156
7404
|
shieldCmd.command("unset <service> <rule>").description("Remove a verdict override, restoring the shield default").action((service, rule) => {
|
|
7157
7405
|
const name = resolveShieldName(service);
|
|
7158
7406
|
if (!name) {
|
|
7159
|
-
console.error(
|
|
7407
|
+
console.error(import_chalk4.default.red(`
|
|
7160
7408
|
\u274C Unknown shield: "${service}"
|
|
7161
7409
|
`));
|
|
7162
7410
|
process.exit(1);
|
|
7163
7411
|
}
|
|
7164
7412
|
const ruleName = resolveShieldRule(name, rule);
|
|
7165
7413
|
if (!ruleName) {
|
|
7166
|
-
console.error(
|
|
7414
|
+
console.error(import_chalk4.default.red(`
|
|
7167
7415
|
\u274C Unknown rule "${rule}" for shield "${name}".
|
|
7168
7416
|
`));
|
|
7169
7417
|
process.exit(1);
|
|
@@ -7171,7 +7419,7 @@ shieldCmd.command("unset <service> <rule>").description("Remove a verdict overri
|
|
|
7171
7419
|
clearShieldOverride(name, ruleName);
|
|
7172
7420
|
const shortName = ruleName.replace(`shield:${name}:`, "");
|
|
7173
7421
|
console.error(
|
|
7174
|
-
|
|
7422
|
+
import_chalk4.default.green(`
|
|
7175
7423
|
\u2705 Override removed \u2014 ${name}/${shortName} restored to default.
|
|
7176
7424
|
`)
|
|
7177
7425
|
);
|
|
@@ -7180,32 +7428,32 @@ program.command("config show").description("Show the full effective runtime conf
|
|
|
7180
7428
|
const config = getConfig();
|
|
7181
7429
|
const active = readActiveShields();
|
|
7182
7430
|
const overrides = readShieldOverrides();
|
|
7183
|
-
console.error(
|
|
7184
|
-
const modeLabel = config.settings.mode === "audit" ?
|
|
7431
|
+
console.error(import_chalk4.default.bold("\n\u{1F50D} Node9 Effective Configuration\n"));
|
|
7432
|
+
const modeLabel = config.settings.mode === "audit" ? import_chalk4.default.blue("audit") : config.settings.mode === "strict" ? import_chalk4.default.red("strict") : import_chalk4.default.white("standard");
|
|
7185
7433
|
console.error(` Mode: ${modeLabel}
|
|
7186
7434
|
`);
|
|
7187
7435
|
if (active.length > 0) {
|
|
7188
|
-
console.error(
|
|
7436
|
+
console.error(import_chalk4.default.bold(" \u2500\u2500 Active Shields \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7189
7437
|
for (const name of active) {
|
|
7190
7438
|
const shield = getShield(name);
|
|
7191
7439
|
if (!shield) continue;
|
|
7192
7440
|
const ruleOverrides = overrides[name] ?? {};
|
|
7193
7441
|
console.error(`
|
|
7194
|
-
${
|
|
7442
|
+
${import_chalk4.default.green("\u25CF")} ${import_chalk4.default.cyan(name)}`);
|
|
7195
7443
|
for (const rule of shield.smartRules) {
|
|
7196
7444
|
const shortName = rule.name ? rule.name.replace(`shield:${name}:`, "") : "(unnamed)";
|
|
7197
7445
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
7198
7446
|
const effectiveVerdict = overrideVerdict ?? rule.verdict;
|
|
7199
|
-
const vLabel = effectiveVerdict === "block" ?
|
|
7200
|
-
const note = overrideVerdict ?
|
|
7447
|
+
const vLabel = effectiveVerdict === "block" ? import_chalk4.default.red("block ") : effectiveVerdict === "review" ? import_chalk4.default.yellow("review") : import_chalk4.default.green("allow ");
|
|
7448
|
+
const note = overrideVerdict ? import_chalk4.default.gray(` \u2190 overridden`) : "";
|
|
7201
7449
|
console.error(` ${vLabel} ${shortName}${note}`);
|
|
7202
7450
|
}
|
|
7203
7451
|
}
|
|
7204
7452
|
console.error("");
|
|
7205
7453
|
} else {
|
|
7206
|
-
console.error(
|
|
7454
|
+
console.error(import_chalk4.default.gray(" No shields active. Run `node9 shield list` to see options.\n"));
|
|
7207
7455
|
}
|
|
7208
|
-
console.error(
|
|
7456
|
+
console.error(import_chalk4.default.bold(" \u2500\u2500 Built-in Rules (always on) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7209
7457
|
for (const rule of config.policy.smartRules) {
|
|
7210
7458
|
const isShieldRule = rule.name?.startsWith("shield:");
|
|
7211
7459
|
const isAdvisory = [
|
|
@@ -7216,11 +7464,11 @@ program.command("config show").description("Show the full effective runtime conf
|
|
|
7216
7464
|
"review-drop-column-sql"
|
|
7217
7465
|
].includes(rule.name ?? "");
|
|
7218
7466
|
if (isShieldRule || isAdvisory) continue;
|
|
7219
|
-
const vLabel = rule.verdict === "block" ?
|
|
7220
|
-
console.error(` ${vLabel} ${
|
|
7467
|
+
const vLabel = rule.verdict === "block" ? import_chalk4.default.red("block ") : rule.verdict === "review" ? import_chalk4.default.yellow("review") : import_chalk4.default.green("allow ");
|
|
7468
|
+
console.error(` ${vLabel} ${import_chalk4.default.gray(rule.name ?? rule.tool)}`);
|
|
7221
7469
|
}
|
|
7222
7470
|
console.error("");
|
|
7223
|
-
console.error(
|
|
7471
|
+
console.error(import_chalk4.default.bold(" \u2500\u2500 Safe by Default (advisory, overridable) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7224
7472
|
const advisoryNames = /* @__PURE__ */ new Set([
|
|
7225
7473
|
"review-rm",
|
|
7226
7474
|
"allow-rm-safe-paths",
|
|
@@ -7230,12 +7478,12 @@ program.command("config show").description("Show the full effective runtime conf
|
|
|
7230
7478
|
]);
|
|
7231
7479
|
for (const rule of config.policy.smartRules) {
|
|
7232
7480
|
if (!advisoryNames.has(rule.name ?? "")) continue;
|
|
7233
|
-
const vLabel = rule.verdict === "block" ?
|
|
7234
|
-
console.error(` ${vLabel} ${
|
|
7481
|
+
const vLabel = rule.verdict === "block" ? import_chalk4.default.red("block ") : rule.verdict === "review" ? import_chalk4.default.yellow("review") : import_chalk4.default.green("allow ");
|
|
7482
|
+
console.error(` ${vLabel} ${import_chalk4.default.gray(rule.name ?? rule.tool)}`);
|
|
7235
7483
|
}
|
|
7236
7484
|
console.error("");
|
|
7237
|
-
console.error(
|
|
7238
|
-
console.error(` ${
|
|
7485
|
+
console.error(import_chalk4.default.bold(" \u2500\u2500 Dangerous Words \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7486
|
+
console.error(` ${import_chalk4.default.gray(config.policy.dangerousWords.join(", "))}
|
|
7239
7487
|
`);
|
|
7240
7488
|
});
|
|
7241
7489
|
if (process.argv[2] !== "daemon") {
|