@node9/proxy 1.5.2 → 1.5.4
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 +115 -20
- package/dist/cli.js +1408 -345
- package/dist/cli.mjs +1406 -343
- package/dist/index.js +201 -49
- package/dist/index.mjs +201 -49
- package/package.json +6 -5
package/dist/cli.js
CHANGED
|
@@ -160,8 +160,8 @@ function sanitizeConfig(raw) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
const lines = result.error.issues.map((issue) => {
|
|
163
|
-
const
|
|
164
|
-
return ` \u2022 ${
|
|
163
|
+
const path30 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
164
|
+
return ` \u2022 ${path30}: ${issue.message}`;
|
|
165
165
|
});
|
|
166
166
|
return {
|
|
167
167
|
sanitized,
|
|
@@ -213,12 +213,21 @@ var init_config_schema = __esm({
|
|
|
213
213
|
verdict: import_zod.z.enum(["allow", "review", "block"], {
|
|
214
214
|
errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
|
|
215
215
|
}),
|
|
216
|
-
reason: import_zod.z.string().optional()
|
|
216
|
+
reason: import_zod.z.string().optional(),
|
|
217
|
+
// Unknown predicate names are filtered out rather than failing the whole rule.
|
|
218
|
+
// Failing the whole z.array() would cause sanitizeConfig to drop the entire
|
|
219
|
+
// `policy` top-level key, silently disabling ALL smart rules in the config.
|
|
220
|
+
dependsOnState: import_zod.z.array(import_zod.z.string()).transform(
|
|
221
|
+
(arr) => arr.filter(
|
|
222
|
+
(p) => p === "no_test_passed_since_last_edit"
|
|
223
|
+
)
|
|
224
|
+
).optional(),
|
|
225
|
+
recoveryCommand: import_zod.z.string().optional()
|
|
217
226
|
});
|
|
218
227
|
ConfigFileSchema = import_zod.z.object({
|
|
219
228
|
version: import_zod.z.string().optional(),
|
|
220
229
|
settings: import_zod.z.object({
|
|
221
|
-
mode: import_zod.z.enum(["standard", "strict", "audit"]).optional(),
|
|
230
|
+
mode: import_zod.z.enum(["standard", "strict", "audit", "observe"]).optional(),
|
|
222
231
|
autoStartDaemon: import_zod.z.boolean().optional(),
|
|
223
232
|
enableUndo: import_zod.z.boolean().optional(),
|
|
224
233
|
enableHookLogDebug: import_zod.z.boolean().optional(),
|
|
@@ -645,12 +654,17 @@ function getConfig(cwd) {
|
|
|
645
654
|
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
646
655
|
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
647
656
|
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
657
|
+
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
648
658
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
649
659
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
650
660
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
651
661
|
if (p.toolInspection)
|
|
652
662
|
mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
|
|
653
|
-
if (p.smartRules)
|
|
663
|
+
if (p.smartRules) {
|
|
664
|
+
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
665
|
+
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
666
|
+
mergedPolicy.smartRules = [...defaultBlocks, ...p.smartRules, ...defaultNonBlocks];
|
|
667
|
+
}
|
|
654
668
|
if (p.snapshot) {
|
|
655
669
|
const s2 = p.snapshot;
|
|
656
670
|
if (s2.tools) mergedPolicy.snapshot.tools.push(...s2.tools);
|
|
@@ -849,7 +863,9 @@ var init_config = __esm({
|
|
|
849
863
|
{
|
|
850
864
|
field: "command",
|
|
851
865
|
op: "matches",
|
|
852
|
-
|
|
866
|
+
// Require the recursive flag to be preceded by whitespace so that
|
|
867
|
+
// filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
|
|
868
|
+
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
853
869
|
},
|
|
854
870
|
{
|
|
855
871
|
field: "command",
|
|
@@ -1758,9 +1774,9 @@ function matchesPattern(text, patterns) {
|
|
|
1758
1774
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1759
1775
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1760
1776
|
}
|
|
1761
|
-
function getNestedValue(obj,
|
|
1777
|
+
function getNestedValue(obj, path30) {
|
|
1762
1778
|
if (!obj || typeof obj !== "object") return null;
|
|
1763
|
-
return
|
|
1779
|
+
return path30.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1764
1780
|
}
|
|
1765
1781
|
function shouldSnapshot(toolName, args, config) {
|
|
1766
1782
|
if (!config.settings.enableUndo) return false;
|
|
@@ -1910,7 +1926,13 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
1910
1926
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
1911
1927
|
reason: matchedRule.reason,
|
|
1912
1928
|
tier: 2,
|
|
1913
|
-
ruleName: matchedRule.name ?? matchedRule.tool
|
|
1929
|
+
ruleName: matchedRule.name ?? matchedRule.tool,
|
|
1930
|
+
...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
|
|
1931
|
+
dependsOnStatePredicates: matchedRule.dependsOnState
|
|
1932
|
+
},
|
|
1933
|
+
...matchedRule.verdict === "block" && matchedRule.recoveryCommand && {
|
|
1934
|
+
recoveryCommand: matchedRule.recoveryCommand
|
|
1935
|
+
}
|
|
1914
1936
|
};
|
|
1915
1937
|
}
|
|
1916
1938
|
}
|
|
@@ -2437,6 +2459,34 @@ var init_state = __esm({
|
|
|
2437
2459
|
});
|
|
2438
2460
|
|
|
2439
2461
|
// src/auth/daemon.ts
|
|
2462
|
+
function notifyActivitySocket(data) {
|
|
2463
|
+
return new Promise((resolve) => {
|
|
2464
|
+
try {
|
|
2465
|
+
const payload = JSON.stringify(data);
|
|
2466
|
+
const sock = import_net.default.createConnection(ACTIVITY_SOCKET_PATH);
|
|
2467
|
+
sock.on("connect", () => {
|
|
2468
|
+
sock.on("close", resolve);
|
|
2469
|
+
sock.end(payload);
|
|
2470
|
+
});
|
|
2471
|
+
sock.on("error", resolve);
|
|
2472
|
+
} catch {
|
|
2473
|
+
resolve();
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
async function checkStatePredicates(predicates) {
|
|
2478
|
+
if (predicates.length === 0) return {};
|
|
2479
|
+
try {
|
|
2480
|
+
const qs = predicates.map(encodeURIComponent).join(",");
|
|
2481
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/state/check?predicates=${qs}`, {
|
|
2482
|
+
signal: AbortSignal.timeout(100)
|
|
2483
|
+
});
|
|
2484
|
+
if (!res.ok) return null;
|
|
2485
|
+
return await res.json();
|
|
2486
|
+
} catch {
|
|
2487
|
+
return null;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2440
2490
|
function getInternalToken() {
|
|
2441
2491
|
try {
|
|
2442
2492
|
const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
|
|
@@ -2470,7 +2520,7 @@ function isDaemonRunning() {
|
|
|
2470
2520
|
return false;
|
|
2471
2521
|
}
|
|
2472
2522
|
}
|
|
2473
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd) {
|
|
2523
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly) {
|
|
2474
2524
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2475
2525
|
const ctrl = new AbortController();
|
|
2476
2526
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -2488,7 +2538,10 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
2488
2538
|
// activity-result as the CLI used for the pending activity event.
|
|
2489
2539
|
activityId,
|
|
2490
2540
|
...riskMetadata && { riskMetadata },
|
|
2491
|
-
...cwd && { cwd }
|
|
2541
|
+
...cwd && { cwd },
|
|
2542
|
+
...recoveryCommand && { recoveryCommand },
|
|
2543
|
+
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
2544
|
+
...viewOnly && { viewOnly: true }
|
|
2492
2545
|
}),
|
|
2493
2546
|
signal: ctrl.signal
|
|
2494
2547
|
});
|
|
@@ -2508,10 +2561,10 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
2508
2561
|
try {
|
|
2509
2562
|
const waitRes = await fetch(`${base}/wait/${id}`, { signal: waitCtrl.signal });
|
|
2510
2563
|
if (!waitRes.ok) return { decision: "deny" };
|
|
2511
|
-
const { decision, source } = await waitRes.json();
|
|
2564
|
+
const { decision, source, reason } = await waitRes.json();
|
|
2512
2565
|
if (decision === "allow") return { decision: "allow", source };
|
|
2513
2566
|
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
2514
|
-
return { decision: "deny", source };
|
|
2567
|
+
return { decision: "deny", source, reason };
|
|
2515
2568
|
} finally {
|
|
2516
2569
|
clearTimeout(waitTimer);
|
|
2517
2570
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -2597,14 +2650,16 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
2597
2650
|
signal: AbortSignal.timeout(3e3)
|
|
2598
2651
|
});
|
|
2599
2652
|
}
|
|
2600
|
-
var import_fs9, import_path10, import_os8, import_child_process, DAEMON_PORT, DAEMON_HOST;
|
|
2653
|
+
var import_fs9, import_net, import_path10, import_os8, import_child_process, ACTIVITY_SOCKET_PATH, DAEMON_PORT, DAEMON_HOST;
|
|
2601
2654
|
var init_daemon = __esm({
|
|
2602
2655
|
"src/auth/daemon.ts"() {
|
|
2603
2656
|
"use strict";
|
|
2604
2657
|
import_fs9 = __toESM(require("fs"));
|
|
2658
|
+
import_net = __toESM(require("net"));
|
|
2605
2659
|
import_path10 = __toESM(require("path"));
|
|
2606
2660
|
import_os8 = __toESM(require("os"));
|
|
2607
2661
|
import_child_process = require("child_process");
|
|
2662
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path10.default.join(import_os8.default.tmpdir(), "node9-activity.sock");
|
|
2608
2663
|
DAEMON_PORT = 7391;
|
|
2609
2664
|
DAEMON_HOST = "127.0.0.1";
|
|
2610
2665
|
}
|
|
@@ -2966,6 +3021,33 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2966
3021
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
2967
3022
|
const controller = new AbortController();
|
|
2968
3023
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
3024
|
+
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
3025
|
+
let ciContext;
|
|
3026
|
+
if (process.env.CI) {
|
|
3027
|
+
try {
|
|
3028
|
+
const ciContextPath = import_path13.default.join(import_os9.default.homedir(), ".node9", "ci-context.json");
|
|
3029
|
+
const stats = import_fs10.default.statSync(ciContextPath);
|
|
3030
|
+
if (stats.size > 1e4) throw new Error("ci-context.json exceeds 10 KB");
|
|
3031
|
+
const raw = import_fs10.default.readFileSync(ciContextPath, "utf8");
|
|
3032
|
+
const parsed = JSON.parse(raw);
|
|
3033
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3034
|
+
throw new Error("ci-context.json is not a plain object");
|
|
3035
|
+
}
|
|
3036
|
+
const p = parsed;
|
|
3037
|
+
ciContext = {
|
|
3038
|
+
tests_after: p["tests_after"],
|
|
3039
|
+
files_changed: p["files_changed"],
|
|
3040
|
+
issues_found: p["issues_found"],
|
|
3041
|
+
issues_fixed: p["issues_fixed"],
|
|
3042
|
+
github_repository: p["github_repository"],
|
|
3043
|
+
github_head_ref: p["github_head_ref"],
|
|
3044
|
+
iteration: p["iteration"],
|
|
3045
|
+
draft_pr_number: p["draft_pr_number"],
|
|
3046
|
+
draft_pr_url: p["draft_pr_url"]
|
|
3047
|
+
};
|
|
3048
|
+
} catch {
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
2969
3051
|
try {
|
|
2970
3052
|
const response = await fetch(creds.apiUrl, {
|
|
2971
3053
|
method: "POST",
|
|
@@ -2980,7 +3062,8 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2980
3062
|
cwd: process.cwd(),
|
|
2981
3063
|
platform: import_os9.default.platform()
|
|
2982
3064
|
},
|
|
2983
|
-
...riskMetadata && { riskMetadata }
|
|
3065
|
+
...riskMetadata && { riskMetadata },
|
|
3066
|
+
...ciContext && { ciContext }
|
|
2984
3067
|
}),
|
|
2985
3068
|
signal: controller.signal
|
|
2986
3069
|
});
|
|
@@ -3006,12 +3089,17 @@ async function pollNode9SaaS(requestId, creds, signal) {
|
|
|
3006
3089
|
});
|
|
3007
3090
|
clearTimeout(pollTimer);
|
|
3008
3091
|
if (!statusRes.ok) continue;
|
|
3009
|
-
const
|
|
3092
|
+
const statusBody = await statusRes.json();
|
|
3093
|
+
const { status } = statusBody;
|
|
3010
3094
|
if (status === "APPROVED") {
|
|
3011
|
-
return { approved: true, reason };
|
|
3095
|
+
return { approved: true, reason: statusBody.reason };
|
|
3012
3096
|
}
|
|
3013
3097
|
if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
|
|
3014
|
-
return { approved: false, reason };
|
|
3098
|
+
return { approved: false, reason: statusBody.reason };
|
|
3099
|
+
}
|
|
3100
|
+
if (status === "FIX") {
|
|
3101
|
+
const feedbackText = statusBody.feedbackText ?? statusBody.reason ?? "Run again with feedback.";
|
|
3102
|
+
return { approved: false, reason: feedbackText };
|
|
3015
3103
|
}
|
|
3016
3104
|
} catch {
|
|
3017
3105
|
}
|
|
@@ -3048,12 +3136,13 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
3048
3136
|
);
|
|
3049
3137
|
}
|
|
3050
3138
|
}
|
|
3051
|
-
var import_fs10, import_os9;
|
|
3139
|
+
var import_fs10, import_os9, import_path13;
|
|
3052
3140
|
var init_cloud = __esm({
|
|
3053
3141
|
"src/auth/cloud.ts"() {
|
|
3054
3142
|
"use strict";
|
|
3055
3143
|
import_fs10 = __toESM(require("fs"));
|
|
3056
3144
|
import_os9 = __toESM(require("os"));
|
|
3145
|
+
import_path13 = __toESM(require("path"));
|
|
3057
3146
|
init_audit();
|
|
3058
3147
|
}
|
|
3059
3148
|
});
|
|
@@ -3094,19 +3183,7 @@ function isNetworkTool(toolName, args) {
|
|
|
3094
3183
|
return false;
|
|
3095
3184
|
}
|
|
3096
3185
|
function notifyActivity(data) {
|
|
3097
|
-
return
|
|
3098
|
-
try {
|
|
3099
|
-
const payload = JSON.stringify(data);
|
|
3100
|
-
const sock = import_net.default.createConnection(ACTIVITY_SOCKET_PATH);
|
|
3101
|
-
sock.on("connect", () => {
|
|
3102
|
-
sock.on("close", resolve);
|
|
3103
|
-
sock.end(payload);
|
|
3104
|
-
});
|
|
3105
|
-
sock.on("error", resolve);
|
|
3106
|
-
} catch {
|
|
3107
|
-
resolve();
|
|
3108
|
-
}
|
|
3109
|
-
});
|
|
3186
|
+
return notifyActivitySocket(data);
|
|
3110
3187
|
}
|
|
3111
3188
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
3112
3189
|
if (!options?.calledFromDaemon) {
|
|
@@ -3123,7 +3200,9 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
3123
3200
|
tool: toolName,
|
|
3124
3201
|
ts: actTs,
|
|
3125
3202
|
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
|
|
3126
|
-
label: result.blockedByLabel
|
|
3203
|
+
label: result.blockedByLabel,
|
|
3204
|
+
ruleHit: result.ruleHit,
|
|
3205
|
+
observeWouldBlock: result.observeWouldBlock
|
|
3127
3206
|
});
|
|
3128
3207
|
}
|
|
3129
3208
|
return result;
|
|
@@ -3150,10 +3229,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3150
3229
|
appendHookDebug(toolName, args, meta, hashAuditArgs);
|
|
3151
3230
|
}
|
|
3152
3231
|
const isManual = meta?.agent === "Terminal";
|
|
3232
|
+
const isObserveMode = config.settings.mode === "observe";
|
|
3153
3233
|
let explainableLabel = "Local Config";
|
|
3154
3234
|
let policyMatchedField;
|
|
3155
3235
|
let policyMatchedWord;
|
|
3156
3236
|
let riskMetadata;
|
|
3237
|
+
let statefulRecoveryCommand;
|
|
3157
3238
|
let taintWarning = null;
|
|
3158
3239
|
if (isNetworkTool(toolName, args)) {
|
|
3159
3240
|
const filePaths = extractFilePaths(toolName, args);
|
|
@@ -3174,10 +3255,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3174
3255
|
if (dlpMatch) {
|
|
3175
3256
|
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
3176
3257
|
if (dlpMatch.severity === "block") {
|
|
3177
|
-
if (!isManual)
|
|
3258
|
+
if (!isManual)
|
|
3259
|
+
appendLocalAudit(
|
|
3260
|
+
toolName,
|
|
3261
|
+
args,
|
|
3262
|
+
"deny",
|
|
3263
|
+
isObserveMode ? "observe-mode-dlp-would-block" : "dlp-block",
|
|
3264
|
+
meta,
|
|
3265
|
+
true
|
|
3266
|
+
);
|
|
3178
3267
|
if (isWriteTool(toolName) && filePath) {
|
|
3179
3268
|
await notifyTaint(filePath, `DLP:${dlpMatch.patternName}`);
|
|
3180
3269
|
}
|
|
3270
|
+
if (isObserveMode) {
|
|
3271
|
+
return {
|
|
3272
|
+
approved: true,
|
|
3273
|
+
checkedBy: "audit",
|
|
3274
|
+
observeWouldBlock: true,
|
|
3275
|
+
blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3181
3278
|
return {
|
|
3182
3279
|
approved: false,
|
|
3183
3280
|
reason: dlpReason,
|
|
@@ -3190,6 +3287,31 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3190
3287
|
explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
|
|
3191
3288
|
}
|
|
3192
3289
|
}
|
|
3290
|
+
if (isObserveMode) {
|
|
3291
|
+
if (!isIgnoredTool(toolName)) {
|
|
3292
|
+
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
3293
|
+
const wouldBlock = policyResult.decision === "block";
|
|
3294
|
+
if (!isManual)
|
|
3295
|
+
appendLocalAudit(
|
|
3296
|
+
toolName,
|
|
3297
|
+
args,
|
|
3298
|
+
"allow",
|
|
3299
|
+
wouldBlock ? "observe-mode-would-block" : "observe-mode",
|
|
3300
|
+
meta,
|
|
3301
|
+
hashAuditArgs
|
|
3302
|
+
);
|
|
3303
|
+
return {
|
|
3304
|
+
approved: true,
|
|
3305
|
+
checkedBy: "audit",
|
|
3306
|
+
...wouldBlock && {
|
|
3307
|
+
observeWouldBlock: true,
|
|
3308
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
3309
|
+
ruleHit: policyResult.ruleName
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
return { approved: true, checkedBy: "audit" };
|
|
3314
|
+
}
|
|
3193
3315
|
if (config.settings.mode === "audit") {
|
|
3194
3316
|
if (!isIgnoredTool(toolName)) {
|
|
3195
3317
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
@@ -3212,19 +3334,46 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3212
3334
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
3213
3335
|
if (policyResult.decision === "allow") {
|
|
3214
3336
|
if (approvers.cloud && creds?.apiKey)
|
|
3215
|
-
auditLocalAllow(toolName, args, "local-policy", creds, meta);
|
|
3337
|
+
await auditLocalAllow(toolName, args, "local-policy", creds, meta);
|
|
3216
3338
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
|
|
3217
3339
|
return { approved: true, checkedBy: "local-policy" };
|
|
3218
3340
|
}
|
|
3219
3341
|
if (policyResult.decision === "block") {
|
|
3220
|
-
if (
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3342
|
+
if (policyResult.dependsOnStatePredicates?.length) {
|
|
3343
|
+
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
3344
|
+
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
3345
|
+
if (stateResults === null && !isManual) {
|
|
3346
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
3347
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3348
|
+
event: "state-check-fail-open",
|
|
3349
|
+
tool: toolName,
|
|
3350
|
+
rule: policyResult.ruleName,
|
|
3351
|
+
predicates: policyResult.dependsOnStatePredicates,
|
|
3352
|
+
reason: "daemon unreachable or /state/check timed out \u2014 block rule downgraded to review"
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
if (predicatesMet && policyResult.recoveryCommand) {
|
|
3356
|
+
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
3357
|
+
}
|
|
3358
|
+
} else if (isDaemonRunning() && !isTestEnv2) {
|
|
3359
|
+
if (!isManual)
|
|
3360
|
+
appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
|
|
3361
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3362
|
+
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
|
|
3363
|
+
} else {
|
|
3364
|
+
if (!isManual)
|
|
3365
|
+
appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
|
|
3366
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3367
|
+
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
|
|
3368
|
+
return {
|
|
3369
|
+
approved: false,
|
|
3370
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
3371
|
+
blockedBy: "local-config",
|
|
3372
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
3373
|
+
ruleHit: policyResult.ruleName,
|
|
3374
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3228
3377
|
}
|
|
3229
3378
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
3230
3379
|
policyMatchedField = policyResult.matchedField;
|
|
@@ -3331,7 +3480,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3331
3480
|
meta,
|
|
3332
3481
|
riskMetadata,
|
|
3333
3482
|
options?.activityId,
|
|
3334
|
-
options?.cwd
|
|
3483
|
+
options?.cwd,
|
|
3484
|
+
statefulRecoveryCommand
|
|
3335
3485
|
);
|
|
3336
3486
|
daemonEntryId = entry.id;
|
|
3337
3487
|
daemonAllowCount = entry.allowCount;
|
|
@@ -3392,20 +3542,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3392
3542
|
if (daemonEntryId && (approvers.browser || approvers.terminal)) {
|
|
3393
3543
|
racePromises.push(
|
|
3394
3544
|
(async () => {
|
|
3395
|
-
const {
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3545
|
+
const {
|
|
3546
|
+
decision: daemonDecision,
|
|
3547
|
+
source: decisionSource,
|
|
3548
|
+
reason: daemonReason
|
|
3549
|
+
} = await waitForDaemonDecision(daemonEntryId, signal);
|
|
3399
3550
|
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
3400
3551
|
const isApproved = daemonDecision === "allow";
|
|
3401
|
-
const
|
|
3552
|
+
const isRedirect = decisionSource === "terminal-redirect";
|
|
3553
|
+
const src = decisionSource === "terminal" || decisionSource === "terminal-redirect" || decisionSource === "browser" ? decisionSource === "browser" ? "browser" : "terminal" : approvers.browser ? "browser" : "terminal";
|
|
3402
3554
|
const via = src === "terminal" ? "Terminal (node9 tail)" : "Browser Dashboard";
|
|
3403
3555
|
return {
|
|
3404
3556
|
approved: isApproved,
|
|
3405
|
-
reason: isApproved ? void 0 :
|
|
3557
|
+
reason: isApproved ? void 0 : (
|
|
3558
|
+
// Use the redirect reason from the tail when choice [2] was selected;
|
|
3559
|
+
// otherwise fall back to the generic rejection message.
|
|
3560
|
+
isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`
|
|
3561
|
+
),
|
|
3406
3562
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
3407
3563
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
3408
|
-
blockedByLabel: `User Decision (${via})`,
|
|
3564
|
+
blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
|
|
3409
3565
|
decisionSource: src
|
|
3410
3566
|
};
|
|
3411
3567
|
})()
|
|
@@ -3480,13 +3636,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3480
3636
|
}
|
|
3481
3637
|
return finalResult;
|
|
3482
3638
|
}
|
|
3483
|
-
var
|
|
3639
|
+
var import_crypto3, WRITE_TOOLS;
|
|
3484
3640
|
var init_orchestrator = __esm({
|
|
3485
3641
|
"src/auth/orchestrator.ts"() {
|
|
3486
3642
|
"use strict";
|
|
3487
|
-
import_net = __toESM(require("net"));
|
|
3488
|
-
import_path13 = __toESM(require("path"));
|
|
3489
|
-
import_os10 = __toESM(require("os"));
|
|
3490
3643
|
import_crypto3 = require("crypto");
|
|
3491
3644
|
init_native();
|
|
3492
3645
|
init_context_sniper();
|
|
@@ -3508,7 +3661,6 @@ var init_orchestrator = __esm({
|
|
|
3508
3661
|
"notebook_edit",
|
|
3509
3662
|
"notebookedit"
|
|
3510
3663
|
]);
|
|
3511
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os10.default.tmpdir(), "node9-activity.sock");
|
|
3512
3664
|
}
|
|
3513
3665
|
});
|
|
3514
3666
|
|
|
@@ -5314,6 +5466,107 @@ var init_taint_store = __esm({
|
|
|
5314
5466
|
}
|
|
5315
5467
|
});
|
|
5316
5468
|
|
|
5469
|
+
// src/daemon/session-counters.ts
|
|
5470
|
+
var SessionCounters, sessionCounters;
|
|
5471
|
+
var init_session_counters = __esm({
|
|
5472
|
+
"src/daemon/session-counters.ts"() {
|
|
5473
|
+
"use strict";
|
|
5474
|
+
SessionCounters = class {
|
|
5475
|
+
_allowed = 0;
|
|
5476
|
+
_blocked = 0;
|
|
5477
|
+
_dlpHits = 0;
|
|
5478
|
+
_wouldBlock = 0;
|
|
5479
|
+
_lastRuleHit = null;
|
|
5480
|
+
_lastBlockedTool = null;
|
|
5481
|
+
incrementAllowed() {
|
|
5482
|
+
this._allowed++;
|
|
5483
|
+
}
|
|
5484
|
+
incrementBlocked() {
|
|
5485
|
+
this._blocked++;
|
|
5486
|
+
}
|
|
5487
|
+
incrementDlpHits() {
|
|
5488
|
+
this._dlpHits++;
|
|
5489
|
+
}
|
|
5490
|
+
incrementWouldBlock() {
|
|
5491
|
+
this._wouldBlock++;
|
|
5492
|
+
}
|
|
5493
|
+
recordRuleHit(label) {
|
|
5494
|
+
this._lastRuleHit = label;
|
|
5495
|
+
}
|
|
5496
|
+
recordBlockedTool(toolName) {
|
|
5497
|
+
this._lastBlockedTool = toolName;
|
|
5498
|
+
}
|
|
5499
|
+
get() {
|
|
5500
|
+
return {
|
|
5501
|
+
allowed: this._allowed,
|
|
5502
|
+
blocked: this._blocked,
|
|
5503
|
+
dlpHits: this._dlpHits,
|
|
5504
|
+
wouldBlock: this._wouldBlock,
|
|
5505
|
+
lastRuleHit: this._lastRuleHit,
|
|
5506
|
+
lastBlockedTool: this._lastBlockedTool
|
|
5507
|
+
};
|
|
5508
|
+
}
|
|
5509
|
+
reset() {
|
|
5510
|
+
this._allowed = 0;
|
|
5511
|
+
this._blocked = 0;
|
|
5512
|
+
this._dlpHits = 0;
|
|
5513
|
+
this._wouldBlock = 0;
|
|
5514
|
+
this._lastRuleHit = null;
|
|
5515
|
+
this._lastBlockedTool = null;
|
|
5516
|
+
}
|
|
5517
|
+
};
|
|
5518
|
+
sessionCounters = new SessionCounters();
|
|
5519
|
+
}
|
|
5520
|
+
});
|
|
5521
|
+
|
|
5522
|
+
// src/daemon/session-history.ts
|
|
5523
|
+
var SessionHistory, sessionHistory;
|
|
5524
|
+
var init_session_history = __esm({
|
|
5525
|
+
"src/daemon/session-history.ts"() {
|
|
5526
|
+
"use strict";
|
|
5527
|
+
SessionHistory = class {
|
|
5528
|
+
lastEditAt = null;
|
|
5529
|
+
lastTestPassAt = null;
|
|
5530
|
+
lastTestFailAt = null;
|
|
5531
|
+
recordEdit(ts = Date.now()) {
|
|
5532
|
+
this.lastEditAt = ts;
|
|
5533
|
+
}
|
|
5534
|
+
recordTestPass(ts = Date.now()) {
|
|
5535
|
+
this.lastTestPassAt = ts;
|
|
5536
|
+
}
|
|
5537
|
+
recordTestFail(ts = Date.now()) {
|
|
5538
|
+
this.lastTestFailAt = ts;
|
|
5539
|
+
}
|
|
5540
|
+
/**
|
|
5541
|
+
* Returns true when the named predicate is currently satisfied.
|
|
5542
|
+
* Unknown predicates always return false (fail-open: don't block on unknown state).
|
|
5543
|
+
*/
|
|
5544
|
+
checkPredicate(name) {
|
|
5545
|
+
switch (name) {
|
|
5546
|
+
case "no_test_passed_since_last_edit":
|
|
5547
|
+
if (this.lastEditAt === null) return false;
|
|
5548
|
+
return this.lastTestPassAt === null || this.lastTestPassAt < this.lastEditAt;
|
|
5549
|
+
default:
|
|
5550
|
+
return false;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
getSnapshot() {
|
|
5554
|
+
return {
|
|
5555
|
+
lastEditAt: this.lastEditAt,
|
|
5556
|
+
lastTestPassAt: this.lastTestPassAt,
|
|
5557
|
+
lastTestFailAt: this.lastTestFailAt
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
reset() {
|
|
5561
|
+
this.lastEditAt = null;
|
|
5562
|
+
this.lastTestPassAt = null;
|
|
5563
|
+
this.lastTestFailAt = null;
|
|
5564
|
+
}
|
|
5565
|
+
};
|
|
5566
|
+
sessionHistory = new SessionHistory();
|
|
5567
|
+
}
|
|
5568
|
+
});
|
|
5569
|
+
|
|
5317
5570
|
// src/daemon/state.ts
|
|
5318
5571
|
function loadInsightCounts() {
|
|
5319
5572
|
try {
|
|
@@ -5475,6 +5728,7 @@ function readBody(req) {
|
|
|
5475
5728
|
});
|
|
5476
5729
|
}
|
|
5477
5730
|
function openBrowser(url) {
|
|
5731
|
+
if (process.env.NODE9_TESTING === "1") return;
|
|
5478
5732
|
try {
|
|
5479
5733
|
const args = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
|
|
5480
5734
|
(0, import_child_process3.spawn)(args[0], args.slice(1), { detached: true, stdio: "ignore" }).unref();
|
|
@@ -5546,6 +5800,14 @@ function startActivitySocket() {
|
|
|
5546
5800
|
socket.on("end", () => {
|
|
5547
5801
|
try {
|
|
5548
5802
|
const data = JSON.parse(Buffer.concat(chunks).toString());
|
|
5803
|
+
if (data.status === "test_pass") {
|
|
5804
|
+
sessionHistory.recordTestPass(data.ts);
|
|
5805
|
+
return;
|
|
5806
|
+
}
|
|
5807
|
+
if (data.status === "test_fail") {
|
|
5808
|
+
sessionHistory.recordTestFail(data.ts);
|
|
5809
|
+
return;
|
|
5810
|
+
}
|
|
5549
5811
|
if (data.status === "pending") {
|
|
5550
5812
|
broadcast("activity", {
|
|
5551
5813
|
id: data.id,
|
|
@@ -5555,6 +5817,24 @@ function startActivitySocket() {
|
|
|
5555
5817
|
status: "pending"
|
|
5556
5818
|
});
|
|
5557
5819
|
} else {
|
|
5820
|
+
if (data.status === "allow") {
|
|
5821
|
+
sessionCounters.incrementAllowed();
|
|
5822
|
+
if (data.observeWouldBlock) sessionCounters.incrementWouldBlock();
|
|
5823
|
+
if (WRITE_TOOL_NAMES.has(data.tool.toLowerCase().replace(/[^a-z_]/g, "_"))) {
|
|
5824
|
+
sessionHistory.recordEdit(data.ts);
|
|
5825
|
+
}
|
|
5826
|
+
} else if (data.status === "block") {
|
|
5827
|
+
sessionCounters.incrementBlocked();
|
|
5828
|
+
sessionCounters.recordBlockedTool(data.tool);
|
|
5829
|
+
if (data.ruleHit) sessionCounters.recordRuleHit(data.ruleHit);
|
|
5830
|
+
} else if (data.status === "dlp") {
|
|
5831
|
+
sessionCounters.incrementBlocked();
|
|
5832
|
+
sessionCounters.incrementDlpHits();
|
|
5833
|
+
sessionCounters.recordBlockedTool(data.tool);
|
|
5834
|
+
} else if (data.status === "taint") {
|
|
5835
|
+
sessionCounters.incrementBlocked();
|
|
5836
|
+
sessionCounters.recordBlockedTool(data.tool);
|
|
5837
|
+
}
|
|
5558
5838
|
broadcast("activity-result", {
|
|
5559
5839
|
id: data.id,
|
|
5560
5840
|
status: data.status,
|
|
@@ -5575,20 +5855,22 @@ function startActivitySocket() {
|
|
|
5575
5855
|
}
|
|
5576
5856
|
});
|
|
5577
5857
|
}
|
|
5578
|
-
var import_net2, import_fs13, import_path16,
|
|
5858
|
+
var import_net2, import_fs13, import_path16, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, WRITE_TOOL_NAMES;
|
|
5579
5859
|
var init_state2 = __esm({
|
|
5580
5860
|
"src/daemon/state.ts"() {
|
|
5581
5861
|
"use strict";
|
|
5582
5862
|
import_net2 = __toESM(require("net"));
|
|
5583
5863
|
import_fs13 = __toESM(require("fs"));
|
|
5584
5864
|
import_path16 = __toESM(require("path"));
|
|
5585
|
-
|
|
5865
|
+
import_os11 = __toESM(require("os"));
|
|
5586
5866
|
import_child_process3 = require("child_process");
|
|
5587
5867
|
import_crypto5 = require("crypto");
|
|
5588
5868
|
init_daemon();
|
|
5589
5869
|
init_suggestion_tracker();
|
|
5590
5870
|
init_taint_store();
|
|
5591
|
-
|
|
5871
|
+
init_session_counters();
|
|
5872
|
+
init_session_history();
|
|
5873
|
+
homeDir = import_os11.default.homedir();
|
|
5592
5874
|
DAEMON_PID_FILE = import_path16.default.join(homeDir, ".node9", "daemon.pid");
|
|
5593
5875
|
DECISIONS_FILE = import_path16.default.join(homeDir, ".node9", "decisions.json");
|
|
5594
5876
|
AUDIT_LOG_FILE = import_path16.default.join(homeDir, ".node9", "audit.log");
|
|
@@ -5613,10 +5895,21 @@ var init_state2 = __esm({
|
|
|
5613
5895
|
"2h": 2 * 60 * 6e4
|
|
5614
5896
|
};
|
|
5615
5897
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5616
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path16.default.join(
|
|
5898
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path16.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
|
|
5617
5899
|
ACTIVITY_RING_SIZE = 100;
|
|
5618
5900
|
activityRing = [];
|
|
5619
5901
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
5902
|
+
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
5903
|
+
"write",
|
|
5904
|
+
"write_file",
|
|
5905
|
+
"create_file",
|
|
5906
|
+
"edit",
|
|
5907
|
+
"multiedit",
|
|
5908
|
+
"str_replace_based_edit_tool",
|
|
5909
|
+
"replace",
|
|
5910
|
+
"notebook_edit",
|
|
5911
|
+
"notebookedit"
|
|
5912
|
+
]);
|
|
5620
5913
|
}
|
|
5621
5914
|
});
|
|
5622
5915
|
|
|
@@ -5666,14 +5959,14 @@ function patchConfig(configPath, patch) {
|
|
|
5666
5959
|
throw err;
|
|
5667
5960
|
}
|
|
5668
5961
|
}
|
|
5669
|
-
var import_fs14, import_path17,
|
|
5962
|
+
var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
|
|
5670
5963
|
var init_patch = __esm({
|
|
5671
5964
|
"src/config/patch.ts"() {
|
|
5672
5965
|
"use strict";
|
|
5673
5966
|
import_fs14 = __toESM(require("fs"));
|
|
5674
5967
|
import_path17 = __toESM(require("path"));
|
|
5675
|
-
|
|
5676
|
-
GLOBAL_CONFIG_PATH = import_path17.default.join(
|
|
5968
|
+
import_os12 = __toESM(require("os"));
|
|
5969
|
+
GLOBAL_CONFIG_PATH = import_path17.default.join(import_os12.default.homedir(), ".node9", "config.json");
|
|
5677
5970
|
}
|
|
5678
5971
|
});
|
|
5679
5972
|
|
|
@@ -5740,6 +6033,8 @@ data: ${JSON.stringify({
|
|
|
5740
6033
|
toolName: e.toolName,
|
|
5741
6034
|
args: e.args,
|
|
5742
6035
|
riskMetadata: e.riskMetadata,
|
|
6036
|
+
...e.recoveryCommand && { recoveryCommand: e.recoveryCommand },
|
|
6037
|
+
...e.viewOnly && { viewOnly: true },
|
|
5743
6038
|
slackDelegated: e.slackDelegated,
|
|
5744
6039
|
timestamp: e.timestamp,
|
|
5745
6040
|
agent: e.agent,
|
|
@@ -5805,6 +6100,9 @@ data: ${JSON.stringify(item.data)}
|
|
|
5805
6100
|
agent,
|
|
5806
6101
|
mcpServer,
|
|
5807
6102
|
riskMetadata,
|
|
6103
|
+
recoveryCommand,
|
|
6104
|
+
skipBackgroundAuth = false,
|
|
6105
|
+
viewOnly = false,
|
|
5808
6106
|
fromCLI = false,
|
|
5809
6107
|
activityId,
|
|
5810
6108
|
cwd
|
|
@@ -5815,6 +6113,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
5815
6113
|
toolName,
|
|
5816
6114
|
args,
|
|
5817
6115
|
riskMetadata: riskMetadata ?? void 0,
|
|
6116
|
+
...typeof recoveryCommand === "string" && recoveryCommand && { recoveryCommand },
|
|
6117
|
+
...viewOnly && { viewOnly: true },
|
|
5818
6118
|
agent: typeof agent === "string" ? agent : void 0,
|
|
5819
6119
|
mcpServer: typeof mcpServer === "string" ? mcpServer : void 0,
|
|
5820
6120
|
slackDelegated: !!slackDelegated,
|
|
@@ -5859,6 +6159,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
5859
6159
|
toolName,
|
|
5860
6160
|
args,
|
|
5861
6161
|
riskMetadata: entry.riskMetadata,
|
|
6162
|
+
...entry.recoveryCommand && { recoveryCommand: entry.recoveryCommand },
|
|
6163
|
+
...entry.viewOnly && { viewOnly: true },
|
|
5862
6164
|
slackDelegated: entry.slackDelegated,
|
|
5863
6165
|
agent: entry.agent,
|
|
5864
6166
|
mcpServer: entry.mcpServer,
|
|
@@ -5875,7 +6177,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
5875
6177
|
}
|
|
5876
6178
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5877
6179
|
res.end(JSON.stringify({ id, allowCount: (insightCounts.get(toolName) ?? 0) + 1 }));
|
|
5878
|
-
if (slackDelegated) return;
|
|
6180
|
+
if (slackDelegated || skipBackgroundAuth) return;
|
|
5879
6181
|
authorizeHeadless(
|
|
5880
6182
|
toolName,
|
|
5881
6183
|
args,
|
|
@@ -6014,7 +6316,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6014
6316
|
saveInsightCounts();
|
|
6015
6317
|
suggestionTracker.resetTool(entry.toolName);
|
|
6016
6318
|
}
|
|
6017
|
-
const VALID_SOURCES = /* @__PURE__ */ new Set(["terminal", "browser", "native"]);
|
|
6319
|
+
const VALID_SOURCES = /* @__PURE__ */ new Set(["terminal", "browser", "native", "terminal-redirect"]);
|
|
6018
6320
|
if (source && VALID_SOURCES.has(source)) entry.decisionSource = source;
|
|
6019
6321
|
if (entry.waiter) {
|
|
6020
6322
|
entry.waiter(resolvedDecision, reason);
|
|
@@ -6043,6 +6345,41 @@ data: ${JSON.stringify(item.data)}
|
|
|
6043
6345
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
6044
6346
|
}
|
|
6045
6347
|
}
|
|
6348
|
+
if (req.method === "GET" && pathname === "/status") {
|
|
6349
|
+
try {
|
|
6350
|
+
const s = getGlobalSettings();
|
|
6351
|
+
const counters = sessionCounters.get();
|
|
6352
|
+
const mode = s.mode ?? "standard";
|
|
6353
|
+
const status = {
|
|
6354
|
+
mode,
|
|
6355
|
+
session: {
|
|
6356
|
+
allowed: counters.allowed,
|
|
6357
|
+
blocked: counters.blocked,
|
|
6358
|
+
dlpHits: counters.dlpHits,
|
|
6359
|
+
wouldBlock: counters.wouldBlock
|
|
6360
|
+
},
|
|
6361
|
+
taintedCount: taintStore.list().length,
|
|
6362
|
+
lastRuleHit: counters.lastRuleHit,
|
|
6363
|
+
lastBlockedTool: counters.lastBlockedTool
|
|
6364
|
+
};
|
|
6365
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6366
|
+
return res.end(JSON.stringify(status));
|
|
6367
|
+
} catch (err) {
|
|
6368
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /status failed:"), err);
|
|
6369
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6370
|
+
return res.end(JSON.stringify({ error: "internal" }));
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
if (req.method === "GET" && pathname === "/state/check") {
|
|
6374
|
+
const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
|
|
6375
|
+
const predicates = predicatesParam.split(",").filter(Boolean);
|
|
6376
|
+
const results = {};
|
|
6377
|
+
for (const p of predicates) {
|
|
6378
|
+
results[p] = sessionHistory.checkPredicate(p);
|
|
6379
|
+
}
|
|
6380
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6381
|
+
return res.end(JSON.stringify(results));
|
|
6382
|
+
}
|
|
6046
6383
|
if (req.method === "POST" && pathname === "/settings") {
|
|
6047
6384
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
6048
6385
|
try {
|
|
@@ -6466,27 +6803,27 @@ function formatBase(activity) {
|
|
|
6466
6803
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6467
6804
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
|
|
6468
6805
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6469
|
-
return `${
|
|
6806
|
+
return `${import_chalk17.default.gray(time)} ${icon} ${import_chalk17.default.white.bold(toolName)} ${import_chalk17.default.dim(argsPreview)}`;
|
|
6470
6807
|
}
|
|
6471
6808
|
function renderResult(activity, result) {
|
|
6472
6809
|
const base = formatBase(activity);
|
|
6473
6810
|
let status;
|
|
6474
6811
|
if (result.status === "allow") {
|
|
6475
|
-
status =
|
|
6812
|
+
status = import_chalk17.default.green("\u2713 ALLOW");
|
|
6476
6813
|
} else if (result.status === "dlp") {
|
|
6477
|
-
status =
|
|
6814
|
+
status = import_chalk17.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
6478
6815
|
} else {
|
|
6479
|
-
status =
|
|
6816
|
+
status = import_chalk17.default.red("\u2717 BLOCK");
|
|
6480
6817
|
}
|
|
6481
6818
|
if (process.stdout.isTTY) {
|
|
6482
|
-
|
|
6483
|
-
|
|
6819
|
+
import_readline4.default.clearLine(process.stdout, 0);
|
|
6820
|
+
import_readline4.default.cursorTo(process.stdout, 0);
|
|
6484
6821
|
}
|
|
6485
6822
|
console.log(`${base} ${status}`);
|
|
6486
6823
|
}
|
|
6487
6824
|
function renderPending(activity) {
|
|
6488
6825
|
if (!process.stdout.isTTY) return;
|
|
6489
|
-
process.stdout.write(`${formatBase(activity)} ${
|
|
6826
|
+
process.stdout.write(`${formatBase(activity)} ${import_chalk17.default.yellow("\u25CF \u2026")}\r`);
|
|
6490
6827
|
}
|
|
6491
6828
|
async function ensureDaemon() {
|
|
6492
6829
|
let pidPort = null;
|
|
@@ -6495,7 +6832,7 @@ async function ensureDaemon() {
|
|
|
6495
6832
|
const { port } = JSON.parse(import_fs24.default.readFileSync(PID_FILE, "utf-8"));
|
|
6496
6833
|
pidPort = port;
|
|
6497
6834
|
} catch {
|
|
6498
|
-
console.error(
|
|
6835
|
+
console.error(import_chalk17.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
6499
6836
|
}
|
|
6500
6837
|
}
|
|
6501
6838
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -6506,7 +6843,7 @@ async function ensureDaemon() {
|
|
|
6506
6843
|
if (res.ok) return checkPort;
|
|
6507
6844
|
} catch {
|
|
6508
6845
|
}
|
|
6509
|
-
console.log(
|
|
6846
|
+
console.log(import_chalk17.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6510
6847
|
const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6511
6848
|
detached: true,
|
|
6512
6849
|
stdio: "ignore",
|
|
@@ -6523,14 +6860,15 @@ async function ensureDaemon() {
|
|
|
6523
6860
|
} catch {
|
|
6524
6861
|
}
|
|
6525
6862
|
}
|
|
6526
|
-
console.error(
|
|
6863
|
+
console.error(import_chalk17.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6527
6864
|
process.exit(1);
|
|
6528
6865
|
}
|
|
6529
6866
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
6530
6867
|
return new Promise((resolve, reject) => {
|
|
6531
|
-
const bodyObj = { decision, source: "terminal" };
|
|
6868
|
+
const bodyObj = { decision, source: opts?.source ?? "terminal" };
|
|
6532
6869
|
if (opts?.persist) bodyObj.persist = true;
|
|
6533
6870
|
if (opts?.trustDuration) bodyObj.trustDuration = opts.trustDuration;
|
|
6871
|
+
if (opts?.reason) bodyObj.reason = opts.reason;
|
|
6534
6872
|
const body = JSON.stringify(bodyObj);
|
|
6535
6873
|
const req = import_http2.default.request(
|
|
6536
6874
|
{
|
|
@@ -6555,33 +6893,61 @@ function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
|
6555
6893
|
});
|
|
6556
6894
|
}
|
|
6557
6895
|
function buildCardLines(req, localCount = 0) {
|
|
6896
|
+
if (req.recoveryCommand) {
|
|
6897
|
+
return buildRecoveryCardLines(req);
|
|
6898
|
+
}
|
|
6558
6899
|
const argsStr = JSON.stringify(req.args ?? {}).replace(/\s+/g, " ");
|
|
6559
6900
|
const argsPreview = argsStr.length > 60 ? argsStr.slice(0, 60) + "\u2026" : argsStr;
|
|
6560
6901
|
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`;
|
|
6561
6902
|
const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
|
|
6562
6903
|
const lines = [
|
|
6563
6904
|
``,
|
|
6564
|
-
`${
|
|
6565
|
-
`${CYAN}\u2551${
|
|
6566
|
-
`${CYAN}\u2551${
|
|
6905
|
+
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
6906
|
+
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
6907
|
+
`${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
|
|
6567
6908
|
];
|
|
6568
6909
|
if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
|
|
6569
|
-
lines.push(`${CYAN}\u2551${
|
|
6910
|
+
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
6570
6911
|
}
|
|
6571
|
-
lines.push(`${CYAN}\u2551${
|
|
6912
|
+
lines.push(`${CYAN}\u2551${RESET2} Args: ${GRAY}${argsPreview}${RESET2}`);
|
|
6572
6913
|
if (localCount >= 2) {
|
|
6573
6914
|
lines.push(
|
|
6574
|
-
`${CYAN}\u2551${
|
|
6915
|
+
`${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
|
|
6575
6916
|
);
|
|
6576
6917
|
}
|
|
6577
6918
|
lines.push(
|
|
6578
|
-
`${CYAN}\u255A${
|
|
6919
|
+
`${CYAN}\u255A${RESET2}`,
|
|
6579
6920
|
``,
|
|
6580
|
-
` ${
|
|
6921
|
+
` ${BOLD2}${GREEN}[\u21B5/y]${RESET2} Allow ${BOLD2}${RED}[n]${RESET2} Deny ${BOLD2}${YELLOW}[a]${RESET2} Always Allow ${BOLD2}${CYAN}[t]${RESET2} Trust 30m`,
|
|
6581
6922
|
``
|
|
6582
6923
|
);
|
|
6583
6924
|
return lines;
|
|
6584
6925
|
}
|
|
6926
|
+
function buildRecoveryCardLines(req) {
|
|
6927
|
+
const argsObj = req.args;
|
|
6928
|
+
const command = typeof argsObj?.command === "string" ? argsObj.command : JSON.stringify(req.args ?? {}).replace(/\s+/g, " ").slice(0, 60);
|
|
6929
|
+
const ruleName = req.riskMetadata?.ruleName?.replace(/^Smart Rule:\s*/i, "") ?? "policy rule";
|
|
6930
|
+
const recoveryCommand = req.recoveryCommand;
|
|
6931
|
+
const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET2}`] : [
|
|
6932
|
+
` ${BOLD2}${GREEN}[1]${RESET2} Allow anyway ${GRAY}(override policy)${RESET2}`,
|
|
6933
|
+
` ${BOLD2}${YELLOW}[2]${RESET2} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
|
|
6934
|
+
` ${BOLD2}${RED}[3]${RESET2} Deny & stop ${GRAY}(hard block)${RESET2}`,
|
|
6935
|
+
``,
|
|
6936
|
+
` ${GRAY}[Timeout: auto-deny]${RESET2}`,
|
|
6937
|
+
` Select [1-3]: `
|
|
6938
|
+
];
|
|
6939
|
+
return [
|
|
6940
|
+
``,
|
|
6941
|
+
`${BOLD2}${CYAN}${DIVIDER}${RESET2}`,
|
|
6942
|
+
`\u{1F6E1}\uFE0F ${BOLD2}NODE9 STATE GUARD:${RESET2} '${BOLD2}${command}${RESET2}'`,
|
|
6943
|
+
`${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET2}`,
|
|
6944
|
+
`${CYAN}${DIVIDER}${RESET2}`,
|
|
6945
|
+
...!req.viewOnly ? [`${BOLD2}What would you like to do?${RESET2}`, ``] : [],
|
|
6946
|
+
...interactiveLines,
|
|
6947
|
+
`${CYAN}${DIVIDER}${RESET2}`,
|
|
6948
|
+
``
|
|
6949
|
+
];
|
|
6950
|
+
}
|
|
6585
6951
|
async function startTail(options = {}) {
|
|
6586
6952
|
const port = await ensureDaemon();
|
|
6587
6953
|
if (options.clear) {
|
|
@@ -6608,7 +6974,7 @@ async function startTail(options = {}) {
|
|
|
6608
6974
|
req2.end();
|
|
6609
6975
|
});
|
|
6610
6976
|
if (result.ok) {
|
|
6611
|
-
console.log(
|
|
6977
|
+
console.log(import_chalk17.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
6612
6978
|
} else if (result.code === "ECONNREFUSED") {
|
|
6613
6979
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
6614
6980
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -6627,10 +6993,10 @@ async function startTail(options = {}) {
|
|
|
6627
6993
|
let cancelActiveCard = null;
|
|
6628
6994
|
const localAllowCounts = /* @__PURE__ */ new Map();
|
|
6629
6995
|
const canApprove = process.stdout.isTTY && process.stdin.isTTY;
|
|
6630
|
-
if (canApprove)
|
|
6996
|
+
if (canApprove) import_readline4.default.emitKeypressEvents(process.stdin);
|
|
6631
6997
|
function clearCard() {
|
|
6632
6998
|
if (cardLineCount > 0) {
|
|
6633
|
-
|
|
6999
|
+
import_readline4.default.moveCursor(process.stdout, 0, -cardLineCount);
|
|
6634
7000
|
process.stdout.write(ERASE_DOWN);
|
|
6635
7001
|
cardLineCount = 0;
|
|
6636
7002
|
}
|
|
@@ -6680,14 +7046,14 @@ async function startTail(options = {}) {
|
|
|
6680
7046
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
6681
7047
|
)
|
|
6682
7048
|
);
|
|
6683
|
-
const decisionStamp = action === "always-allow" ?
|
|
6684
|
-
stampedLines.push(` ${
|
|
7049
|
+
const decisionStamp = action === "always-allow" ? import_chalk17.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk17.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk17.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk17.default.yellow("\u21A9 REDIRECT AI") : import_chalk17.default.red("\u2717 DENIED");
|
|
7050
|
+
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
6685
7051
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
6686
7052
|
process.stdout.write(SHOW_CURSOR);
|
|
6687
7053
|
cardLineCount = 0;
|
|
6688
7054
|
if (action === "allow" || action === "always-allow" || action === "trust") {
|
|
6689
7055
|
localAllowCounts.set(req2.toolName, (localAllowCounts.get(req2.toolName) ?? 0) + 1);
|
|
6690
|
-
} else if (action === "deny") {
|
|
7056
|
+
} else if (action === "deny" || action === "redirect") {
|
|
6691
7057
|
localAllowCounts.delete(req2.toolName);
|
|
6692
7058
|
}
|
|
6693
7059
|
let httpDecision;
|
|
@@ -6698,13 +7064,18 @@ async function startTail(options = {}) {
|
|
|
6698
7064
|
} else if (action === "trust") {
|
|
6699
7065
|
httpDecision = "trust";
|
|
6700
7066
|
httpOpts = { trustDuration: "30m" };
|
|
7067
|
+
} else if (action === "redirect") {
|
|
7068
|
+
httpDecision = "deny";
|
|
7069
|
+
const recoveryCommand = req2.recoveryCommand ?? "the required pre-condition";
|
|
7070
|
+
const redirectReason = `USER INTERVENTION: I am blocking this ${req2.toolName} because the required pre-condition has not been met. Please execute \`${recoveryCommand}\`. If it passes, you are then authorized to run \`${req2.toolName}\`.`;
|
|
7071
|
+
httpOpts = { reason: redirectReason, source: "terminal-redirect" };
|
|
6701
7072
|
} else {
|
|
6702
7073
|
httpDecision = action;
|
|
6703
7074
|
}
|
|
6704
7075
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err) => {
|
|
6705
7076
|
try {
|
|
6706
7077
|
import_fs24.default.appendFileSync(
|
|
6707
|
-
|
|
7078
|
+
import_path27.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log"),
|
|
6708
7079
|
`[tail] POST /decision failed: ${String(err)}
|
|
6709
7080
|
`
|
|
6710
7081
|
);
|
|
@@ -6726,8 +7097,8 @@ async function startTail(options = {}) {
|
|
|
6726
7097
|
);
|
|
6727
7098
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
6728
7099
|
if (externalDecision) {
|
|
6729
|
-
const source = externalDecision === "allow" ?
|
|
6730
|
-
stampedLines.push(` ${
|
|
7100
|
+
const source = externalDecision === "allow" ? import_chalk17.default.green("\u2713 ALLOWED") : import_chalk17.default.red("\u2717 DENIED");
|
|
7101
|
+
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
6731
7102
|
}
|
|
6732
7103
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
6733
7104
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -6736,17 +7107,34 @@ async function startTail(options = {}) {
|
|
|
6736
7107
|
cardActive = false;
|
|
6737
7108
|
showNextCard();
|
|
6738
7109
|
};
|
|
7110
|
+
if (req2.viewOnly) {
|
|
7111
|
+
process.stdin.resume();
|
|
7112
|
+
onKeypress = () => {
|
|
7113
|
+
};
|
|
7114
|
+
process.stdin.on("keypress", onKeypress);
|
|
7115
|
+
return;
|
|
7116
|
+
}
|
|
6739
7117
|
process.stdin.resume();
|
|
6740
7118
|
onKeypress = (_str, key) => {
|
|
6741
7119
|
const name = key?.name ?? "";
|
|
6742
|
-
if (
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
7120
|
+
if (req2.recoveryCommand) {
|
|
7121
|
+
if (name === "1") {
|
|
7122
|
+
settle("allow");
|
|
7123
|
+
} else if (name === "2") {
|
|
7124
|
+
settle("redirect");
|
|
7125
|
+
} else if (name === "3" || key?.ctrl && name === "c") {
|
|
7126
|
+
settle("deny");
|
|
7127
|
+
}
|
|
7128
|
+
} else {
|
|
7129
|
+
if (name === "y" || name === "return") {
|
|
7130
|
+
settle("allow");
|
|
7131
|
+
} else if (name === "n" || name === "d" || key?.ctrl && name === "c") {
|
|
7132
|
+
settle("deny");
|
|
7133
|
+
} else if (name === "a") {
|
|
7134
|
+
settle("always-allow");
|
|
7135
|
+
} else if (name === "t") {
|
|
7136
|
+
settle("trust");
|
|
7137
|
+
}
|
|
6750
7138
|
}
|
|
6751
7139
|
};
|
|
6752
7140
|
process.stdin.on("keypress", onKeypress);
|
|
@@ -6768,41 +7156,41 @@ async function startTail(options = {}) {
|
|
|
6768
7156
|
}
|
|
6769
7157
|
} catch {
|
|
6770
7158
|
}
|
|
6771
|
-
console.log(
|
|
6772
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
7159
|
+
console.log(import_chalk17.default.cyan.bold(`
|
|
7160
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk17.default.dim(`\u2192 ${dashboardUrl}`));
|
|
6773
7161
|
if (canApprove) {
|
|
6774
7162
|
console.log(
|
|
6775
|
-
|
|
7163
|
+
import_chalk17.default.dim("Interactive approvals: [\u21B5/y] Allow [n] Deny [a] Always Allow [t] Trust 30m")
|
|
6776
7164
|
);
|
|
6777
7165
|
}
|
|
6778
7166
|
if (options.history) {
|
|
6779
|
-
console.log(
|
|
7167
|
+
console.log(import_chalk17.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
6780
7168
|
} else {
|
|
6781
7169
|
console.log(
|
|
6782
|
-
|
|
7170
|
+
import_chalk17.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
|
|
6783
7171
|
);
|
|
6784
7172
|
}
|
|
6785
7173
|
process.on("SIGINT", () => {
|
|
6786
7174
|
clearCard();
|
|
6787
7175
|
process.stdout.write(SHOW_CURSOR);
|
|
6788
7176
|
if (process.stdout.isTTY) {
|
|
6789
|
-
|
|
6790
|
-
|
|
7177
|
+
import_readline4.default.clearLine(process.stdout, 0);
|
|
7178
|
+
import_readline4.default.cursorTo(process.stdout, 0);
|
|
6791
7179
|
}
|
|
6792
|
-
console.log(
|
|
7180
|
+
console.log(import_chalk17.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
6793
7181
|
process.exit(0);
|
|
6794
7182
|
});
|
|
6795
7183
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
6796
7184
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
6797
7185
|
if (res.statusCode !== 200) {
|
|
6798
|
-
console.error(
|
|
7186
|
+
console.error(import_chalk17.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
6799
7187
|
process.exit(1);
|
|
6800
7188
|
}
|
|
6801
7189
|
let currentEvent = "";
|
|
6802
7190
|
let currentData = "";
|
|
6803
7191
|
res.on("error", () => {
|
|
6804
7192
|
});
|
|
6805
|
-
const rl =
|
|
7193
|
+
const rl = import_readline4.default.createInterface({ input: res, crlfDelay: Infinity });
|
|
6806
7194
|
rl.on("error", () => {
|
|
6807
7195
|
});
|
|
6808
7196
|
rl.on("line", (line) => {
|
|
@@ -6822,10 +7210,10 @@ async function startTail(options = {}) {
|
|
|
6822
7210
|
clearCard();
|
|
6823
7211
|
process.stdout.write(SHOW_CURSOR);
|
|
6824
7212
|
if (process.stdout.isTTY) {
|
|
6825
|
-
|
|
6826
|
-
|
|
7213
|
+
import_readline4.default.clearLine(process.stdout, 0);
|
|
7214
|
+
import_readline4.default.cursorTo(process.stdout, 0);
|
|
6827
7215
|
}
|
|
6828
|
-
console.log(
|
|
7216
|
+
console.log(import_chalk17.default.red("\n\u274C Daemon disconnected."));
|
|
6829
7217
|
process.exit(1);
|
|
6830
7218
|
});
|
|
6831
7219
|
});
|
|
@@ -6911,26 +7299,26 @@ async function startTail(options = {}) {
|
|
|
6911
7299
|
}
|
|
6912
7300
|
req.on("error", (err) => {
|
|
6913
7301
|
const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
|
|
6914
|
-
console.error(
|
|
7302
|
+
console.error(import_chalk17.default.red(`
|
|
6915
7303
|
\u274C ${msg}`));
|
|
6916
7304
|
process.exit(1);
|
|
6917
7305
|
});
|
|
6918
7306
|
}
|
|
6919
|
-
var import_http2,
|
|
7307
|
+
var import_http2, import_chalk17, import_fs24, import_os20, import_path27, import_readline4, import_child_process13, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
|
|
6920
7308
|
var init_tail = __esm({
|
|
6921
7309
|
"src/tui/tail.ts"() {
|
|
6922
7310
|
"use strict";
|
|
6923
7311
|
import_http2 = __toESM(require("http"));
|
|
6924
|
-
|
|
7312
|
+
import_chalk17 = __toESM(require("chalk"));
|
|
6925
7313
|
import_fs24 = __toESM(require("fs"));
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
7314
|
+
import_os20 = __toESM(require("os"));
|
|
7315
|
+
import_path27 = __toESM(require("path"));
|
|
7316
|
+
import_readline4 = __toESM(require("readline"));
|
|
6929
7317
|
import_child_process13 = require("child_process");
|
|
6930
7318
|
init_daemon2();
|
|
6931
7319
|
init_daemon();
|
|
6932
7320
|
init_core();
|
|
6933
|
-
PID_FILE =
|
|
7321
|
+
PID_FILE = import_path27.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
|
|
6934
7322
|
ICONS = {
|
|
6935
7323
|
bash: "\u{1F4BB}",
|
|
6936
7324
|
shell: "\u{1F4BB}",
|
|
@@ -6948,8 +7336,8 @@ var init_tail = __esm({
|
|
|
6948
7336
|
delete: "\u{1F5D1}\uFE0F",
|
|
6949
7337
|
web: "\u{1F310}"
|
|
6950
7338
|
};
|
|
6951
|
-
|
|
6952
|
-
|
|
7339
|
+
RESET2 = "\x1B[0m";
|
|
7340
|
+
BOLD2 = "\x1B[1m";
|
|
6953
7341
|
RED = "\x1B[31m";
|
|
6954
7342
|
YELLOW = "\x1B[33m";
|
|
6955
7343
|
CYAN = "\x1B[36m";
|
|
@@ -6958,6 +7346,326 @@ var init_tail = __esm({
|
|
|
6958
7346
|
HIDE_CURSOR = "\x1B[?25l";
|
|
6959
7347
|
SHOW_CURSOR = "\x1B[?25h";
|
|
6960
7348
|
ERASE_DOWN = "\x1B[J";
|
|
7349
|
+
DIVIDER = "\u2500".repeat(60);
|
|
7350
|
+
}
|
|
7351
|
+
});
|
|
7352
|
+
|
|
7353
|
+
// src/cli/hud.ts
|
|
7354
|
+
var hud_exports = {};
|
|
7355
|
+
__export(hud_exports, {
|
|
7356
|
+
countConfigs: () => countConfigs,
|
|
7357
|
+
main: () => main,
|
|
7358
|
+
renderEnvironmentLine: () => renderEnvironmentLine
|
|
7359
|
+
});
|
|
7360
|
+
async function readStdin() {
|
|
7361
|
+
const chunks = [];
|
|
7362
|
+
for await (const chunk of process.stdin) {
|
|
7363
|
+
chunks.push(chunk);
|
|
7364
|
+
}
|
|
7365
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
7366
|
+
if (!raw) return {};
|
|
7367
|
+
try {
|
|
7368
|
+
return JSON.parse(raw);
|
|
7369
|
+
} catch {
|
|
7370
|
+
return {};
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
7373
|
+
function queryDaemon() {
|
|
7374
|
+
return new Promise((resolve) => {
|
|
7375
|
+
const timeout = setTimeout(() => resolve(null), 50);
|
|
7376
|
+
try {
|
|
7377
|
+
const req = import_http3.default.get(
|
|
7378
|
+
`http://${DAEMON_HOST}:${DAEMON_PORT}/status`,
|
|
7379
|
+
{ timeout: 50 },
|
|
7380
|
+
(res) => {
|
|
7381
|
+
const chunks = [];
|
|
7382
|
+
res.on("data", (c) => chunks.push(c));
|
|
7383
|
+
res.on("end", () => {
|
|
7384
|
+
clearTimeout(timeout);
|
|
7385
|
+
try {
|
|
7386
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
7387
|
+
} catch {
|
|
7388
|
+
resolve(null);
|
|
7389
|
+
}
|
|
7390
|
+
});
|
|
7391
|
+
}
|
|
7392
|
+
);
|
|
7393
|
+
req.on("error", () => {
|
|
7394
|
+
clearTimeout(timeout);
|
|
7395
|
+
resolve(null);
|
|
7396
|
+
});
|
|
7397
|
+
req.on("timeout", () => {
|
|
7398
|
+
clearTimeout(timeout);
|
|
7399
|
+
req.destroy();
|
|
7400
|
+
resolve(null);
|
|
7401
|
+
});
|
|
7402
|
+
} catch {
|
|
7403
|
+
clearTimeout(timeout);
|
|
7404
|
+
resolve(null);
|
|
7405
|
+
}
|
|
7406
|
+
});
|
|
7407
|
+
}
|
|
7408
|
+
function dim(s) {
|
|
7409
|
+
return `${DIM}${s}${RESET3}`;
|
|
7410
|
+
}
|
|
7411
|
+
function bold(s) {
|
|
7412
|
+
return `${BOLD3}${s}${RESET3}`;
|
|
7413
|
+
}
|
|
7414
|
+
function color(c, s) {
|
|
7415
|
+
return `${c}${s}${RESET3}`;
|
|
7416
|
+
}
|
|
7417
|
+
function progressBar(pct, warnAt = 70, critAt = 85) {
|
|
7418
|
+
const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
|
|
7419
|
+
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
7420
|
+
const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
|
|
7421
|
+
return `${c}${bar}${RESET3}`;
|
|
7422
|
+
}
|
|
7423
|
+
function formatTimeLeft(resetsAt) {
|
|
7424
|
+
if (!resetsAt) return "";
|
|
7425
|
+
const ms = new Date(resetsAt).getTime() - Date.now();
|
|
7426
|
+
if (ms <= 0) return "";
|
|
7427
|
+
const totalMin = Math.ceil(ms / 6e4);
|
|
7428
|
+
const h = Math.floor(totalMin / 60);
|
|
7429
|
+
const m = totalMin % 60;
|
|
7430
|
+
if (h > 0) return ` (${h}h ${m}m left)`;
|
|
7431
|
+
return ` (${m}m left)`;
|
|
7432
|
+
}
|
|
7433
|
+
function safeReadJson(filePath) {
|
|
7434
|
+
if (!import_fs25.default.existsSync(filePath)) return null;
|
|
7435
|
+
try {
|
|
7436
|
+
return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
|
|
7437
|
+
} catch {
|
|
7438
|
+
return null;
|
|
7439
|
+
}
|
|
7440
|
+
}
|
|
7441
|
+
function getMcpServerNames(filePath) {
|
|
7442
|
+
const cfg = safeReadJson(filePath);
|
|
7443
|
+
if (!cfg || typeof cfg.mcpServers !== "object" || cfg.mcpServers === null) return /* @__PURE__ */ new Set();
|
|
7444
|
+
return new Set(Object.keys(cfg.mcpServers));
|
|
7445
|
+
}
|
|
7446
|
+
function getDisabledMcpServers(filePath, key) {
|
|
7447
|
+
const cfg = safeReadJson(filePath);
|
|
7448
|
+
if (!cfg || !Array.isArray(cfg[key])) return /* @__PURE__ */ new Set();
|
|
7449
|
+
return new Set(cfg[key].filter((s) => typeof s === "string"));
|
|
7450
|
+
}
|
|
7451
|
+
function countHooksInFile(filePath) {
|
|
7452
|
+
const cfg = safeReadJson(filePath);
|
|
7453
|
+
if (!cfg || typeof cfg.hooks !== "object" || cfg.hooks === null) return 0;
|
|
7454
|
+
return Object.keys(cfg.hooks).length;
|
|
7455
|
+
}
|
|
7456
|
+
function countRulesInDir(rulesDir) {
|
|
7457
|
+
if (!import_fs25.default.existsSync(rulesDir)) return 0;
|
|
7458
|
+
let count = 0;
|
|
7459
|
+
try {
|
|
7460
|
+
for (const entry of import_fs25.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7461
|
+
if (entry.isDirectory()) {
|
|
7462
|
+
count += countRulesInDir(import_path28.default.join(rulesDir, entry.name));
|
|
7463
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7464
|
+
count++;
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
} catch {
|
|
7468
|
+
}
|
|
7469
|
+
return count;
|
|
7470
|
+
}
|
|
7471
|
+
function isSamePath(a, b) {
|
|
7472
|
+
try {
|
|
7473
|
+
return import_path28.default.resolve(a) === import_path28.default.resolve(b);
|
|
7474
|
+
} catch {
|
|
7475
|
+
return false;
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7478
|
+
function countConfigs(cwd) {
|
|
7479
|
+
const homeDir2 = import_os21.default.homedir();
|
|
7480
|
+
const claudeDir = import_path28.default.join(homeDir2, ".claude");
|
|
7481
|
+
let claudeMdCount = 0;
|
|
7482
|
+
let rulesCount = 0;
|
|
7483
|
+
let hooksCount = 0;
|
|
7484
|
+
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7485
|
+
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7486
|
+
if (import_fs25.default.existsSync(import_path28.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7487
|
+
rulesCount += countRulesInDir(import_path28.default.join(claudeDir, "rules"));
|
|
7488
|
+
const userSettings = import_path28.default.join(claudeDir, "settings.json");
|
|
7489
|
+
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7490
|
+
hooksCount += countHooksInFile(userSettings);
|
|
7491
|
+
const userClaudeJson = import_path28.default.join(homeDir2, ".claude.json");
|
|
7492
|
+
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7493
|
+
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7494
|
+
userMcpServers.delete(name);
|
|
7495
|
+
}
|
|
7496
|
+
if (cwd) {
|
|
7497
|
+
if (import_fs25.default.existsSync(import_path28.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7498
|
+
if (import_fs25.default.existsSync(import_path28.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7499
|
+
const projectClaudeDir = import_path28.default.join(cwd, ".claude");
|
|
7500
|
+
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7501
|
+
if (!overlapsUserScope) {
|
|
7502
|
+
if (import_fs25.default.existsSync(import_path28.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7503
|
+
rulesCount += countRulesInDir(import_path28.default.join(projectClaudeDir, "rules"));
|
|
7504
|
+
const projSettings = import_path28.default.join(projectClaudeDir, "settings.json");
|
|
7505
|
+
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7506
|
+
hooksCount += countHooksInFile(projSettings);
|
|
7507
|
+
}
|
|
7508
|
+
if (import_fs25.default.existsSync(import_path28.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7509
|
+
const localSettings = import_path28.default.join(projectClaudeDir, "settings.local.json");
|
|
7510
|
+
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7511
|
+
hooksCount += countHooksInFile(localSettings);
|
|
7512
|
+
const mcpJsonServers = getMcpServerNames(import_path28.default.join(cwd, ".mcp.json"));
|
|
7513
|
+
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7514
|
+
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7515
|
+
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
7516
|
+
}
|
|
7517
|
+
return {
|
|
7518
|
+
claudeMdCount,
|
|
7519
|
+
rulesCount,
|
|
7520
|
+
mcpCount: userMcpServers.size + projectMcpServers.size,
|
|
7521
|
+
hooksCount
|
|
7522
|
+
};
|
|
7523
|
+
}
|
|
7524
|
+
function renderEnvironmentLine(counts) {
|
|
7525
|
+
const { claudeMdCount, rulesCount, mcpCount, hooksCount } = counts;
|
|
7526
|
+
if (claudeMdCount === 0 && rulesCount === 0 && mcpCount === 0 && hooksCount === 0) return null;
|
|
7527
|
+
const parts = [
|
|
7528
|
+
`${claudeMdCount} CLAUDE.md`,
|
|
7529
|
+
`${rulesCount} rules`,
|
|
7530
|
+
`${mcpCount} MCPs`,
|
|
7531
|
+
`${hooksCount} hooks`
|
|
7532
|
+
];
|
|
7533
|
+
return color(DIM, parts.join(` ${dim("|")} `));
|
|
7534
|
+
}
|
|
7535
|
+
function renderOffline() {
|
|
7536
|
+
process.stdout.write(`${color(BLUE, "\u{1F6E1}")} ${bold("node9")} ${dim("|")} ${dim("offline")}
|
|
7537
|
+
`);
|
|
7538
|
+
}
|
|
7539
|
+
function renderSecurityLine(status) {
|
|
7540
|
+
const parts = [];
|
|
7541
|
+
parts.push(`${color(BLUE, "\u{1F6E1}")} ${bold("node9")}`);
|
|
7542
|
+
const modeColors = {
|
|
7543
|
+
standard: GREEN2,
|
|
7544
|
+
strict: RED2,
|
|
7545
|
+
observe: MAGENTA,
|
|
7546
|
+
audit: YELLOW2
|
|
7547
|
+
};
|
|
7548
|
+
const modeIcon = {
|
|
7549
|
+
standard: "",
|
|
7550
|
+
strict: "",
|
|
7551
|
+
observe: "\u{1F441} ",
|
|
7552
|
+
audit: ""
|
|
7553
|
+
};
|
|
7554
|
+
const mc = modeColors[status.mode] ?? WHITE;
|
|
7555
|
+
parts.push(`${dim("|")} ${color(mc, modeIcon[status.mode] ?? "")}${color(mc, status.mode)}`);
|
|
7556
|
+
if (status.mode === "observe") {
|
|
7557
|
+
parts.push(`${dim("|")} ${color(GREEN2, `\u2705 ${status.session.allowed} passed`)}`);
|
|
7558
|
+
if (status.session.wouldBlock > 0) {
|
|
7559
|
+
parts.push(color(YELLOW2, `\u26A0 ${status.session.wouldBlock} would-block`));
|
|
7560
|
+
}
|
|
7561
|
+
} else {
|
|
7562
|
+
parts.push(`${dim("|")} ${color(GREEN2, `\u2705 ${status.session.allowed} allowed`)}`);
|
|
7563
|
+
if (status.session.blocked > 0) {
|
|
7564
|
+
parts.push(color(RED2, `\u{1F6D1} ${status.session.blocked} blocked`));
|
|
7565
|
+
}
|
|
7566
|
+
if (status.session.dlpHits > 0) {
|
|
7567
|
+
parts.push(color(RED2, `\u{1F6A8} ${status.session.dlpHits} dlp`));
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
if (status.taintedCount > 0) {
|
|
7571
|
+
parts.push(color(YELLOW2, `\u{1F4A7} ${status.taintedCount} tainted`));
|
|
7572
|
+
}
|
|
7573
|
+
if (status.lastRuleHit) {
|
|
7574
|
+
const ruleName = status.lastRuleHit.replace(/^Smart Rule:\s*/i, "");
|
|
7575
|
+
parts.push(color(CYAN2, `\u26A1 ${ruleName}`));
|
|
7576
|
+
}
|
|
7577
|
+
return parts.join(" ");
|
|
7578
|
+
}
|
|
7579
|
+
function renderContextLine(stdin) {
|
|
7580
|
+
const cw = stdin.context_window;
|
|
7581
|
+
if (!cw) return null;
|
|
7582
|
+
const parts = [];
|
|
7583
|
+
const modelName = typeof stdin.model === "string" ? stdin.model : stdin.model?.display_name ?? "";
|
|
7584
|
+
if (modelName) {
|
|
7585
|
+
parts.push(color(CYAN2, modelName));
|
|
7586
|
+
}
|
|
7587
|
+
const usedPct = cw.used_percentage ?? (cw.current_usage && cw.context_window_size ? Math.round(
|
|
7588
|
+
((cw.current_usage.input_tokens ?? 0) + (cw.current_usage.output_tokens ?? 0)) / cw.context_window_size * 100
|
|
7589
|
+
) : null);
|
|
7590
|
+
if (usedPct !== null) {
|
|
7591
|
+
const bar = progressBar(usedPct);
|
|
7592
|
+
parts.push(`${dim("\u2502")} ctx ${bar} ${usedPct}%`);
|
|
7593
|
+
}
|
|
7594
|
+
const rl = stdin.rate_limits;
|
|
7595
|
+
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
7596
|
+
const pct = Math.round(rl.five_hour.used_percentage);
|
|
7597
|
+
const bar = progressBar(pct, 60, 80);
|
|
7598
|
+
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
7599
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
|
|
7600
|
+
}
|
|
7601
|
+
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
7602
|
+
const pct = Math.round(rl.seven_day.used_percentage);
|
|
7603
|
+
const bar = progressBar(pct, 60, 80);
|
|
7604
|
+
parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
|
|
7605
|
+
}
|
|
7606
|
+
if (parts.length === 0) return null;
|
|
7607
|
+
return parts.join(" ");
|
|
7608
|
+
}
|
|
7609
|
+
async function main() {
|
|
7610
|
+
try {
|
|
7611
|
+
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7612
|
+
if (!daemonStatus2) {
|
|
7613
|
+
renderOffline();
|
|
7614
|
+
return;
|
|
7615
|
+
}
|
|
7616
|
+
process.stdout.write(renderSecurityLine(daemonStatus2) + "\n");
|
|
7617
|
+
const ctxLine = renderContextLine(stdin);
|
|
7618
|
+
if (ctxLine) {
|
|
7619
|
+
process.stdout.write(ctxLine + "\n");
|
|
7620
|
+
}
|
|
7621
|
+
const showEnvCounts = (() => {
|
|
7622
|
+
try {
|
|
7623
|
+
const cwd = stdin.cwd ?? process.cwd();
|
|
7624
|
+
for (const configPath of [
|
|
7625
|
+
import_path28.default.join(cwd, "node9.config.json"),
|
|
7626
|
+
import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json")
|
|
7627
|
+
]) {
|
|
7628
|
+
if (!import_fs25.default.existsSync(configPath)) continue;
|
|
7629
|
+
const cfg = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
7630
|
+
const hud = cfg.settings?.hud;
|
|
7631
|
+
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7632
|
+
}
|
|
7633
|
+
} catch {
|
|
7634
|
+
}
|
|
7635
|
+
return true;
|
|
7636
|
+
})();
|
|
7637
|
+
if (showEnvCounts) {
|
|
7638
|
+
const envLine = renderEnvironmentLine(countConfigs(stdin.cwd));
|
|
7639
|
+
if (envLine) {
|
|
7640
|
+
process.stdout.write(envLine + "\n");
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
} catch {
|
|
7644
|
+
renderOffline();
|
|
7645
|
+
}
|
|
7646
|
+
}
|
|
7647
|
+
var import_fs25, import_path28, import_os21, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
|
|
7648
|
+
var init_hud = __esm({
|
|
7649
|
+
"src/cli/hud.ts"() {
|
|
7650
|
+
"use strict";
|
|
7651
|
+
import_fs25 = __toESM(require("fs"));
|
|
7652
|
+
import_path28 = __toESM(require("path"));
|
|
7653
|
+
import_os21 = __toESM(require("os"));
|
|
7654
|
+
import_http3 = __toESM(require("http"));
|
|
7655
|
+
init_daemon();
|
|
7656
|
+
RESET3 = "\x1B[0m";
|
|
7657
|
+
BOLD3 = "\x1B[1m";
|
|
7658
|
+
DIM = "\x1B[2m";
|
|
7659
|
+
RED2 = "\x1B[31m";
|
|
7660
|
+
GREEN2 = "\x1B[32m";
|
|
7661
|
+
YELLOW2 = "\x1B[33m";
|
|
7662
|
+
BLUE = "\x1B[34m";
|
|
7663
|
+
MAGENTA = "\x1B[35m";
|
|
7664
|
+
CYAN2 = "\x1B[36m";
|
|
7665
|
+
WHITE = "\x1B[37m";
|
|
7666
|
+
BAR_FILLED = "\u2588";
|
|
7667
|
+
BAR_EMPTY = "\u2591";
|
|
7668
|
+
BAR_WIDTH = 10;
|
|
6961
7669
|
}
|
|
6962
7670
|
});
|
|
6963
7671
|
|
|
@@ -6968,7 +7676,7 @@ init_core();
|
|
|
6968
7676
|
// src/setup.ts
|
|
6969
7677
|
var import_fs11 = __toESM(require("fs"));
|
|
6970
7678
|
var import_path14 = __toESM(require("path"));
|
|
6971
|
-
var
|
|
7679
|
+
var import_os10 = __toESM(require("os"));
|
|
6972
7680
|
var import_chalk = __toESM(require("chalk"));
|
|
6973
7681
|
var import_prompts = require("@inquirer/prompts");
|
|
6974
7682
|
function printDaemonTip() {
|
|
@@ -7002,7 +7710,7 @@ function isNode9Hook(cmd) {
|
|
|
7002
7710
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
7003
7711
|
}
|
|
7004
7712
|
function teardownClaude() {
|
|
7005
|
-
const homeDir2 =
|
|
7713
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7006
7714
|
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
7007
7715
|
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
7008
7716
|
let changed = false;
|
|
@@ -7052,7 +7760,7 @@ function teardownClaude() {
|
|
|
7052
7760
|
}
|
|
7053
7761
|
}
|
|
7054
7762
|
function teardownGemini() {
|
|
7055
|
-
const homeDir2 =
|
|
7763
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7056
7764
|
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
7057
7765
|
const settings = readJson(settingsPath);
|
|
7058
7766
|
if (!settings) {
|
|
@@ -7091,7 +7799,7 @@ function teardownGemini() {
|
|
|
7091
7799
|
}
|
|
7092
7800
|
}
|
|
7093
7801
|
function teardownCursor() {
|
|
7094
|
-
const homeDir2 =
|
|
7802
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7095
7803
|
const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7096
7804
|
const mcpConfig = readJson(mcpPath);
|
|
7097
7805
|
if (!mcpConfig?.mcpServers) {
|
|
@@ -7118,7 +7826,7 @@ function teardownCursor() {
|
|
|
7118
7826
|
}
|
|
7119
7827
|
}
|
|
7120
7828
|
async function setupClaude() {
|
|
7121
|
-
const homeDir2 =
|
|
7829
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7122
7830
|
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
7123
7831
|
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
7124
7832
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
@@ -7194,7 +7902,7 @@ async function setupClaude() {
|
|
|
7194
7902
|
}
|
|
7195
7903
|
}
|
|
7196
7904
|
async function setupGemini() {
|
|
7197
|
-
const homeDir2 =
|
|
7905
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7198
7906
|
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
7199
7907
|
const settings = readJson(settingsPath) ?? {};
|
|
7200
7908
|
const servers = settings.mcpServers ?? {};
|
|
@@ -7276,7 +7984,7 @@ async function setupGemini() {
|
|
|
7276
7984
|
printDaemonTip();
|
|
7277
7985
|
}
|
|
7278
7986
|
}
|
|
7279
|
-
function detectAgents(homeDir2 =
|
|
7987
|
+
function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
7280
7988
|
const exists = (p) => {
|
|
7281
7989
|
try {
|
|
7282
7990
|
return import_fs11.default.existsSync(p);
|
|
@@ -7296,7 +8004,7 @@ function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
|
7296
8004
|
};
|
|
7297
8005
|
}
|
|
7298
8006
|
async function setupCursor() {
|
|
7299
|
-
const homeDir2 =
|
|
8007
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7300
8008
|
const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7301
8009
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
7302
8010
|
const servers = mcpConfig.mcpServers ?? {};
|
|
@@ -7350,14 +8058,60 @@ async function setupCursor() {
|
|
|
7350
8058
|
printDaemonTip();
|
|
7351
8059
|
}
|
|
7352
8060
|
}
|
|
8061
|
+
function setupHud() {
|
|
8062
|
+
const homeDir2 = import_os10.default.homedir();
|
|
8063
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
8064
|
+
const settings = readJson(hooksPath) ?? {};
|
|
8065
|
+
const hudCommand = fullPathCommand("hud");
|
|
8066
|
+
const statusLineObj = { type: "command", command: hudCommand };
|
|
8067
|
+
const existing = settings.statusLine;
|
|
8068
|
+
const existingCommand = typeof existing === "object" ? existing?.command : existing;
|
|
8069
|
+
if (existingCommand === hudCommand) {
|
|
8070
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F node9 HUD is already configured in ~/.claude/settings.json"));
|
|
8071
|
+
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8072
|
+
return;
|
|
8073
|
+
}
|
|
8074
|
+
if (existing && existingCommand !== hudCommand) {
|
|
8075
|
+
console.log(
|
|
8076
|
+
import_chalk.default.yellow(
|
|
8077
|
+
` \u26A0\uFE0F statusLine is already set to: "${existingCommand}"
|
|
8078
|
+
Overwriting with node9 HUD.`
|
|
8079
|
+
)
|
|
8080
|
+
);
|
|
8081
|
+
}
|
|
8082
|
+
settings.statusLine = statusLineObj;
|
|
8083
|
+
writeJson(hooksPath, settings);
|
|
8084
|
+
console.log(import_chalk.default.green.bold("\u2705 node9 HUD added to Claude Code statusline"));
|
|
8085
|
+
console.log(import_chalk.default.gray(" Settings: ~/.claude/settings.json"));
|
|
8086
|
+
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8087
|
+
}
|
|
8088
|
+
function teardownHud() {
|
|
8089
|
+
const homeDir2 = import_os10.default.homedir();
|
|
8090
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
8091
|
+
const settings = readJson(hooksPath);
|
|
8092
|
+
if (!settings) {
|
|
8093
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
|
|
8094
|
+
return;
|
|
8095
|
+
}
|
|
8096
|
+
const existing = settings.statusLine;
|
|
8097
|
+
const existingCommand = typeof existing === "object" ? existing?.command : existing;
|
|
8098
|
+
if (!existingCommand || !String(existingCommand).includes("node9")) {
|
|
8099
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F node9 HUD not found in ~/.claude/settings.json"));
|
|
8100
|
+
return;
|
|
8101
|
+
}
|
|
8102
|
+
delete settings.statusLine;
|
|
8103
|
+
writeJson(hooksPath, settings);
|
|
8104
|
+
console.log(import_chalk.default.green(" \u2705 node9 HUD removed from ~/.claude/settings.json"));
|
|
8105
|
+
console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
|
|
8106
|
+
}
|
|
7353
8107
|
|
|
7354
8108
|
// src/cli.ts
|
|
7355
8109
|
init_daemon2();
|
|
7356
|
-
var
|
|
7357
|
-
var
|
|
7358
|
-
var
|
|
8110
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
8111
|
+
var import_fs26 = __toESM(require("fs"));
|
|
8112
|
+
var import_path29 = __toESM(require("path"));
|
|
7359
8113
|
var import_os22 = __toESM(require("os"));
|
|
7360
|
-
var
|
|
8114
|
+
var import_prompts2 = require("@inquirer/prompts");
|
|
7361
8115
|
|
|
7362
8116
|
// src/utils/duration.ts
|
|
7363
8117
|
function parseDuration(str) {
|
|
@@ -7387,7 +8141,7 @@ var import_execa2 = require("execa");
|
|
|
7387
8141
|
init_orchestrator();
|
|
7388
8142
|
|
|
7389
8143
|
// src/policy/negotiation.ts
|
|
7390
|
-
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason) {
|
|
8144
|
+
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand) {
|
|
7391
8145
|
if (isHumanDecision) {
|
|
7392
8146
|
return `NODE9: The human user rejected this action.
|
|
7393
8147
|
REASON: ${humanReason || "No specific reason provided."}
|
|
@@ -7443,10 +8197,11 @@ INSTRUCTION: Inform the user this action is pending approval. Wait for them to a
|
|
|
7443
8197
|
INSTRUCTION: Do NOT use "${rule}". Find a read-only or non-destructive alternative.
|
|
7444
8198
|
Do NOT attempt to bypass this rule.`;
|
|
7445
8199
|
}
|
|
8200
|
+
const recovery = recoveryCommand ? `
|
|
8201
|
+
REQUIRED ACTION: Run \`${recoveryCommand}\` first, then retry your original command.` : "\n- Pivot to a non-destructive or read-only alternative.";
|
|
7446
8202
|
return `NODE9: Action blocked by security policy [${blockedByLabel}].
|
|
7447
8203
|
INSTRUCTIONS:
|
|
7448
|
-
- Do NOT retry this exact command or attempt to bypass the rule
|
|
7449
|
-
- Pivot to a non-destructive or read-only alternative.
|
|
8204
|
+
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
7450
8205
|
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
7451
8206
|
}
|
|
7452
8207
|
|
|
@@ -7547,6 +8302,7 @@ function openBrowserLocal() {
|
|
|
7547
8302
|
}
|
|
7548
8303
|
}
|
|
7549
8304
|
async function autoStartDaemonAndWait() {
|
|
8305
|
+
if (process.env.NODE9_TESTING === "1") return false;
|
|
7550
8306
|
try {
|
|
7551
8307
|
const child = (0, import_child_process7.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
7552
8308
|
detached: true,
|
|
@@ -7579,7 +8335,7 @@ async function autoStartDaemonAndWait() {
|
|
|
7579
8335
|
var import_chalk5 = __toESM(require("chalk"));
|
|
7580
8336
|
var import_fs18 = __toESM(require("fs"));
|
|
7581
8337
|
var import_path20 = __toESM(require("path"));
|
|
7582
|
-
var
|
|
8338
|
+
var import_os14 = __toESM(require("os"));
|
|
7583
8339
|
init_orchestrator();
|
|
7584
8340
|
init_daemon();
|
|
7585
8341
|
init_config();
|
|
@@ -7590,9 +8346,9 @@ var import_child_process8 = require("child_process");
|
|
|
7590
8346
|
var import_crypto7 = __toESM(require("crypto"));
|
|
7591
8347
|
var import_fs17 = __toESM(require("fs"));
|
|
7592
8348
|
var import_path19 = __toESM(require("path"));
|
|
7593
|
-
var
|
|
7594
|
-
var SNAPSHOT_STACK_PATH = import_path19.default.join(
|
|
7595
|
-
var UNDO_LATEST_PATH = import_path19.default.join(
|
|
8349
|
+
var import_os13 = __toESM(require("os"));
|
|
8350
|
+
var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
|
|
8351
|
+
var UNDO_LATEST_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
|
|
7596
8352
|
var MAX_SNAPSHOTS = 10;
|
|
7597
8353
|
var GIT_TIMEOUT = 15e3;
|
|
7598
8354
|
function readStack() {
|
|
@@ -7608,16 +8364,33 @@ function writeStack(stack) {
|
|
|
7608
8364
|
if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
|
|
7609
8365
|
import_fs17.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
7610
8366
|
}
|
|
8367
|
+
function extractFilePath(args) {
|
|
8368
|
+
if (!args || typeof args !== "object") return null;
|
|
8369
|
+
const a = args;
|
|
8370
|
+
const fp = a.file_path ?? a.path ?? a.filename;
|
|
8371
|
+
return typeof fp === "string" ? fp : null;
|
|
8372
|
+
}
|
|
7611
8373
|
function buildArgsSummary(tool, args) {
|
|
8374
|
+
const filePath = extractFilePath(args);
|
|
8375
|
+
if (filePath) return filePath;
|
|
7612
8376
|
if (!args || typeof args !== "object") return "";
|
|
7613
8377
|
const a = args;
|
|
7614
|
-
const filePath = a.file_path ?? a.path ?? a.filename;
|
|
7615
|
-
if (typeof filePath === "string") return filePath;
|
|
7616
8378
|
const cmd = a.command ?? a.cmd;
|
|
7617
8379
|
if (typeof cmd === "string") return cmd.slice(0, 80);
|
|
7618
8380
|
const sql = a.sql ?? a.query;
|
|
7619
8381
|
if (typeof sql === "string") return sql.slice(0, 80);
|
|
7620
|
-
return
|
|
8382
|
+
return "";
|
|
8383
|
+
}
|
|
8384
|
+
function findProjectRoot(filePath) {
|
|
8385
|
+
let dir = import_path19.default.dirname(filePath);
|
|
8386
|
+
while (true) {
|
|
8387
|
+
if (import_fs17.default.existsSync(import_path19.default.join(dir, ".git")) || import_fs17.default.existsSync(import_path19.default.join(dir, "package.json"))) {
|
|
8388
|
+
return dir;
|
|
8389
|
+
}
|
|
8390
|
+
const parent = import_path19.default.dirname(dir);
|
|
8391
|
+
if (parent === dir) return process.cwd();
|
|
8392
|
+
dir = parent;
|
|
8393
|
+
}
|
|
7621
8394
|
}
|
|
7622
8395
|
function normalizeCwdForHash(cwd) {
|
|
7623
8396
|
let normalized;
|
|
@@ -7632,7 +8405,7 @@ function normalizeCwdForHash(cwd) {
|
|
|
7632
8405
|
}
|
|
7633
8406
|
function getShadowRepoDir(cwd) {
|
|
7634
8407
|
const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
7635
|
-
return import_path19.default.join(
|
|
8408
|
+
return import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
|
|
7636
8409
|
}
|
|
7637
8410
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
7638
8411
|
try {
|
|
@@ -7688,9 +8461,9 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7688
8461
|
} catch {
|
|
7689
8462
|
}
|
|
7690
8463
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
7691
|
-
if (init.status !== 0) {
|
|
7692
|
-
|
|
7693
|
-
|
|
8464
|
+
if (init.status !== 0 || init.error) {
|
|
8465
|
+
const reason = init.error ? init.error.message : init.stderr?.toString();
|
|
8466
|
+
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
7694
8467
|
return false;
|
|
7695
8468
|
}
|
|
7696
8469
|
const configFile = import_path19.default.join(shadowDir, "config");
|
|
@@ -7720,7 +8493,9 @@ function buildGitEnv(cwd) {
|
|
|
7720
8493
|
async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
|
|
7721
8494
|
let indexFile = null;
|
|
7722
8495
|
try {
|
|
7723
|
-
const
|
|
8496
|
+
const rawFilePath = extractFilePath(args);
|
|
8497
|
+
const absFilePath = rawFilePath && import_path19.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8498
|
+
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
7724
8499
|
const shadowDir = getShadowRepoDir(cwd);
|
|
7725
8500
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
7726
8501
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
@@ -7743,15 +8518,53 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
7743
8518
|
const commitHash = commitRes.stdout?.toString().trim();
|
|
7744
8519
|
if (!commitHash || commitRes.status !== 0) return null;
|
|
7745
8520
|
const stack = readStack();
|
|
8521
|
+
const prevEntry = [...stack].reverse().find((e) => e.cwd === cwd);
|
|
8522
|
+
let capturedFiles = [];
|
|
8523
|
+
let capturedDiff = null;
|
|
8524
|
+
if (prevEntry) {
|
|
8525
|
+
const filesRes = (0, import_child_process8.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
|
|
8526
|
+
env: shadowEnv,
|
|
8527
|
+
timeout: GIT_TIMEOUT
|
|
8528
|
+
});
|
|
8529
|
+
if (filesRes.status === 0) {
|
|
8530
|
+
capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8531
|
+
}
|
|
8532
|
+
const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
|
|
8533
|
+
env: shadowEnv,
|
|
8534
|
+
timeout: GIT_TIMEOUT
|
|
8535
|
+
});
|
|
8536
|
+
if (diffRes.status === 0) {
|
|
8537
|
+
capturedDiff = diffRes.stdout?.toString() || null;
|
|
8538
|
+
}
|
|
8539
|
+
} else {
|
|
8540
|
+
const filesRes = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
|
|
8541
|
+
env: shadowEnv,
|
|
8542
|
+
timeout: GIT_TIMEOUT
|
|
8543
|
+
});
|
|
8544
|
+
if (filesRes.status === 0) {
|
|
8545
|
+
capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8546
|
+
}
|
|
8547
|
+
capturedDiff = null;
|
|
8548
|
+
}
|
|
7746
8549
|
stack.push({
|
|
7747
8550
|
hash: commitHash,
|
|
7748
8551
|
tool,
|
|
7749
8552
|
argsSummary: buildArgsSummary(tool, args),
|
|
8553
|
+
files: capturedFiles,
|
|
8554
|
+
diff: capturedDiff,
|
|
7750
8555
|
cwd,
|
|
7751
8556
|
timestamp: Date.now()
|
|
7752
8557
|
});
|
|
7753
8558
|
const shouldGc = stack.length % 5 === 0;
|
|
7754
|
-
|
|
8559
|
+
let cwdCount = 0;
|
|
8560
|
+
let oldestCwdIdx = -1;
|
|
8561
|
+
for (let i = 0; i < stack.length; i++) {
|
|
8562
|
+
if (stack[i].cwd === cwd) {
|
|
8563
|
+
if (oldestCwdIdx === -1) oldestCwdIdx = i;
|
|
8564
|
+
cwdCount++;
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8567
|
+
if (cwdCount > MAX_SNAPSHOTS) stack.splice(oldestCwdIdx, 1);
|
|
7755
8568
|
writeStack(stack);
|
|
7756
8569
|
import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
7757
8570
|
if (shouldGc) {
|
|
@@ -7807,14 +8620,21 @@ function applyUndo(hash, cwd) {
|
|
|
7807
8620
|
env,
|
|
7808
8621
|
timeout: GIT_TIMEOUT
|
|
7809
8622
|
});
|
|
7810
|
-
if (restore.status !== 0)
|
|
8623
|
+
if (restore.status !== 0 || restore.error) {
|
|
8624
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
8625
|
+
const msg = restore.error ? restore.error.message : restore.stderr?.toString();
|
|
8626
|
+
console.error("[Node9] git restore failed:", msg);
|
|
8627
|
+
}
|
|
8628
|
+
return false;
|
|
8629
|
+
}
|
|
7811
8630
|
const lsTree = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
7812
8631
|
cwd: dir,
|
|
7813
8632
|
env,
|
|
7814
8633
|
timeout: GIT_TIMEOUT
|
|
7815
8634
|
});
|
|
7816
8635
|
if (lsTree.status !== 0) {
|
|
7817
|
-
|
|
8636
|
+
const errorMsg = lsTree.stderr?.toString() || "Unknown git error";
|
|
8637
|
+
process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}: ${errorMsg}
|
|
7818
8638
|
`);
|
|
7819
8639
|
return false;
|
|
7820
8640
|
}
|
|
@@ -7859,7 +8679,7 @@ function registerCheckCommand(program2) {
|
|
|
7859
8679
|
} catch (err) {
|
|
7860
8680
|
const tempConfig = getConfig();
|
|
7861
8681
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
7862
|
-
const logPath = import_path20.default.join(
|
|
8682
|
+
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
7863
8683
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7864
8684
|
import_fs18.default.appendFileSync(
|
|
7865
8685
|
logPath,
|
|
@@ -7872,7 +8692,7 @@ RAW: ${raw}
|
|
|
7872
8692
|
}
|
|
7873
8693
|
const config = getConfig(payload.cwd || void 0);
|
|
7874
8694
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
7875
|
-
const logPath = import_path20.default.join(
|
|
8695
|
+
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
7876
8696
|
if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
|
|
7877
8697
|
import_fs18.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
|
|
7878
8698
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
@@ -7900,6 +8720,8 @@ RAW: ${raw}
|
|
|
7900
8720
|
}
|
|
7901
8721
|
writeTty(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
|
|
7902
8722
|
if (result2?.changeHint) writeTty(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
|
|
8723
|
+
if (result2?.recoveryCommand)
|
|
8724
|
+
writeTty(import_chalk5.default.green(` \u{1F4A1} Run: ${result2.recoveryCommand}`));
|
|
7903
8725
|
writeTty("");
|
|
7904
8726
|
} catch {
|
|
7905
8727
|
} finally {
|
|
@@ -7912,7 +8734,8 @@ RAW: ${raw}
|
|
|
7912
8734
|
const aiFeedbackMessage = buildNegotiationMessage(
|
|
7913
8735
|
blockedByContext,
|
|
7914
8736
|
isHumanDecision,
|
|
7915
|
-
msg
|
|
8737
|
+
msg,
|
|
8738
|
+
result2?.recoveryCommand
|
|
7916
8739
|
);
|
|
7917
8740
|
process.stdout.write(
|
|
7918
8741
|
JSON.stringify({
|
|
@@ -7980,7 +8803,7 @@ RAW: ${raw}
|
|
|
7980
8803
|
});
|
|
7981
8804
|
} catch (err) {
|
|
7982
8805
|
if (process.env.NODE9_DEBUG === "1") {
|
|
7983
|
-
const logPath = import_path20.default.join(
|
|
8806
|
+
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
7984
8807
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7985
8808
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
7986
8809
|
`);
|
|
@@ -8018,7 +8841,7 @@ RAW: ${raw}
|
|
|
8018
8841
|
// src/cli/commands/log.ts
|
|
8019
8842
|
var import_fs19 = __toESM(require("fs"));
|
|
8020
8843
|
var import_path21 = __toESM(require("path"));
|
|
8021
|
-
var
|
|
8844
|
+
var import_os15 = __toESM(require("os"));
|
|
8022
8845
|
init_audit();
|
|
8023
8846
|
init_config();
|
|
8024
8847
|
init_policy();
|
|
@@ -8062,6 +8885,20 @@ function containsShellMetachar(token) {
|
|
|
8062
8885
|
}
|
|
8063
8886
|
|
|
8064
8887
|
// src/cli/commands/log.ts
|
|
8888
|
+
var TEST_COMMAND_RE = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
8889
|
+
function detectTestResult(command, output) {
|
|
8890
|
+
if (!TEST_COMMAND_RE.test(command)) return null;
|
|
8891
|
+
const out = output.toLowerCase();
|
|
8892
|
+
if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
|
|
8893
|
+
out
|
|
8894
|
+
) && !/\b(fail|error|failed)\b/.test(out)) {
|
|
8895
|
+
return "pass";
|
|
8896
|
+
}
|
|
8897
|
+
if (/\b(tests?\s+failed|failing|failed|error|assertion\s+error|\d+\s+failed)\b/i.test(out)) {
|
|
8898
|
+
return "fail";
|
|
8899
|
+
}
|
|
8900
|
+
return null;
|
|
8901
|
+
}
|
|
8065
8902
|
function sanitize3(value) {
|
|
8066
8903
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
8067
8904
|
}
|
|
@@ -8080,7 +8917,7 @@ function registerLogCommand(program2) {
|
|
|
8080
8917
|
decision: "allowed",
|
|
8081
8918
|
source: "post-hook"
|
|
8082
8919
|
};
|
|
8083
|
-
const logPath = import_path21.default.join(
|
|
8920
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "audit.log");
|
|
8084
8921
|
if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
|
|
8085
8922
|
import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
|
|
8086
8923
|
import_fs19.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
@@ -8093,6 +8930,21 @@ function registerLogCommand(program2) {
|
|
|
8093
8930
|
}
|
|
8094
8931
|
}
|
|
8095
8932
|
}
|
|
8933
|
+
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
8934
|
+
const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
8935
|
+
const output = payload.tool_response?.output ?? "";
|
|
8936
|
+
if (bashCommand && output) {
|
|
8937
|
+
const testResult = detectTestResult(bashCommand, output);
|
|
8938
|
+
if (testResult) {
|
|
8939
|
+
await notifyActivitySocket({
|
|
8940
|
+
id: "test-result",
|
|
8941
|
+
ts: Date.now(),
|
|
8942
|
+
tool,
|
|
8943
|
+
status: testResult === "pass" ? "test_pass" : "test_fail"
|
|
8944
|
+
});
|
|
8945
|
+
}
|
|
8946
|
+
}
|
|
8947
|
+
}
|
|
8096
8948
|
const safeCwd = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
8097
8949
|
const config = getConfig(safeCwd);
|
|
8098
8950
|
if (shouldSnapshot(tool, {}, config)) {
|
|
@@ -8102,7 +8954,7 @@ function registerLogCommand(program2) {
|
|
|
8102
8954
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8103
8955
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
8104
8956
|
`);
|
|
8105
|
-
const debugPath = import_path21.default.join(
|
|
8957
|
+
const debugPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
8106
8958
|
try {
|
|
8107
8959
|
import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
8108
8960
|
`);
|
|
@@ -8410,12 +9262,12 @@ function registerConfigShowCommand(program2) {
|
|
|
8410
9262
|
var import_chalk7 = __toESM(require("chalk"));
|
|
8411
9263
|
var import_fs20 = __toESM(require("fs"));
|
|
8412
9264
|
var import_path22 = __toESM(require("path"));
|
|
8413
|
-
var
|
|
9265
|
+
var import_os16 = __toESM(require("os"));
|
|
8414
9266
|
var import_child_process9 = require("child_process");
|
|
8415
9267
|
init_daemon();
|
|
8416
9268
|
function registerDoctorCommand(program2, version2) {
|
|
8417
9269
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
8418
|
-
const homeDir2 =
|
|
9270
|
+
const homeDir2 = import_os16.default.homedir();
|
|
8419
9271
|
let failures = 0;
|
|
8420
9272
|
function pass(msg) {
|
|
8421
9273
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -8578,7 +9430,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8578
9430
|
var import_chalk8 = __toESM(require("chalk"));
|
|
8579
9431
|
var import_fs21 = __toESM(require("fs"));
|
|
8580
9432
|
var import_path23 = __toESM(require("path"));
|
|
8581
|
-
var
|
|
9433
|
+
var import_os17 = __toESM(require("os"));
|
|
8582
9434
|
function formatRelativeTime(timestamp) {
|
|
8583
9435
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
8584
9436
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -8591,7 +9443,7 @@ function formatRelativeTime(timestamp) {
|
|
|
8591
9443
|
}
|
|
8592
9444
|
function registerAuditCommand(program2) {
|
|
8593
9445
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
8594
|
-
const logPath = import_path23.default.join(
|
|
9446
|
+
const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
|
|
8595
9447
|
if (!import_fs21.default.existsSync(logPath)) {
|
|
8596
9448
|
console.log(
|
|
8597
9449
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
@@ -8718,7 +9570,7 @@ function registerDaemonCommand(program2) {
|
|
|
8718
9570
|
var import_chalk10 = __toESM(require("chalk"));
|
|
8719
9571
|
var import_fs22 = __toESM(require("fs"));
|
|
8720
9572
|
var import_path24 = __toESM(require("path"));
|
|
8721
|
-
var
|
|
9573
|
+
var import_os18 = __toESM(require("os"));
|
|
8722
9574
|
init_core();
|
|
8723
9575
|
init_daemon();
|
|
8724
9576
|
function readJson2(filePath) {
|
|
@@ -8789,7 +9641,7 @@ function registerStatusCommand(program2) {
|
|
|
8789
9641
|
const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
|
|
8790
9642
|
console.log(` Mode: ${modeLabel}`);
|
|
8791
9643
|
const projectConfig = import_path24.default.join(process.cwd(), "node9.config.json");
|
|
8792
|
-
const globalConfig = import_path24.default.join(
|
|
9644
|
+
const globalConfig = import_path24.default.join(import_os18.default.homedir(), ".node9", "config.json");
|
|
8793
9645
|
console.log(
|
|
8794
9646
|
` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
|
|
8795
9647
|
);
|
|
@@ -8801,7 +9653,7 @@ function registerStatusCommand(program2) {
|
|
|
8801
9653
|
` Sandbox: ${import_chalk10.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
8802
9654
|
);
|
|
8803
9655
|
}
|
|
8804
|
-
const homeDir2 =
|
|
9656
|
+
const homeDir2 = import_os18.default.homedir();
|
|
8805
9657
|
const claudeSettings = readJson2(
|
|
8806
9658
|
import_path24.default.join(homeDir2, ".claude", "settings.json")
|
|
8807
9659
|
);
|
|
@@ -8870,12 +9722,12 @@ function registerStatusCommand(program2) {
|
|
|
8870
9722
|
var import_chalk11 = __toESM(require("chalk"));
|
|
8871
9723
|
var import_fs23 = __toESM(require("fs"));
|
|
8872
9724
|
var import_path25 = __toESM(require("path"));
|
|
8873
|
-
var
|
|
9725
|
+
var import_os19 = __toESM(require("os"));
|
|
8874
9726
|
init_core();
|
|
8875
9727
|
function registerInitCommand(program2) {
|
|
8876
9728
|
program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
|
|
8877
9729
|
console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
8878
|
-
const configPath = import_path25.default.join(
|
|
9730
|
+
const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
|
|
8879
9731
|
if (import_fs23.default.existsSync(configPath) && !options.force) {
|
|
8880
9732
|
console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
8881
9733
|
} else {
|
|
@@ -8916,106 +9768,305 @@ function registerInitCommand(program2) {
|
|
|
8916
9768
|
else if (agent === "cursor") await setupCursor();
|
|
8917
9769
|
console.log("");
|
|
8918
9770
|
}
|
|
9771
|
+
if (detected.claude) {
|
|
9772
|
+
setupHud();
|
|
9773
|
+
console.log(import_chalk11.default.green("\u2705 node9 HUD added to Claude Code statusline"));
|
|
9774
|
+
console.log(import_chalk11.default.gray(" Restart Claude Code to activate the security statusline."));
|
|
9775
|
+
console.log("");
|
|
9776
|
+
}
|
|
8919
9777
|
console.log(import_chalk11.default.green.bold("\u{1F6E1}\uFE0F Node9 is ready!"));
|
|
8920
9778
|
console.log(import_chalk11.default.gray(" Run: node9 daemon start"));
|
|
8921
9779
|
});
|
|
8922
9780
|
}
|
|
8923
9781
|
|
|
8924
9782
|
// src/cli/commands/undo.ts
|
|
9783
|
+
var import_path26 = __toESM(require("path"));
|
|
9784
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
9785
|
+
|
|
9786
|
+
// src/tui/undo-navigator.ts
|
|
9787
|
+
var import_readline2 = __toESM(require("readline"));
|
|
8925
9788
|
var import_chalk12 = __toESM(require("chalk"));
|
|
8926
|
-
var
|
|
9789
|
+
var RESET = "\x1B[0m";
|
|
9790
|
+
var BOLD = "\x1B[1m";
|
|
9791
|
+
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
9792
|
+
var SESSION_GAP_MS = 6e4;
|
|
9793
|
+
function formatAge(timestamp) {
|
|
9794
|
+
const age = Math.round((Date.now() - timestamp) / 1e3);
|
|
9795
|
+
if (age < 60) return `${age}s ago`;
|
|
9796
|
+
if (age < 3600) return `${Math.round(age / 60)}m ago`;
|
|
9797
|
+
if (age < 86400) return `${Math.round(age / 3600)}h ago`;
|
|
9798
|
+
return `${Math.round(age / 86400)}d ago`;
|
|
9799
|
+
}
|
|
9800
|
+
function renderDiff(raw) {
|
|
9801
|
+
const lines = raw.split("\n").filter(
|
|
9802
|
+
(l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
|
|
9803
|
+
);
|
|
9804
|
+
for (const line of lines) {
|
|
9805
|
+
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
9806
|
+
process.stdout.write(import_chalk12.default.bold(line) + "\n");
|
|
9807
|
+
} else if (line.startsWith("+")) {
|
|
9808
|
+
process.stdout.write(import_chalk12.default.green(line) + "\n");
|
|
9809
|
+
} else if (line.startsWith("-")) {
|
|
9810
|
+
process.stdout.write(import_chalk12.default.red(line) + "\n");
|
|
9811
|
+
} else if (line.startsWith("@@")) {
|
|
9812
|
+
process.stdout.write(import_chalk12.default.cyan(line) + "\n");
|
|
9813
|
+
} else {
|
|
9814
|
+
process.stdout.write(import_chalk12.default.gray(line) + "\n");
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
function isSessionBoundary(entries, idx) {
|
|
9819
|
+
if (idx <= 0) return false;
|
|
9820
|
+
return entries[idx - 1].timestamp - entries[idx].timestamp > SESSION_GAP_MS;
|
|
9821
|
+
}
|
|
9822
|
+
function sessionStart(entries, idx) {
|
|
9823
|
+
let i = idx;
|
|
9824
|
+
while (i > 0 && !isSessionBoundary(entries, i)) i--;
|
|
9825
|
+
return i;
|
|
9826
|
+
}
|
|
9827
|
+
function render(entries, idx) {
|
|
9828
|
+
const entry = entries[idx];
|
|
9829
|
+
const total = entries.length;
|
|
9830
|
+
const step = idx + 1;
|
|
9831
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
9832
|
+
process.stdout.write(
|
|
9833
|
+
import_chalk12.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk12.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk12.default.gray(
|
|
9834
|
+
` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
|
|
9835
|
+
) : "") + "\n\n"
|
|
9836
|
+
);
|
|
9837
|
+
process.stdout.write(
|
|
9838
|
+
` ${BOLD}Tool:${RESET} ${import_chalk12.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk12.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
|
|
9839
|
+
);
|
|
9840
|
+
process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk12.default.gray(formatAge(entry.timestamp))}
|
|
9841
|
+
`);
|
|
9842
|
+
process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk12.default.gray(entry.cwd)}
|
|
9843
|
+
`);
|
|
9844
|
+
if (entry.files && entry.files.length > 0) {
|
|
9845
|
+
process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk12.default.gray(entry.files.join(", "))}
|
|
9846
|
+
`);
|
|
9847
|
+
}
|
|
9848
|
+
if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
|
|
9849
|
+
process.stdout.write(import_chalk12.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
|
|
9850
|
+
}
|
|
9851
|
+
process.stdout.write("\n");
|
|
9852
|
+
const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
|
|
9853
|
+
if (diff) {
|
|
9854
|
+
renderDiff(diff);
|
|
9855
|
+
} else {
|
|
9856
|
+
process.stdout.write(
|
|
9857
|
+
import_chalk12.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
|
|
9858
|
+
);
|
|
9859
|
+
}
|
|
9860
|
+
process.stdout.write("\n");
|
|
9861
|
+
process.stdout.write(
|
|
9862
|
+
import_chalk12.default.gray(" ") + (idx < total - 1 ? import_chalk12.default.white("[\u2190] older") : import_chalk12.default.gray("[\u2190] older")) + import_chalk12.default.gray(" ") + (idx > 0 ? import_chalk12.default.white("[\u2192] newer") : import_chalk12.default.gray("[\u2192] newer")) + import_chalk12.default.gray(" ") + import_chalk12.default.green("[\u21B5] restore here") + import_chalk12.default.gray(" ") + import_chalk12.default.yellow("[s] session start") + import_chalk12.default.gray(" ") + import_chalk12.default.gray("[q] quit") + "\n"
|
|
9863
|
+
);
|
|
9864
|
+
}
|
|
9865
|
+
async function runUndoNavigator(entries) {
|
|
9866
|
+
if (entries.length === 0) return { restored: false };
|
|
9867
|
+
const display = [...entries].reverse();
|
|
9868
|
+
let idx = 0;
|
|
9869
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
9870
|
+
render(display, idx);
|
|
9871
|
+
return { restored: false };
|
|
9872
|
+
}
|
|
9873
|
+
import_readline2.default.emitKeypressEvents(process.stdin);
|
|
9874
|
+
return new Promise((resolve) => {
|
|
9875
|
+
let done = false;
|
|
9876
|
+
render(display, idx);
|
|
9877
|
+
try {
|
|
9878
|
+
process.stdin.setRawMode(true);
|
|
9879
|
+
} catch {
|
|
9880
|
+
resolve({ restored: false });
|
|
9881
|
+
return;
|
|
9882
|
+
}
|
|
9883
|
+
process.stdin.resume();
|
|
9884
|
+
const cleanup = () => {
|
|
9885
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
9886
|
+
try {
|
|
9887
|
+
process.stdin.setRawMode(false);
|
|
9888
|
+
} catch {
|
|
9889
|
+
}
|
|
9890
|
+
process.stdin.pause();
|
|
9891
|
+
};
|
|
9892
|
+
const onKeypress = (_str, key) => {
|
|
9893
|
+
if (done) return;
|
|
9894
|
+
const name = key?.name ?? "";
|
|
9895
|
+
if (name === "left" || name === "h") {
|
|
9896
|
+
if (idx < display.length - 1) {
|
|
9897
|
+
idx++;
|
|
9898
|
+
render(display, idx);
|
|
9899
|
+
}
|
|
9900
|
+
} else if (name === "right" || name === "l") {
|
|
9901
|
+
if (idx > 0) {
|
|
9902
|
+
idx--;
|
|
9903
|
+
render(display, idx);
|
|
9904
|
+
}
|
|
9905
|
+
} else if (name === "s") {
|
|
9906
|
+
const start = sessionStart(display, idx);
|
|
9907
|
+
if (start !== idx) {
|
|
9908
|
+
idx = start;
|
|
9909
|
+
render(display, idx);
|
|
9910
|
+
}
|
|
9911
|
+
} else if (name === "return" || name === "y") {
|
|
9912
|
+
done = true;
|
|
9913
|
+
cleanup();
|
|
9914
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
9915
|
+
const entry = display[idx];
|
|
9916
|
+
process.stdout.write(import_chalk12.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
|
|
9917
|
+
if (applyUndo(entry.hash, entry.cwd)) {
|
|
9918
|
+
process.stdout.write(import_chalk12.default.green("\u2705 Reverted successfully.\n\n"));
|
|
9919
|
+
resolve({ restored: true });
|
|
9920
|
+
} else {
|
|
9921
|
+
process.stdout.write(import_chalk12.default.red("\u274C Undo failed.\n\n"));
|
|
9922
|
+
resolve({ restored: false });
|
|
9923
|
+
}
|
|
9924
|
+
} else if (name === "q" || key?.ctrl && name === "c") {
|
|
9925
|
+
done = true;
|
|
9926
|
+
cleanup();
|
|
9927
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
9928
|
+
process.stdout.write(import_chalk12.default.gray("\nCancelled.\n\n"));
|
|
9929
|
+
resolve({ restored: false });
|
|
9930
|
+
}
|
|
9931
|
+
};
|
|
9932
|
+
process.stdin.on("keypress", onKeypress);
|
|
9933
|
+
});
|
|
9934
|
+
}
|
|
9935
|
+
|
|
9936
|
+
// src/cli/commands/undo.ts
|
|
9937
|
+
function findMatchingCwd(startDir, history) {
|
|
9938
|
+
const cwds = new Set(history.map((e) => e.cwd));
|
|
9939
|
+
let dir = startDir;
|
|
9940
|
+
while (true) {
|
|
9941
|
+
if (cwds.has(dir)) return dir;
|
|
9942
|
+
const parent = import_path26.default.dirname(dir);
|
|
9943
|
+
if (parent === dir) return null;
|
|
9944
|
+
dir = parent;
|
|
9945
|
+
}
|
|
9946
|
+
}
|
|
9947
|
+
function formatAge2(timestamp) {
|
|
9948
|
+
const age = Math.round((Date.now() - timestamp) / 1e3);
|
|
9949
|
+
if (age < 60) return `${age}s ago`;
|
|
9950
|
+
if (age < 3600) return `${Math.round(age / 60)}m ago`;
|
|
9951
|
+
if (age < 86400) return `${Math.round(age / 3600)}h ago`;
|
|
9952
|
+
return `${Math.round(age / 86400)}d ago`;
|
|
9953
|
+
}
|
|
8927
9954
|
function registerUndoCommand(program2) {
|
|
8928
9955
|
program2.command("undo").description(
|
|
8929
|
-
"
|
|
8930
|
-
).option("--steps <n>", "
|
|
8931
|
-
const steps = Math.max(1, parseInt(options.steps, 10) || 1);
|
|
9956
|
+
"Browse and restore pre-AI snapshots. Arrow keys to navigate, Enter to restore. Use --steps N to go back N actions non-interactively, --list to print history."
|
|
9957
|
+
).option("--steps <n>", "Non-interactive: restore N steps back (default: 1)").option("--list", "Print snapshot history as a table and exit").option("--all", "Include snapshots from all directories, not just the current one").action(async (options) => {
|
|
8932
9958
|
const allHistory = getSnapshotHistory();
|
|
8933
|
-
const
|
|
9959
|
+
const matchedCwd = options.all ? null : findMatchingCwd(process.cwd(), allHistory);
|
|
9960
|
+
const history = options.all ? allHistory : allHistory.filter((s) => s.cwd === matchedCwd);
|
|
8934
9961
|
if (history.length === 0) {
|
|
8935
9962
|
if (!options.all && allHistory.length > 0) {
|
|
8936
9963
|
console.log(
|
|
8937
|
-
|
|
9964
|
+
import_chalk13.default.yellow(
|
|
8938
9965
|
`
|
|
8939
9966
|
\u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
|
|
8940
|
-
Run ${
|
|
9967
|
+
Run ${import_chalk13.default.cyan("node9 undo --all")} to see snapshots from all projects.
|
|
8941
9968
|
`
|
|
8942
9969
|
)
|
|
8943
9970
|
);
|
|
8944
9971
|
} else {
|
|
8945
|
-
console.log(
|
|
9972
|
+
console.log(import_chalk13.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
|
|
8946
9973
|
}
|
|
8947
9974
|
return;
|
|
8948
9975
|
}
|
|
8949
|
-
|
|
8950
|
-
|
|
9976
|
+
if (options.list) {
|
|
9977
|
+
console.log(import_chalk13.default.magenta.bold("\n\u23EA Snapshot History\n"));
|
|
8951
9978
|
console.log(
|
|
8952
|
-
|
|
8953
|
-
`
|
|
8954
|
-
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
8955
|
-
`
|
|
9979
|
+
import_chalk13.default.gray(
|
|
9980
|
+
` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
|
|
8956
9981
|
)
|
|
8957
9982
|
);
|
|
9983
|
+
console.log(import_chalk13.default.gray(" " + "\u2500".repeat(80)));
|
|
9984
|
+
const display = [...history].reverse();
|
|
9985
|
+
let prevTs = null;
|
|
9986
|
+
for (let i = 0; i < display.length; i++) {
|
|
9987
|
+
const e = display[i];
|
|
9988
|
+
const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
|
|
9989
|
+
if (isGap) console.log(import_chalk13.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
|
|
9990
|
+
const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
|
|
9991
|
+
const tool = e.tool.slice(0, 8).padEnd(8);
|
|
9992
|
+
const when = formatAge2(e.timestamp).padEnd(10);
|
|
9993
|
+
const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
|
|
9994
|
+
console.log(
|
|
9995
|
+
import_chalk13.default.white(
|
|
9996
|
+
` ${String(i + 1).padEnd(3)} ${label} ${import_chalk13.default.cyan(tool)} ${import_chalk13.default.gray(when)} ${import_chalk13.default.gray(dir)}`
|
|
9997
|
+
)
|
|
9998
|
+
);
|
|
9999
|
+
prevTs = e.timestamp;
|
|
10000
|
+
}
|
|
10001
|
+
console.log("");
|
|
8958
10002
|
return;
|
|
8959
10003
|
}
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
10004
|
+
if (options.steps !== void 0) {
|
|
10005
|
+
const steps = Math.max(1, parseInt(options.steps, 10) || 1);
|
|
10006
|
+
const idx = history.length - steps;
|
|
10007
|
+
if (idx < 0) {
|
|
10008
|
+
console.log(
|
|
10009
|
+
import_chalk13.default.yellow(
|
|
10010
|
+
`
|
|
10011
|
+
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
10012
|
+
`
|
|
10013
|
+
)
|
|
10014
|
+
);
|
|
10015
|
+
return;
|
|
10016
|
+
}
|
|
10017
|
+
const snapshot = history[idx];
|
|
10018
|
+
const ageStr = formatAge2(snapshot.timestamp);
|
|
10019
|
+
console.log(
|
|
10020
|
+
import_chalk13.default.magenta.bold(`
|
|
8965
10021
|
\u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
|
|
8966
|
-
|
|
8967
|
-
console.log(
|
|
8968
|
-
import_chalk12.default.white(
|
|
8969
|
-
` Tool: ${import_chalk12.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk12.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
8970
|
-
)
|
|
8971
|
-
);
|
|
8972
|
-
console.log(import_chalk12.default.white(` When: ${import_chalk12.default.gray(ageStr)}`));
|
|
8973
|
-
console.log(import_chalk12.default.white(` Dir: ${import_chalk12.default.gray(snapshot.cwd)}`));
|
|
8974
|
-
if (steps > 1)
|
|
10022
|
+
);
|
|
8975
10023
|
console.log(
|
|
8976
|
-
|
|
10024
|
+
import_chalk13.default.white(
|
|
10025
|
+
` Tool: ${import_chalk13.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk13.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
10026
|
+
)
|
|
8977
10027
|
);
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
console.log(
|
|
8991
|
-
|
|
8992
|
-
console.log(
|
|
10028
|
+
console.log(import_chalk13.default.white(` When: ${import_chalk13.default.gray(ageStr)}`));
|
|
10029
|
+
console.log(import_chalk13.default.white(` Dir: ${import_chalk13.default.gray(snapshot.cwd)}`));
|
|
10030
|
+
if (steps > 1)
|
|
10031
|
+
console.log(
|
|
10032
|
+
import_chalk13.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
|
|
10033
|
+
);
|
|
10034
|
+
console.log("");
|
|
10035
|
+
const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
|
|
10036
|
+
if (diff) {
|
|
10037
|
+
const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
|
|
10038
|
+
for (const line of lines) {
|
|
10039
|
+
if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk13.default.bold(line));
|
|
10040
|
+
else if (line.startsWith("+")) console.log(import_chalk13.default.green(line));
|
|
10041
|
+
else if (line.startsWith("-")) console.log(import_chalk13.default.red(line));
|
|
10042
|
+
else if (line.startsWith("@@")) console.log(import_chalk13.default.cyan(line));
|
|
10043
|
+
else console.log(import_chalk13.default.gray(line));
|
|
8993
10044
|
}
|
|
10045
|
+
console.log("");
|
|
10046
|
+
} else {
|
|
10047
|
+
console.log(
|
|
10048
|
+
import_chalk13.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
|
|
10049
|
+
);
|
|
8994
10050
|
}
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
default: false
|
|
9004
|
-
});
|
|
9005
|
-
if (proceed) {
|
|
9006
|
-
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
9007
|
-
console.log(import_chalk12.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10051
|
+
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
10052
|
+
const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
|
|
10053
|
+
if (proceed) {
|
|
10054
|
+
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
10055
|
+
console.log(import_chalk13.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10056
|
+
} else {
|
|
10057
|
+
console.error(import_chalk13.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
10058
|
+
}
|
|
9008
10059
|
} else {
|
|
9009
|
-
console.
|
|
10060
|
+
console.log(import_chalk13.default.gray("\nCancelled.\n"));
|
|
9010
10061
|
}
|
|
9011
|
-
|
|
9012
|
-
console.log(import_chalk12.default.gray("\nCancelled.\n"));
|
|
10062
|
+
return;
|
|
9013
10063
|
}
|
|
10064
|
+
await runUndoNavigator(history);
|
|
9014
10065
|
});
|
|
9015
10066
|
}
|
|
9016
10067
|
|
|
9017
10068
|
// src/cli/commands/watch.ts
|
|
9018
|
-
var
|
|
10069
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
9019
10070
|
var import_child_process11 = require("child_process");
|
|
9020
10071
|
init_daemon();
|
|
9021
10072
|
function registerWatchCommand(program2) {
|
|
@@ -9032,7 +10083,7 @@ function registerWatchCommand(program2) {
|
|
|
9032
10083
|
throw new Error("not running");
|
|
9033
10084
|
}
|
|
9034
10085
|
} catch {
|
|
9035
|
-
console.error(
|
|
10086
|
+
console.error(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
9036
10087
|
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
9037
10088
|
detached: true,
|
|
9038
10089
|
stdio: "ignore",
|
|
@@ -9054,12 +10105,12 @@ function registerWatchCommand(program2) {
|
|
|
9054
10105
|
}
|
|
9055
10106
|
}
|
|
9056
10107
|
if (!ready) {
|
|
9057
|
-
console.error(
|
|
10108
|
+
console.error(import_chalk14.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
9058
10109
|
process.exit(1);
|
|
9059
10110
|
}
|
|
9060
10111
|
}
|
|
9061
10112
|
console.error(
|
|
9062
|
-
|
|
10113
|
+
import_chalk14.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk14.default.dim(` \u2192 localhost:${port}`) + import_chalk14.default.dim(
|
|
9063
10114
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
9064
10115
|
)
|
|
9065
10116
|
);
|
|
@@ -9068,7 +10119,7 @@ function registerWatchCommand(program2) {
|
|
|
9068
10119
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
9069
10120
|
});
|
|
9070
10121
|
if (result.error) {
|
|
9071
|
-
console.error(
|
|
10122
|
+
console.error(import_chalk14.default.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
9072
10123
|
process.exit(1);
|
|
9073
10124
|
}
|
|
9074
10125
|
process.exit(result.status ?? 0);
|
|
@@ -9076,8 +10127,8 @@ function registerWatchCommand(program2) {
|
|
|
9076
10127
|
}
|
|
9077
10128
|
|
|
9078
10129
|
// src/mcp-gateway/index.ts
|
|
9079
|
-
var
|
|
9080
|
-
var
|
|
10130
|
+
var import_readline3 = __toESM(require("readline"));
|
|
10131
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
9081
10132
|
var import_child_process12 = require("child_process");
|
|
9082
10133
|
var import_execa3 = require("execa");
|
|
9083
10134
|
init_orchestrator();
|
|
@@ -9141,13 +10192,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
9141
10192
|
const prov = checkProvenance(executable);
|
|
9142
10193
|
if (prov.trustLevel === "suspect") {
|
|
9143
10194
|
console.error(
|
|
9144
|
-
|
|
10195
|
+
import_chalk15.default.red(
|
|
9145
10196
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
9146
10197
|
)
|
|
9147
10198
|
);
|
|
9148
|
-
console.error(
|
|
10199
|
+
console.error(import_chalk15.default.red(" Verify this binary is trusted before proceeding."));
|
|
9149
10200
|
}
|
|
9150
|
-
console.error(
|
|
10201
|
+
console.error(import_chalk15.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
9151
10202
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
9152
10203
|
"NODE_OPTIONS",
|
|
9153
10204
|
"NODE_PATH",
|
|
@@ -9175,7 +10226,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
9175
10226
|
let authPending = false;
|
|
9176
10227
|
let deferredExitCode = null;
|
|
9177
10228
|
let deferredStdinEnd = false;
|
|
9178
|
-
const agentIn =
|
|
10229
|
+
const agentIn = import_readline3.default.createInterface({ input: process.stdin, terminal: false });
|
|
9179
10230
|
agentIn.on("line", async (line) => {
|
|
9180
10231
|
let message;
|
|
9181
10232
|
try {
|
|
@@ -9211,10 +10262,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
9211
10262
|
mcpServer
|
|
9212
10263
|
});
|
|
9213
10264
|
if (!result.approved) {
|
|
9214
|
-
console.error(
|
|
10265
|
+
console.error(import_chalk15.default.red(`
|
|
9215
10266
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
9216
|
-
console.error(
|
|
9217
|
-
console.error(
|
|
10267
|
+
console.error(import_chalk15.default.gray(` Tool: ${toolName}`));
|
|
10268
|
+
console.error(import_chalk15.default.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
9218
10269
|
`));
|
|
9219
10270
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
9220
10271
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -9287,7 +10338,7 @@ function registerMcpGatewayCommand(program2) {
|
|
|
9287
10338
|
}
|
|
9288
10339
|
|
|
9289
10340
|
// src/cli/commands/trust.ts
|
|
9290
|
-
var
|
|
10341
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
9291
10342
|
init_trusted_hosts();
|
|
9292
10343
|
function isValidHost(host) {
|
|
9293
10344
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
@@ -9298,44 +10349,44 @@ function registerTrustCommand(program2) {
|
|
|
9298
10349
|
const normalized = normalizeHost(host.trim());
|
|
9299
10350
|
if (!isValidHost(normalized)) {
|
|
9300
10351
|
console.error(
|
|
9301
|
-
|
|
10352
|
+
import_chalk16.default.red(`
|
|
9302
10353
|
\u274C Invalid host: "${host}"
|
|
9303
|
-
`) +
|
|
10354
|
+
`) + import_chalk16.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
9304
10355
|
);
|
|
9305
10356
|
process.exit(1);
|
|
9306
10357
|
}
|
|
9307
10358
|
addTrustedHost(normalized);
|
|
9308
|
-
console.log(
|
|
10359
|
+
console.log(import_chalk16.default.green(`
|
|
9309
10360
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
9310
10361
|
console.log(
|
|
9311
|
-
|
|
10362
|
+
import_chalk16.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
9312
10363
|
);
|
|
9313
10364
|
});
|
|
9314
10365
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
9315
10366
|
const normalized = normalizeHost(host.trim());
|
|
9316
10367
|
const removed = removeTrustedHost(normalized);
|
|
9317
10368
|
if (!removed) {
|
|
9318
|
-
console.error(
|
|
10369
|
+
console.error(import_chalk16.default.yellow(`
|
|
9319
10370
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
9320
10371
|
`));
|
|
9321
10372
|
process.exit(1);
|
|
9322
10373
|
}
|
|
9323
|
-
console.log(
|
|
10374
|
+
console.log(import_chalk16.default.green(`
|
|
9324
10375
|
\u2705 ${normalized} removed from trusted hosts.
|
|
9325
10376
|
`));
|
|
9326
10377
|
});
|
|
9327
10378
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
9328
10379
|
const hosts = readTrustedHosts();
|
|
9329
10380
|
if (hosts.length === 0) {
|
|
9330
|
-
console.log(
|
|
9331
|
-
console.log(` Add one: ${
|
|
10381
|
+
console.log(import_chalk16.default.gray("\n No trusted hosts configured.\n"));
|
|
10382
|
+
console.log(` Add one: ${import_chalk16.default.cyan("node9 trust add api.mycompany.com")}
|
|
9332
10383
|
`);
|
|
9333
10384
|
return;
|
|
9334
10385
|
}
|
|
9335
|
-
console.log(
|
|
10386
|
+
console.log(import_chalk16.default.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
9336
10387
|
for (const entry of hosts) {
|
|
9337
10388
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
9338
|
-
console.log(` ${
|
|
10389
|
+
console.log(` ${import_chalk16.default.cyan(entry.host.padEnd(40))} ${import_chalk16.default.gray(`added ${date}`)}`);
|
|
9339
10390
|
}
|
|
9340
10391
|
console.log("");
|
|
9341
10392
|
});
|
|
@@ -9343,20 +10394,20 @@ function registerTrustCommand(program2) {
|
|
|
9343
10394
|
|
|
9344
10395
|
// src/cli.ts
|
|
9345
10396
|
var { version } = JSON.parse(
|
|
9346
|
-
|
|
10397
|
+
import_fs26.default.readFileSync(import_path29.default.join(__dirname, "../package.json"), "utf-8")
|
|
9347
10398
|
);
|
|
9348
10399
|
var program = new import_commander.Command();
|
|
9349
10400
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
9350
10401
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
9351
10402
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
9352
|
-
const credPath =
|
|
9353
|
-
if (!
|
|
9354
|
-
|
|
10403
|
+
const credPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "credentials.json");
|
|
10404
|
+
if (!import_fs26.default.existsSync(import_path29.default.dirname(credPath)))
|
|
10405
|
+
import_fs26.default.mkdirSync(import_path29.default.dirname(credPath), { recursive: true });
|
|
9355
10406
|
const profileName = options.profile || "default";
|
|
9356
10407
|
let existingCreds = {};
|
|
9357
10408
|
try {
|
|
9358
|
-
if (
|
|
9359
|
-
const raw = JSON.parse(
|
|
10409
|
+
if (import_fs26.default.existsSync(credPath)) {
|
|
10410
|
+
const raw = JSON.parse(import_fs26.default.readFileSync(credPath, "utf-8"));
|
|
9360
10411
|
if (raw.apiKey) {
|
|
9361
10412
|
existingCreds = {
|
|
9362
10413
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -9368,13 +10419,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
9368
10419
|
} catch {
|
|
9369
10420
|
}
|
|
9370
10421
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
9371
|
-
|
|
10422
|
+
import_fs26.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
9372
10423
|
if (profileName === "default") {
|
|
9373
|
-
const configPath =
|
|
10424
|
+
const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
9374
10425
|
let config = {};
|
|
9375
10426
|
try {
|
|
9376
|
-
if (
|
|
9377
|
-
config = JSON.parse(
|
|
10427
|
+
if (import_fs26.default.existsSync(configPath))
|
|
10428
|
+
config = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
|
|
9378
10429
|
} catch {
|
|
9379
10430
|
}
|
|
9380
10431
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -9389,36 +10440,40 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
9389
10440
|
approvers.cloud = false;
|
|
9390
10441
|
}
|
|
9391
10442
|
s.approvers = approvers;
|
|
9392
|
-
if (!
|
|
9393
|
-
|
|
9394
|
-
|
|
10443
|
+
if (!import_fs26.default.existsSync(import_path29.default.dirname(configPath)))
|
|
10444
|
+
import_fs26.default.mkdirSync(import_path29.default.dirname(configPath), { recursive: true });
|
|
10445
|
+
import_fs26.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
9395
10446
|
}
|
|
9396
10447
|
if (options.profile && profileName !== "default") {
|
|
9397
|
-
console.log(
|
|
9398
|
-
console.log(
|
|
10448
|
+
console.log(import_chalk18.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
10449
|
+
console.log(import_chalk18.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
9399
10450
|
} else if (options.local) {
|
|
9400
|
-
console.log(
|
|
9401
|
-
console.log(
|
|
10451
|
+
console.log(import_chalk18.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
10452
|
+
console.log(import_chalk18.default.gray(` All decisions stay on this machine.`));
|
|
9402
10453
|
} else {
|
|
9403
|
-
console.log(
|
|
9404
|
-
console.log(
|
|
10454
|
+
console.log(import_chalk18.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
10455
|
+
console.log(import_chalk18.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
9405
10456
|
}
|
|
9406
10457
|
});
|
|
9407
|
-
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) => {
|
|
10458
|
+
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
9408
10459
|
if (target === "gemini") return await setupGemini();
|
|
9409
10460
|
if (target === "claude") return await setupClaude();
|
|
9410
10461
|
if (target === "cursor") return await setupCursor();
|
|
9411
|
-
|
|
10462
|
+
if (target === "hud") return setupHud();
|
|
10463
|
+
console.error(import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
9412
10464
|
process.exit(1);
|
|
9413
10465
|
});
|
|
9414
|
-
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) => {
|
|
10466
|
+
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
9415
10467
|
if (!target) {
|
|
9416
|
-
console.log(
|
|
9417
|
-
console.log(" Usage: " +
|
|
10468
|
+
console.log(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
10469
|
+
console.log(" Usage: " + import_chalk18.default.white("node9 setup <target>") + "\n");
|
|
9418
10470
|
console.log(" Targets:");
|
|
9419
|
-
console.log(" " +
|
|
9420
|
-
console.log(" " +
|
|
9421
|
-
console.log(" " +
|
|
10471
|
+
console.log(" " + import_chalk18.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
10472
|
+
console.log(" " + import_chalk18.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
10473
|
+
console.log(" " + import_chalk18.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
10474
|
+
process.stdout.write(
|
|
10475
|
+
" " + import_chalk18.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
10476
|
+
);
|
|
9422
10477
|
console.log("");
|
|
9423
10478
|
return;
|
|
9424
10479
|
}
|
|
@@ -9426,7 +10481,8 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
9426
10481
|
if (t === "gemini") return await setupGemini();
|
|
9427
10482
|
if (t === "claude") return await setupClaude();
|
|
9428
10483
|
if (t === "cursor") return await setupCursor();
|
|
9429
|
-
|
|
10484
|
+
if (t === "hud") return setupHud();
|
|
10485
|
+
console.error(import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
9430
10486
|
process.exit(1);
|
|
9431
10487
|
});
|
|
9432
10488
|
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) => {
|
|
@@ -9434,31 +10490,34 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
9434
10490
|
if (target === "claude") fn = teardownClaude;
|
|
9435
10491
|
else if (target === "gemini") fn = teardownGemini;
|
|
9436
10492
|
else if (target === "cursor") fn = teardownCursor;
|
|
10493
|
+
else if (target === "hud") fn = teardownHud;
|
|
9437
10494
|
else {
|
|
9438
|
-
console.error(
|
|
10495
|
+
console.error(
|
|
10496
|
+
import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
10497
|
+
);
|
|
9439
10498
|
process.exit(1);
|
|
9440
10499
|
}
|
|
9441
|
-
console.log(
|
|
10500
|
+
console.log(import_chalk18.default.cyan(`
|
|
9442
10501
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
9443
10502
|
`));
|
|
9444
10503
|
try {
|
|
9445
10504
|
fn();
|
|
9446
10505
|
} catch (err) {
|
|
9447
|
-
console.error(
|
|
10506
|
+
console.error(import_chalk18.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9448
10507
|
process.exit(1);
|
|
9449
10508
|
}
|
|
9450
|
-
console.log(
|
|
10509
|
+
console.log(import_chalk18.default.gray("\n Restart the agent for changes to take effect."));
|
|
9451
10510
|
});
|
|
9452
10511
|
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) => {
|
|
9453
|
-
console.log(
|
|
9454
|
-
console.log(
|
|
10512
|
+
console.log(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
10513
|
+
console.log(import_chalk18.default.bold("Stopping daemon..."));
|
|
9455
10514
|
try {
|
|
9456
10515
|
stopDaemon();
|
|
9457
|
-
console.log(
|
|
10516
|
+
console.log(import_chalk18.default.green(" \u2705 Daemon stopped"));
|
|
9458
10517
|
} catch {
|
|
9459
|
-
console.log(
|
|
10518
|
+
console.log(import_chalk18.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
9460
10519
|
}
|
|
9461
|
-
console.log(
|
|
10520
|
+
console.log(import_chalk18.default.bold("\nRemoving hooks..."));
|
|
9462
10521
|
let teardownFailed = false;
|
|
9463
10522
|
for (const [label, fn] of [
|
|
9464
10523
|
["Claude", teardownClaude],
|
|
@@ -9470,45 +10529,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
9470
10529
|
} catch (err) {
|
|
9471
10530
|
teardownFailed = true;
|
|
9472
10531
|
console.error(
|
|
9473
|
-
|
|
10532
|
+
import_chalk18.default.red(
|
|
9474
10533
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
9475
10534
|
)
|
|
9476
10535
|
);
|
|
9477
10536
|
}
|
|
9478
10537
|
}
|
|
9479
10538
|
if (options.purge) {
|
|
9480
|
-
const node9Dir =
|
|
9481
|
-
if (
|
|
9482
|
-
const confirmed = await (0,
|
|
10539
|
+
const node9Dir = import_path29.default.join(import_os22.default.homedir(), ".node9");
|
|
10540
|
+
if (import_fs26.default.existsSync(node9Dir)) {
|
|
10541
|
+
const confirmed = await (0, import_prompts2.confirm)({
|
|
9483
10542
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
9484
10543
|
default: false
|
|
9485
10544
|
});
|
|
9486
10545
|
if (confirmed) {
|
|
9487
|
-
|
|
9488
|
-
if (
|
|
10546
|
+
import_fs26.default.rmSync(node9Dir, { recursive: true });
|
|
10547
|
+
if (import_fs26.default.existsSync(node9Dir)) {
|
|
9489
10548
|
console.error(
|
|
9490
|
-
|
|
10549
|
+
import_chalk18.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
9491
10550
|
);
|
|
9492
10551
|
} else {
|
|
9493
|
-
console.log(
|
|
10552
|
+
console.log(import_chalk18.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
9494
10553
|
}
|
|
9495
10554
|
} else {
|
|
9496
|
-
console.log(
|
|
10555
|
+
console.log(import_chalk18.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
9497
10556
|
}
|
|
9498
10557
|
} else {
|
|
9499
|
-
console.log(
|
|
10558
|
+
console.log(import_chalk18.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
9500
10559
|
}
|
|
9501
10560
|
} else {
|
|
9502
10561
|
console.log(
|
|
9503
|
-
|
|
10562
|
+
import_chalk18.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
9504
10563
|
);
|
|
9505
10564
|
}
|
|
9506
10565
|
if (teardownFailed) {
|
|
9507
|
-
console.error(
|
|
10566
|
+
console.error(import_chalk18.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
9508
10567
|
process.exit(1);
|
|
9509
10568
|
}
|
|
9510
|
-
console.log(
|
|
9511
|
-
console.log(
|
|
10569
|
+
console.log(import_chalk18.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
10570
|
+
console.log(import_chalk18.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
9512
10571
|
});
|
|
9513
10572
|
registerDoctorCommand(program, version);
|
|
9514
10573
|
program.command("explain").description(
|
|
@@ -9521,7 +10580,7 @@ program.command("explain").description(
|
|
|
9521
10580
|
try {
|
|
9522
10581
|
args = JSON.parse(trimmed);
|
|
9523
10582
|
} catch {
|
|
9524
|
-
console.error(
|
|
10583
|
+
console.error(import_chalk18.default.red(`
|
|
9525
10584
|
\u274C Invalid JSON: ${trimmed}
|
|
9526
10585
|
`));
|
|
9527
10586
|
process.exit(1);
|
|
@@ -9532,54 +10591,54 @@ program.command("explain").description(
|
|
|
9532
10591
|
}
|
|
9533
10592
|
const result = await explainPolicy(tool, args);
|
|
9534
10593
|
console.log("");
|
|
9535
|
-
console.log(
|
|
10594
|
+
console.log(import_chalk18.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
9536
10595
|
console.log("");
|
|
9537
|
-
console.log(` ${
|
|
10596
|
+
console.log(` ${import_chalk18.default.bold("Tool:")} ${import_chalk18.default.white(result.tool)}`);
|
|
9538
10597
|
if (argsRaw) {
|
|
9539
10598
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
9540
|
-
console.log(` ${
|
|
10599
|
+
console.log(` ${import_chalk18.default.bold("Input:")} ${import_chalk18.default.gray(preview)}`);
|
|
9541
10600
|
}
|
|
9542
10601
|
console.log("");
|
|
9543
|
-
console.log(
|
|
10602
|
+
console.log(import_chalk18.default.bold("Config Sources (Waterfall):"));
|
|
9544
10603
|
for (const tier of result.waterfall) {
|
|
9545
|
-
const num =
|
|
10604
|
+
const num = import_chalk18.default.gray(` ${tier.tier}.`);
|
|
9546
10605
|
const label = tier.label.padEnd(16);
|
|
9547
10606
|
let statusStr;
|
|
9548
10607
|
if (tier.tier === 1) {
|
|
9549
|
-
statusStr =
|
|
10608
|
+
statusStr = import_chalk18.default.gray(tier.note ?? "");
|
|
9550
10609
|
} else if (tier.status === "active") {
|
|
9551
|
-
const loc = tier.path ?
|
|
9552
|
-
const note = tier.note ?
|
|
9553
|
-
statusStr =
|
|
10610
|
+
const loc = tier.path ? import_chalk18.default.gray(tier.path) : "";
|
|
10611
|
+
const note = tier.note ? import_chalk18.default.gray(`(${tier.note})`) : "";
|
|
10612
|
+
statusStr = import_chalk18.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
9554
10613
|
} else {
|
|
9555
|
-
statusStr =
|
|
10614
|
+
statusStr = import_chalk18.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
9556
10615
|
}
|
|
9557
|
-
console.log(`${num} ${
|
|
10616
|
+
console.log(`${num} ${import_chalk18.default.white(label)} ${statusStr}`);
|
|
9558
10617
|
}
|
|
9559
10618
|
console.log("");
|
|
9560
|
-
console.log(
|
|
10619
|
+
console.log(import_chalk18.default.bold("Policy Evaluation:"));
|
|
9561
10620
|
for (const step of result.steps) {
|
|
9562
10621
|
const isFinal = step.isFinal;
|
|
9563
10622
|
let icon;
|
|
9564
|
-
if (step.outcome === "allow") icon =
|
|
9565
|
-
else if (step.outcome === "review") icon =
|
|
9566
|
-
else if (step.outcome === "skip") icon =
|
|
9567
|
-
else icon =
|
|
10623
|
+
if (step.outcome === "allow") icon = import_chalk18.default.green(" \u2705");
|
|
10624
|
+
else if (step.outcome === "review") icon = import_chalk18.default.red(" \u{1F534}");
|
|
10625
|
+
else if (step.outcome === "skip") icon = import_chalk18.default.gray(" \u2500 ");
|
|
10626
|
+
else icon = import_chalk18.default.gray(" \u25CB ");
|
|
9568
10627
|
const name = step.name.padEnd(18);
|
|
9569
|
-
const nameStr = isFinal ?
|
|
9570
|
-
const detail = isFinal ?
|
|
9571
|
-
const arrow = isFinal ?
|
|
10628
|
+
const nameStr = isFinal ? import_chalk18.default.white.bold(name) : import_chalk18.default.white(name);
|
|
10629
|
+
const detail = isFinal ? import_chalk18.default.white(step.detail) : import_chalk18.default.gray(step.detail);
|
|
10630
|
+
const arrow = isFinal ? import_chalk18.default.yellow(" \u2190 STOP") : "";
|
|
9572
10631
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
9573
10632
|
}
|
|
9574
10633
|
console.log("");
|
|
9575
10634
|
if (result.decision === "allow") {
|
|
9576
|
-
console.log(
|
|
10635
|
+
console.log(import_chalk18.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk18.default.gray(" \u2014 no approval needed"));
|
|
9577
10636
|
} else {
|
|
9578
10637
|
console.log(
|
|
9579
|
-
|
|
10638
|
+
import_chalk18.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk18.default.gray(" \u2014 human approval required")
|
|
9580
10639
|
);
|
|
9581
10640
|
if (result.blockedByLabel) {
|
|
9582
|
-
console.log(
|
|
10641
|
+
console.log(import_chalk18.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
9583
10642
|
}
|
|
9584
10643
|
}
|
|
9585
10644
|
console.log("");
|
|
@@ -9593,7 +10652,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
9593
10652
|
try {
|
|
9594
10653
|
await startTail2(options);
|
|
9595
10654
|
} catch (err) {
|
|
9596
|
-
console.error(
|
|
10655
|
+
console.error(import_chalk18.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
9597
10656
|
process.exit(1);
|
|
9598
10657
|
}
|
|
9599
10658
|
});
|
|
@@ -9601,11 +10660,15 @@ registerWatchCommand(program);
|
|
|
9601
10660
|
registerMcpGatewayCommand(program);
|
|
9602
10661
|
registerCheckCommand(program);
|
|
9603
10662
|
registerLogCommand(program);
|
|
10663
|
+
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").action(async () => {
|
|
10664
|
+
const { main: main2 } = await Promise.resolve().then(() => (init_hud(), hud_exports));
|
|
10665
|
+
await main2();
|
|
10666
|
+
});
|
|
9604
10667
|
program.command("pause").description("Temporarily disable Node9 protection for a set duration").option("-d, --duration <duration>", "How long to pause (e.g. 15m, 1h, 30s)", "15m").action((options) => {
|
|
9605
10668
|
const ms = parseDuration(options.duration);
|
|
9606
10669
|
if (ms === null) {
|
|
9607
10670
|
console.error(
|
|
9608
|
-
|
|
10671
|
+
import_chalk18.default.red(`
|
|
9609
10672
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
9610
10673
|
`)
|
|
9611
10674
|
);
|
|
@@ -9613,20 +10676,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
9613
10676
|
}
|
|
9614
10677
|
pauseNode9(ms, options.duration);
|
|
9615
10678
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
9616
|
-
console.log(
|
|
10679
|
+
console.log(import_chalk18.default.yellow(`
|
|
9617
10680
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
9618
|
-
console.log(
|
|
9619
|
-
console.log(
|
|
10681
|
+
console.log(import_chalk18.default.gray(` All tool calls will be allowed without review.`));
|
|
10682
|
+
console.log(import_chalk18.default.gray(` Run "node9 resume" to re-enable early.
|
|
9620
10683
|
`));
|
|
9621
10684
|
});
|
|
9622
10685
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
9623
10686
|
const { paused } = checkPause();
|
|
9624
10687
|
if (!paused) {
|
|
9625
|
-
console.log(
|
|
10688
|
+
console.log(import_chalk18.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
9626
10689
|
return;
|
|
9627
10690
|
}
|
|
9628
10691
|
resumeNode9();
|
|
9629
|
-
console.log(
|
|
10692
|
+
console.log(import_chalk18.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
9630
10693
|
});
|
|
9631
10694
|
var HOOK_BASED_AGENTS = {
|
|
9632
10695
|
claude: "claude",
|
|
@@ -9639,15 +10702,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
9639
10702
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
9640
10703
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
9641
10704
|
console.error(
|
|
9642
|
-
|
|
10705
|
+
import_chalk18.default.yellow(`
|
|
9643
10706
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
9644
10707
|
);
|
|
9645
|
-
console.error(
|
|
10708
|
+
console.error(import_chalk18.default.white(`
|
|
9646
10709
|
"${target}" uses its own hook system. Use:`));
|
|
9647
10710
|
console.error(
|
|
9648
|
-
|
|
10711
|
+
import_chalk18.default.green(` node9 addto ${target} `) + import_chalk18.default.gray("# one-time setup")
|
|
9649
10712
|
);
|
|
9650
|
-
console.error(
|
|
10713
|
+
console.error(import_chalk18.default.green(` ${target} `) + import_chalk18.default.gray("# run normally"));
|
|
9651
10714
|
process.exit(1);
|
|
9652
10715
|
}
|
|
9653
10716
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -9664,12 +10727,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
9664
10727
|
}
|
|
9665
10728
|
);
|
|
9666
10729
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
9667
|
-
console.error(
|
|
10730
|
+
console.error(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
9668
10731
|
const daemonReady = await autoStartDaemonAndWait();
|
|
9669
10732
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
9670
10733
|
}
|
|
9671
10734
|
if (result.noApprovalMechanism && process.stdout.isTTY) {
|
|
9672
|
-
const approved = await (0,
|
|
10735
|
+
const approved = await (0, import_prompts2.confirm)({
|
|
9673
10736
|
message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
|
|
9674
10737
|
default: false
|
|
9675
10738
|
});
|
|
@@ -9677,12 +10740,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
9677
10740
|
}
|
|
9678
10741
|
if (!result.approved) {
|
|
9679
10742
|
console.error(
|
|
9680
|
-
|
|
10743
|
+
import_chalk18.default.red(`
|
|
9681
10744
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
9682
10745
|
);
|
|
9683
10746
|
process.exit(1);
|
|
9684
10747
|
}
|
|
9685
|
-
console.error(
|
|
10748
|
+
console.error(import_chalk18.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
9686
10749
|
await runProxy(fullCommand);
|
|
9687
10750
|
} else {
|
|
9688
10751
|
program.help();
|
|
@@ -9697,9 +10760,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
9697
10760
|
const isCheckHook = process.argv[2] === "check";
|
|
9698
10761
|
if (isCheckHook) {
|
|
9699
10762
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
9700
|
-
const logPath =
|
|
10763
|
+
const logPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log");
|
|
9701
10764
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
9702
|
-
|
|
10765
|
+
import_fs26.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
9703
10766
|
`);
|
|
9704
10767
|
}
|
|
9705
10768
|
process.exit(0);
|