@node9/proxy 1.5.1 → 1.5.3
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 +60 -7
- package/dist/cli.js +945 -190
- package/dist/cli.mjs +942 -187
- package/dist/index.js +156 -47
- package/dist/index.mjs +156 -47
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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);
|
|
@@ -1910,7 +1924,13 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
|
|
|
1910
1924
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
1911
1925
|
reason: matchedRule.reason,
|
|
1912
1926
|
tier: 2,
|
|
1913
|
-
ruleName: matchedRule.name ?? matchedRule.tool
|
|
1927
|
+
ruleName: matchedRule.name ?? matchedRule.tool,
|
|
1928
|
+
...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
|
|
1929
|
+
dependsOnStatePredicates: matchedRule.dependsOnState
|
|
1930
|
+
},
|
|
1931
|
+
...matchedRule.verdict === "block" && matchedRule.recoveryCommand && {
|
|
1932
|
+
recoveryCommand: matchedRule.recoveryCommand
|
|
1933
|
+
}
|
|
1914
1934
|
};
|
|
1915
1935
|
}
|
|
1916
1936
|
}
|
|
@@ -2437,6 +2457,34 @@ var init_state = __esm({
|
|
|
2437
2457
|
});
|
|
2438
2458
|
|
|
2439
2459
|
// src/auth/daemon.ts
|
|
2460
|
+
function notifyActivitySocket(data) {
|
|
2461
|
+
return new Promise((resolve) => {
|
|
2462
|
+
try {
|
|
2463
|
+
const payload = JSON.stringify(data);
|
|
2464
|
+
const sock = import_net.default.createConnection(ACTIVITY_SOCKET_PATH);
|
|
2465
|
+
sock.on("connect", () => {
|
|
2466
|
+
sock.on("close", resolve);
|
|
2467
|
+
sock.end(payload);
|
|
2468
|
+
});
|
|
2469
|
+
sock.on("error", resolve);
|
|
2470
|
+
} catch {
|
|
2471
|
+
resolve();
|
|
2472
|
+
}
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
async function checkStatePredicates(predicates) {
|
|
2476
|
+
if (predicates.length === 0) return {};
|
|
2477
|
+
try {
|
|
2478
|
+
const qs = predicates.map(encodeURIComponent).join(",");
|
|
2479
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/state/check?predicates=${qs}`, {
|
|
2480
|
+
signal: AbortSignal.timeout(100)
|
|
2481
|
+
});
|
|
2482
|
+
if (!res.ok) return null;
|
|
2483
|
+
return await res.json();
|
|
2484
|
+
} catch {
|
|
2485
|
+
return null;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2440
2488
|
function getInternalToken() {
|
|
2441
2489
|
try {
|
|
2442
2490
|
const pidFile = import_path10.default.join(import_os8.default.homedir(), ".node9", "daemon.pid");
|
|
@@ -2470,7 +2518,7 @@ function isDaemonRunning() {
|
|
|
2470
2518
|
return false;
|
|
2471
2519
|
}
|
|
2472
2520
|
}
|
|
2473
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd) {
|
|
2521
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly) {
|
|
2474
2522
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2475
2523
|
const ctrl = new AbortController();
|
|
2476
2524
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -2488,7 +2536,10 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
2488
2536
|
// activity-result as the CLI used for the pending activity event.
|
|
2489
2537
|
activityId,
|
|
2490
2538
|
...riskMetadata && { riskMetadata },
|
|
2491
|
-
...cwd && { cwd }
|
|
2539
|
+
...cwd && { cwd },
|
|
2540
|
+
...recoveryCommand && { recoveryCommand },
|
|
2541
|
+
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
2542
|
+
...viewOnly && { viewOnly: true }
|
|
2492
2543
|
}),
|
|
2493
2544
|
signal: ctrl.signal
|
|
2494
2545
|
});
|
|
@@ -2508,10 +2559,10 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
2508
2559
|
try {
|
|
2509
2560
|
const waitRes = await fetch(`${base}/wait/${id}`, { signal: waitCtrl.signal });
|
|
2510
2561
|
if (!waitRes.ok) return { decision: "deny" };
|
|
2511
|
-
const { decision, source } = await waitRes.json();
|
|
2562
|
+
const { decision, source, reason } = await waitRes.json();
|
|
2512
2563
|
if (decision === "allow") return { decision: "allow", source };
|
|
2513
2564
|
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
2514
|
-
return { decision: "deny", source };
|
|
2565
|
+
return { decision: "deny", source, reason };
|
|
2515
2566
|
} finally {
|
|
2516
2567
|
clearTimeout(waitTimer);
|
|
2517
2568
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -2597,14 +2648,16 @@ async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
|
2597
2648
|
signal: AbortSignal.timeout(3e3)
|
|
2598
2649
|
});
|
|
2599
2650
|
}
|
|
2600
|
-
var import_fs9, import_path10, import_os8, import_child_process, DAEMON_PORT, DAEMON_HOST;
|
|
2651
|
+
var import_fs9, import_net, import_path10, import_os8, import_child_process, ACTIVITY_SOCKET_PATH, DAEMON_PORT, DAEMON_HOST;
|
|
2601
2652
|
var init_daemon = __esm({
|
|
2602
2653
|
"src/auth/daemon.ts"() {
|
|
2603
2654
|
"use strict";
|
|
2604
2655
|
import_fs9 = __toESM(require("fs"));
|
|
2656
|
+
import_net = __toESM(require("net"));
|
|
2605
2657
|
import_path10 = __toESM(require("path"));
|
|
2606
2658
|
import_os8 = __toESM(require("os"));
|
|
2607
2659
|
import_child_process = require("child_process");
|
|
2660
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path10.default.join(import_os8.default.tmpdir(), "node9-activity.sock");
|
|
2608
2661
|
DAEMON_PORT = 7391;
|
|
2609
2662
|
DAEMON_HOST = "127.0.0.1";
|
|
2610
2663
|
}
|
|
@@ -3094,19 +3147,7 @@ function isNetworkTool(toolName, args) {
|
|
|
3094
3147
|
return false;
|
|
3095
3148
|
}
|
|
3096
3149
|
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
|
-
});
|
|
3150
|
+
return notifyActivitySocket(data);
|
|
3110
3151
|
}
|
|
3111
3152
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
3112
3153
|
if (!options?.calledFromDaemon) {
|
|
@@ -3123,7 +3164,9 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
3123
3164
|
tool: toolName,
|
|
3124
3165
|
ts: actTs,
|
|
3125
3166
|
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
|
|
3126
|
-
label: result.blockedByLabel
|
|
3167
|
+
label: result.blockedByLabel,
|
|
3168
|
+
ruleHit: result.ruleHit,
|
|
3169
|
+
observeWouldBlock: result.observeWouldBlock
|
|
3127
3170
|
});
|
|
3128
3171
|
}
|
|
3129
3172
|
return result;
|
|
@@ -3150,10 +3193,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3150
3193
|
appendHookDebug(toolName, args, meta, hashAuditArgs);
|
|
3151
3194
|
}
|
|
3152
3195
|
const isManual = meta?.agent === "Terminal";
|
|
3196
|
+
const isObserveMode = config.settings.mode === "observe";
|
|
3153
3197
|
let explainableLabel = "Local Config";
|
|
3154
3198
|
let policyMatchedField;
|
|
3155
3199
|
let policyMatchedWord;
|
|
3156
3200
|
let riskMetadata;
|
|
3201
|
+
let statefulRecoveryCommand;
|
|
3157
3202
|
let taintWarning = null;
|
|
3158
3203
|
if (isNetworkTool(toolName, args)) {
|
|
3159
3204
|
const filePaths = extractFilePaths(toolName, args);
|
|
@@ -3174,10 +3219,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3174
3219
|
if (dlpMatch) {
|
|
3175
3220
|
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
3176
3221
|
if (dlpMatch.severity === "block") {
|
|
3177
|
-
if (!isManual)
|
|
3222
|
+
if (!isManual)
|
|
3223
|
+
appendLocalAudit(
|
|
3224
|
+
toolName,
|
|
3225
|
+
args,
|
|
3226
|
+
"deny",
|
|
3227
|
+
isObserveMode ? "observe-mode-dlp-would-block" : "dlp-block",
|
|
3228
|
+
meta,
|
|
3229
|
+
true
|
|
3230
|
+
);
|
|
3178
3231
|
if (isWriteTool(toolName) && filePath) {
|
|
3179
3232
|
await notifyTaint(filePath, `DLP:${dlpMatch.patternName}`);
|
|
3180
3233
|
}
|
|
3234
|
+
if (isObserveMode) {
|
|
3235
|
+
return {
|
|
3236
|
+
approved: true,
|
|
3237
|
+
checkedBy: "audit",
|
|
3238
|
+
observeWouldBlock: true,
|
|
3239
|
+
blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3181
3242
|
return {
|
|
3182
3243
|
approved: false,
|
|
3183
3244
|
reason: dlpReason,
|
|
@@ -3190,6 +3251,31 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3190
3251
|
explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
|
|
3191
3252
|
}
|
|
3192
3253
|
}
|
|
3254
|
+
if (isObserveMode) {
|
|
3255
|
+
if (!isIgnoredTool(toolName)) {
|
|
3256
|
+
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
3257
|
+
const wouldBlock = policyResult.decision === "block";
|
|
3258
|
+
if (!isManual)
|
|
3259
|
+
appendLocalAudit(
|
|
3260
|
+
toolName,
|
|
3261
|
+
args,
|
|
3262
|
+
"allow",
|
|
3263
|
+
wouldBlock ? "observe-mode-would-block" : "observe-mode",
|
|
3264
|
+
meta,
|
|
3265
|
+
hashAuditArgs
|
|
3266
|
+
);
|
|
3267
|
+
return {
|
|
3268
|
+
approved: true,
|
|
3269
|
+
checkedBy: "audit",
|
|
3270
|
+
...wouldBlock && {
|
|
3271
|
+
observeWouldBlock: true,
|
|
3272
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
3273
|
+
ruleHit: policyResult.ruleName
|
|
3274
|
+
}
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
return { approved: true, checkedBy: "audit" };
|
|
3278
|
+
}
|
|
3193
3279
|
if (config.settings.mode === "audit") {
|
|
3194
3280
|
if (!isIgnoredTool(toolName)) {
|
|
3195
3281
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
@@ -3217,14 +3303,34 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3217
3303
|
return { approved: true, checkedBy: "local-policy" };
|
|
3218
3304
|
}
|
|
3219
3305
|
if (policyResult.decision === "block") {
|
|
3220
|
-
if (
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3306
|
+
if (policyResult.dependsOnStatePredicates?.length) {
|
|
3307
|
+
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
3308
|
+
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
3309
|
+
if (stateResults === null && !isManual) {
|
|
3310
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
3311
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3312
|
+
event: "state-check-fail-open",
|
|
3313
|
+
tool: toolName,
|
|
3314
|
+
rule: policyResult.ruleName,
|
|
3315
|
+
predicates: policyResult.dependsOnStatePredicates,
|
|
3316
|
+
reason: "daemon unreachable or /state/check timed out \u2014 block rule downgraded to review"
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
if (predicatesMet && policyResult.recoveryCommand) {
|
|
3320
|
+
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
3321
|
+
}
|
|
3322
|
+
} else {
|
|
3323
|
+
if (!isManual)
|
|
3324
|
+
appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
|
|
3325
|
+
return {
|
|
3326
|
+
approved: false,
|
|
3327
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
3328
|
+
blockedBy: "local-config",
|
|
3329
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
3330
|
+
ruleHit: policyResult.ruleName,
|
|
3331
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3228
3334
|
}
|
|
3229
3335
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
3230
3336
|
policyMatchedField = policyResult.matchedField;
|
|
@@ -3331,7 +3437,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3331
3437
|
meta,
|
|
3332
3438
|
riskMetadata,
|
|
3333
3439
|
options?.activityId,
|
|
3334
|
-
options?.cwd
|
|
3440
|
+
options?.cwd,
|
|
3441
|
+
statefulRecoveryCommand
|
|
3335
3442
|
);
|
|
3336
3443
|
daemonEntryId = entry.id;
|
|
3337
3444
|
daemonAllowCount = entry.allowCount;
|
|
@@ -3392,20 +3499,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3392
3499
|
if (daemonEntryId && (approvers.browser || approvers.terminal)) {
|
|
3393
3500
|
racePromises.push(
|
|
3394
3501
|
(async () => {
|
|
3395
|
-
const {
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3502
|
+
const {
|
|
3503
|
+
decision: daemonDecision,
|
|
3504
|
+
source: decisionSource,
|
|
3505
|
+
reason: daemonReason
|
|
3506
|
+
} = await waitForDaemonDecision(daemonEntryId, signal);
|
|
3399
3507
|
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
3400
3508
|
const isApproved = daemonDecision === "allow";
|
|
3401
|
-
const
|
|
3509
|
+
const isRedirect = decisionSource === "terminal-redirect";
|
|
3510
|
+
const src = decisionSource === "terminal" || decisionSource === "terminal-redirect" || decisionSource === "browser" ? decisionSource === "browser" ? "browser" : "terminal" : approvers.browser ? "browser" : "terminal";
|
|
3402
3511
|
const via = src === "terminal" ? "Terminal (node9 tail)" : "Browser Dashboard";
|
|
3403
3512
|
return {
|
|
3404
3513
|
approved: isApproved,
|
|
3405
|
-
reason: isApproved ? void 0 :
|
|
3514
|
+
reason: isApproved ? void 0 : (
|
|
3515
|
+
// Use the redirect reason from the tail when choice [2] was selected;
|
|
3516
|
+
// otherwise fall back to the generic rejection message.
|
|
3517
|
+
isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`
|
|
3518
|
+
),
|
|
3406
3519
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
3407
3520
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
3408
|
-
blockedByLabel: `User Decision (${via})`,
|
|
3521
|
+
blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
|
|
3409
3522
|
decisionSource: src
|
|
3410
3523
|
};
|
|
3411
3524
|
})()
|
|
@@ -3480,13 +3593,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3480
3593
|
}
|
|
3481
3594
|
return finalResult;
|
|
3482
3595
|
}
|
|
3483
|
-
var
|
|
3596
|
+
var import_crypto3, WRITE_TOOLS;
|
|
3484
3597
|
var init_orchestrator = __esm({
|
|
3485
3598
|
"src/auth/orchestrator.ts"() {
|
|
3486
3599
|
"use strict";
|
|
3487
|
-
import_net = __toESM(require("net"));
|
|
3488
|
-
import_path13 = __toESM(require("path"));
|
|
3489
|
-
import_os10 = __toESM(require("os"));
|
|
3490
3600
|
import_crypto3 = require("crypto");
|
|
3491
3601
|
init_native();
|
|
3492
3602
|
init_context_sniper();
|
|
@@ -3508,7 +3618,6 @@ var init_orchestrator = __esm({
|
|
|
3508
3618
|
"notebook_edit",
|
|
3509
3619
|
"notebookedit"
|
|
3510
3620
|
]);
|
|
3511
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os10.default.tmpdir(), "node9-activity.sock");
|
|
3512
3621
|
}
|
|
3513
3622
|
});
|
|
3514
3623
|
|
|
@@ -5235,12 +5344,12 @@ var init_suggestion_tracker = __esm({
|
|
|
5235
5344
|
});
|
|
5236
5345
|
|
|
5237
5346
|
// src/daemon/taint-store.ts
|
|
5238
|
-
var import_fs12,
|
|
5347
|
+
var import_fs12, import_path14, DEFAULT_TTL_MS, TaintStore;
|
|
5239
5348
|
var init_taint_store = __esm({
|
|
5240
5349
|
"src/daemon/taint-store.ts"() {
|
|
5241
5350
|
"use strict";
|
|
5242
5351
|
import_fs12 = __toESM(require("fs"));
|
|
5243
|
-
|
|
5352
|
+
import_path14 = __toESM(require("path"));
|
|
5244
5353
|
DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
5245
5354
|
TaintStore = class {
|
|
5246
5355
|
records = /* @__PURE__ */ new Map();
|
|
@@ -5305,15 +5414,116 @@ var init_taint_store = __esm({
|
|
|
5305
5414
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
5306
5415
|
_resolve(filePath) {
|
|
5307
5416
|
try {
|
|
5308
|
-
return import_fs12.default.realpathSync.native(
|
|
5417
|
+
return import_fs12.default.realpathSync.native(import_path14.default.resolve(filePath));
|
|
5309
5418
|
} catch {
|
|
5310
|
-
return
|
|
5419
|
+
return import_path14.default.resolve(filePath);
|
|
5311
5420
|
}
|
|
5312
5421
|
}
|
|
5313
5422
|
};
|
|
5314
5423
|
}
|
|
5315
5424
|
});
|
|
5316
5425
|
|
|
5426
|
+
// src/daemon/session-counters.ts
|
|
5427
|
+
var SessionCounters, sessionCounters;
|
|
5428
|
+
var init_session_counters = __esm({
|
|
5429
|
+
"src/daemon/session-counters.ts"() {
|
|
5430
|
+
"use strict";
|
|
5431
|
+
SessionCounters = class {
|
|
5432
|
+
_allowed = 0;
|
|
5433
|
+
_blocked = 0;
|
|
5434
|
+
_dlpHits = 0;
|
|
5435
|
+
_wouldBlock = 0;
|
|
5436
|
+
_lastRuleHit = null;
|
|
5437
|
+
_lastBlockedTool = null;
|
|
5438
|
+
incrementAllowed() {
|
|
5439
|
+
this._allowed++;
|
|
5440
|
+
}
|
|
5441
|
+
incrementBlocked() {
|
|
5442
|
+
this._blocked++;
|
|
5443
|
+
}
|
|
5444
|
+
incrementDlpHits() {
|
|
5445
|
+
this._dlpHits++;
|
|
5446
|
+
}
|
|
5447
|
+
incrementWouldBlock() {
|
|
5448
|
+
this._wouldBlock++;
|
|
5449
|
+
}
|
|
5450
|
+
recordRuleHit(label) {
|
|
5451
|
+
this._lastRuleHit = label;
|
|
5452
|
+
}
|
|
5453
|
+
recordBlockedTool(toolName) {
|
|
5454
|
+
this._lastBlockedTool = toolName;
|
|
5455
|
+
}
|
|
5456
|
+
get() {
|
|
5457
|
+
return {
|
|
5458
|
+
allowed: this._allowed,
|
|
5459
|
+
blocked: this._blocked,
|
|
5460
|
+
dlpHits: this._dlpHits,
|
|
5461
|
+
wouldBlock: this._wouldBlock,
|
|
5462
|
+
lastRuleHit: this._lastRuleHit,
|
|
5463
|
+
lastBlockedTool: this._lastBlockedTool
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
reset() {
|
|
5467
|
+
this._allowed = 0;
|
|
5468
|
+
this._blocked = 0;
|
|
5469
|
+
this._dlpHits = 0;
|
|
5470
|
+
this._wouldBlock = 0;
|
|
5471
|
+
this._lastRuleHit = null;
|
|
5472
|
+
this._lastBlockedTool = null;
|
|
5473
|
+
}
|
|
5474
|
+
};
|
|
5475
|
+
sessionCounters = new SessionCounters();
|
|
5476
|
+
}
|
|
5477
|
+
});
|
|
5478
|
+
|
|
5479
|
+
// src/daemon/session-history.ts
|
|
5480
|
+
var SessionHistory, sessionHistory;
|
|
5481
|
+
var init_session_history = __esm({
|
|
5482
|
+
"src/daemon/session-history.ts"() {
|
|
5483
|
+
"use strict";
|
|
5484
|
+
SessionHistory = class {
|
|
5485
|
+
lastEditAt = null;
|
|
5486
|
+
lastTestPassAt = null;
|
|
5487
|
+
lastTestFailAt = null;
|
|
5488
|
+
recordEdit(ts = Date.now()) {
|
|
5489
|
+
this.lastEditAt = ts;
|
|
5490
|
+
}
|
|
5491
|
+
recordTestPass(ts = Date.now()) {
|
|
5492
|
+
this.lastTestPassAt = ts;
|
|
5493
|
+
}
|
|
5494
|
+
recordTestFail(ts = Date.now()) {
|
|
5495
|
+
this.lastTestFailAt = ts;
|
|
5496
|
+
}
|
|
5497
|
+
/**
|
|
5498
|
+
* Returns true when the named predicate is currently satisfied.
|
|
5499
|
+
* Unknown predicates always return false (fail-open: don't block on unknown state).
|
|
5500
|
+
*/
|
|
5501
|
+
checkPredicate(name) {
|
|
5502
|
+
switch (name) {
|
|
5503
|
+
case "no_test_passed_since_last_edit":
|
|
5504
|
+
if (this.lastEditAt === null) return false;
|
|
5505
|
+
return this.lastTestPassAt === null || this.lastTestPassAt < this.lastEditAt;
|
|
5506
|
+
default:
|
|
5507
|
+
return false;
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
getSnapshot() {
|
|
5511
|
+
return {
|
|
5512
|
+
lastEditAt: this.lastEditAt,
|
|
5513
|
+
lastTestPassAt: this.lastTestPassAt,
|
|
5514
|
+
lastTestFailAt: this.lastTestFailAt
|
|
5515
|
+
};
|
|
5516
|
+
}
|
|
5517
|
+
reset() {
|
|
5518
|
+
this.lastEditAt = null;
|
|
5519
|
+
this.lastTestPassAt = null;
|
|
5520
|
+
this.lastTestFailAt = null;
|
|
5521
|
+
}
|
|
5522
|
+
};
|
|
5523
|
+
sessionHistory = new SessionHistory();
|
|
5524
|
+
}
|
|
5525
|
+
});
|
|
5526
|
+
|
|
5317
5527
|
// src/daemon/state.ts
|
|
5318
5528
|
function loadInsightCounts() {
|
|
5319
5529
|
try {
|
|
@@ -5357,7 +5567,7 @@ function markRejectionHandlerRegistered() {
|
|
|
5357
5567
|
daemonRejectionHandlerRegistered = true;
|
|
5358
5568
|
}
|
|
5359
5569
|
function atomicWriteSync2(filePath, data, options) {
|
|
5360
|
-
const dir =
|
|
5570
|
+
const dir = import_path15.default.dirname(filePath);
|
|
5361
5571
|
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
5362
5572
|
const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
|
|
5363
5573
|
try {
|
|
@@ -5397,7 +5607,7 @@ function appendAuditLog(data) {
|
|
|
5397
5607
|
decision: data.decision,
|
|
5398
5608
|
source: "daemon"
|
|
5399
5609
|
};
|
|
5400
|
-
const dir =
|
|
5610
|
+
const dir = import_path15.default.dirname(AUDIT_LOG_FILE);
|
|
5401
5611
|
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
5402
5612
|
import_fs13.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
5403
5613
|
} catch {
|
|
@@ -5546,6 +5756,14 @@ function startActivitySocket() {
|
|
|
5546
5756
|
socket.on("end", () => {
|
|
5547
5757
|
try {
|
|
5548
5758
|
const data = JSON.parse(Buffer.concat(chunks).toString());
|
|
5759
|
+
if (data.status === "test_pass") {
|
|
5760
|
+
sessionHistory.recordTestPass(data.ts);
|
|
5761
|
+
return;
|
|
5762
|
+
}
|
|
5763
|
+
if (data.status === "test_fail") {
|
|
5764
|
+
sessionHistory.recordTestFail(data.ts);
|
|
5765
|
+
return;
|
|
5766
|
+
}
|
|
5549
5767
|
if (data.status === "pending") {
|
|
5550
5768
|
broadcast("activity", {
|
|
5551
5769
|
id: data.id,
|
|
@@ -5555,6 +5773,24 @@ function startActivitySocket() {
|
|
|
5555
5773
|
status: "pending"
|
|
5556
5774
|
});
|
|
5557
5775
|
} else {
|
|
5776
|
+
if (data.status === "allow") {
|
|
5777
|
+
sessionCounters.incrementAllowed();
|
|
5778
|
+
if (data.observeWouldBlock) sessionCounters.incrementWouldBlock();
|
|
5779
|
+
if (WRITE_TOOL_NAMES.has(data.tool.toLowerCase().replace(/[^a-z_]/g, "_"))) {
|
|
5780
|
+
sessionHistory.recordEdit(data.ts);
|
|
5781
|
+
}
|
|
5782
|
+
} else if (data.status === "block") {
|
|
5783
|
+
sessionCounters.incrementBlocked();
|
|
5784
|
+
sessionCounters.recordBlockedTool(data.tool);
|
|
5785
|
+
if (data.ruleHit) sessionCounters.recordRuleHit(data.ruleHit);
|
|
5786
|
+
} else if (data.status === "dlp") {
|
|
5787
|
+
sessionCounters.incrementBlocked();
|
|
5788
|
+
sessionCounters.incrementDlpHits();
|
|
5789
|
+
sessionCounters.recordBlockedTool(data.tool);
|
|
5790
|
+
} else if (data.status === "taint") {
|
|
5791
|
+
sessionCounters.incrementBlocked();
|
|
5792
|
+
sessionCounters.recordBlockedTool(data.tool);
|
|
5793
|
+
}
|
|
5558
5794
|
broadcast("activity-result", {
|
|
5559
5795
|
id: data.id,
|
|
5560
5796
|
status: data.status,
|
|
@@ -5575,27 +5811,29 @@ function startActivitySocket() {
|
|
|
5575
5811
|
}
|
|
5576
5812
|
});
|
|
5577
5813
|
}
|
|
5578
|
-
var import_net2, import_fs13,
|
|
5814
|
+
var import_net2, import_fs13, import_path15, 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
5815
|
var init_state2 = __esm({
|
|
5580
5816
|
"src/daemon/state.ts"() {
|
|
5581
5817
|
"use strict";
|
|
5582
5818
|
import_net2 = __toESM(require("net"));
|
|
5583
5819
|
import_fs13 = __toESM(require("fs"));
|
|
5584
|
-
|
|
5585
|
-
|
|
5820
|
+
import_path15 = __toESM(require("path"));
|
|
5821
|
+
import_os11 = __toESM(require("os"));
|
|
5586
5822
|
import_child_process3 = require("child_process");
|
|
5587
5823
|
import_crypto5 = require("crypto");
|
|
5588
5824
|
init_daemon();
|
|
5589
5825
|
init_suggestion_tracker();
|
|
5590
5826
|
init_taint_store();
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5827
|
+
init_session_counters();
|
|
5828
|
+
init_session_history();
|
|
5829
|
+
homeDir = import_os11.default.homedir();
|
|
5830
|
+
DAEMON_PID_FILE = import_path15.default.join(homeDir, ".node9", "daemon.pid");
|
|
5831
|
+
DECISIONS_FILE = import_path15.default.join(homeDir, ".node9", "decisions.json");
|
|
5832
|
+
AUDIT_LOG_FILE = import_path15.default.join(homeDir, ".node9", "audit.log");
|
|
5833
|
+
TRUST_FILE2 = import_path15.default.join(homeDir, ".node9", "trust.json");
|
|
5834
|
+
GLOBAL_CONFIG_FILE = import_path15.default.join(homeDir, ".node9", "config.json");
|
|
5835
|
+
CREDENTIALS_FILE = import_path15.default.join(homeDir, ".node9", "credentials.json");
|
|
5836
|
+
INSIGHT_COUNTS_FILE = import_path15.default.join(homeDir, ".node9", "insight-counts.json");
|
|
5599
5837
|
pending = /* @__PURE__ */ new Map();
|
|
5600
5838
|
sseClients = /* @__PURE__ */ new Set();
|
|
5601
5839
|
suggestionTracker = new SuggestionTracker(3);
|
|
@@ -5613,10 +5851,21 @@ var init_state2 = __esm({
|
|
|
5613
5851
|
"2h": 2 * 60 * 6e4
|
|
5614
5852
|
};
|
|
5615
5853
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5616
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
5854
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path15.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
|
|
5617
5855
|
ACTIVITY_RING_SIZE = 100;
|
|
5618
5856
|
activityRing = [];
|
|
5619
5857
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
5858
|
+
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
5859
|
+
"write",
|
|
5860
|
+
"write_file",
|
|
5861
|
+
"create_file",
|
|
5862
|
+
"edit",
|
|
5863
|
+
"multiedit",
|
|
5864
|
+
"str_replace_based_edit_tool",
|
|
5865
|
+
"replace",
|
|
5866
|
+
"notebook_edit",
|
|
5867
|
+
"notebookedit"
|
|
5868
|
+
]);
|
|
5620
5869
|
}
|
|
5621
5870
|
});
|
|
5622
5871
|
|
|
@@ -5644,7 +5893,7 @@ function patchConfig(configPath, patch) {
|
|
|
5644
5893
|
ignored.push(patch.toolName);
|
|
5645
5894
|
}
|
|
5646
5895
|
}
|
|
5647
|
-
const dir =
|
|
5896
|
+
const dir = import_path16.default.dirname(configPath);
|
|
5648
5897
|
import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5649
5898
|
const tmp = configPath + ".node9-tmp";
|
|
5650
5899
|
try {
|
|
@@ -5666,14 +5915,14 @@ function patchConfig(configPath, patch) {
|
|
|
5666
5915
|
throw err;
|
|
5667
5916
|
}
|
|
5668
5917
|
}
|
|
5669
|
-
var import_fs14,
|
|
5918
|
+
var import_fs14, import_path16, import_os12, GLOBAL_CONFIG_PATH;
|
|
5670
5919
|
var init_patch = __esm({
|
|
5671
5920
|
"src/config/patch.ts"() {
|
|
5672
5921
|
"use strict";
|
|
5673
5922
|
import_fs14 = __toESM(require("fs"));
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
GLOBAL_CONFIG_PATH =
|
|
5923
|
+
import_path16 = __toESM(require("path"));
|
|
5924
|
+
import_os12 = __toESM(require("os"));
|
|
5925
|
+
GLOBAL_CONFIG_PATH = import_path16.default.join(import_os12.default.homedir(), ".node9", "config.json");
|
|
5677
5926
|
}
|
|
5678
5927
|
});
|
|
5679
5928
|
|
|
@@ -5740,6 +5989,8 @@ data: ${JSON.stringify({
|
|
|
5740
5989
|
toolName: e.toolName,
|
|
5741
5990
|
args: e.args,
|
|
5742
5991
|
riskMetadata: e.riskMetadata,
|
|
5992
|
+
...e.recoveryCommand && { recoveryCommand: e.recoveryCommand },
|
|
5993
|
+
...e.viewOnly && { viewOnly: true },
|
|
5743
5994
|
slackDelegated: e.slackDelegated,
|
|
5744
5995
|
timestamp: e.timestamp,
|
|
5745
5996
|
agent: e.agent,
|
|
@@ -5805,6 +6056,9 @@ data: ${JSON.stringify(item.data)}
|
|
|
5805
6056
|
agent,
|
|
5806
6057
|
mcpServer,
|
|
5807
6058
|
riskMetadata,
|
|
6059
|
+
recoveryCommand,
|
|
6060
|
+
skipBackgroundAuth = false,
|
|
6061
|
+
viewOnly = false,
|
|
5808
6062
|
fromCLI = false,
|
|
5809
6063
|
activityId,
|
|
5810
6064
|
cwd
|
|
@@ -5815,6 +6069,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
5815
6069
|
toolName,
|
|
5816
6070
|
args,
|
|
5817
6071
|
riskMetadata: riskMetadata ?? void 0,
|
|
6072
|
+
...typeof recoveryCommand === "string" && recoveryCommand && { recoveryCommand },
|
|
6073
|
+
...viewOnly && { viewOnly: true },
|
|
5818
6074
|
agent: typeof agent === "string" ? agent : void 0,
|
|
5819
6075
|
mcpServer: typeof mcpServer === "string" ? mcpServer : void 0,
|
|
5820
6076
|
slackDelegated: !!slackDelegated,
|
|
@@ -5849,7 +6105,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
5849
6105
|
status: "pending"
|
|
5850
6106
|
});
|
|
5851
6107
|
}
|
|
5852
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6108
|
+
const projectCwd = typeof cwd === "string" && import_path17.default.isAbsolute(cwd) ? cwd : void 0;
|
|
5853
6109
|
const projectConfig = getConfig(projectCwd);
|
|
5854
6110
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
5855
6111
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -5859,6 +6115,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
5859
6115
|
toolName,
|
|
5860
6116
|
args,
|
|
5861
6117
|
riskMetadata: entry.riskMetadata,
|
|
6118
|
+
...entry.recoveryCommand && { recoveryCommand: entry.recoveryCommand },
|
|
6119
|
+
...entry.viewOnly && { viewOnly: true },
|
|
5862
6120
|
slackDelegated: entry.slackDelegated,
|
|
5863
6121
|
agent: entry.agent,
|
|
5864
6122
|
mcpServer: entry.mcpServer,
|
|
@@ -5875,7 +6133,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
5875
6133
|
}
|
|
5876
6134
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5877
6135
|
res.end(JSON.stringify({ id, allowCount: (insightCounts.get(toolName) ?? 0) + 1 }));
|
|
5878
|
-
if (slackDelegated) return;
|
|
6136
|
+
if (slackDelegated || skipBackgroundAuth) return;
|
|
5879
6137
|
authorizeHeadless(
|
|
5880
6138
|
toolName,
|
|
5881
6139
|
args,
|
|
@@ -6014,7 +6272,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6014
6272
|
saveInsightCounts();
|
|
6015
6273
|
suggestionTracker.resetTool(entry.toolName);
|
|
6016
6274
|
}
|
|
6017
|
-
const VALID_SOURCES = /* @__PURE__ */ new Set(["terminal", "browser", "native"]);
|
|
6275
|
+
const VALID_SOURCES = /* @__PURE__ */ new Set(["terminal", "browser", "native", "terminal-redirect"]);
|
|
6018
6276
|
if (source && VALID_SOURCES.has(source)) entry.decisionSource = source;
|
|
6019
6277
|
if (entry.waiter) {
|
|
6020
6278
|
entry.waiter(resolvedDecision, reason);
|
|
@@ -6043,6 +6301,41 @@ data: ${JSON.stringify(item.data)}
|
|
|
6043
6301
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
6044
6302
|
}
|
|
6045
6303
|
}
|
|
6304
|
+
if (req.method === "GET" && pathname === "/status") {
|
|
6305
|
+
try {
|
|
6306
|
+
const s = getGlobalSettings();
|
|
6307
|
+
const counters = sessionCounters.get();
|
|
6308
|
+
const mode = s.mode ?? "standard";
|
|
6309
|
+
const status = {
|
|
6310
|
+
mode,
|
|
6311
|
+
session: {
|
|
6312
|
+
allowed: counters.allowed,
|
|
6313
|
+
blocked: counters.blocked,
|
|
6314
|
+
dlpHits: counters.dlpHits,
|
|
6315
|
+
wouldBlock: counters.wouldBlock
|
|
6316
|
+
},
|
|
6317
|
+
taintedCount: taintStore.list().length,
|
|
6318
|
+
lastRuleHit: counters.lastRuleHit,
|
|
6319
|
+
lastBlockedTool: counters.lastBlockedTool
|
|
6320
|
+
};
|
|
6321
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6322
|
+
return res.end(JSON.stringify(status));
|
|
6323
|
+
} catch (err) {
|
|
6324
|
+
console.error(import_chalk2.default.red("[node9 daemon] GET /status failed:"), err);
|
|
6325
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6326
|
+
return res.end(JSON.stringify({ error: "internal" }));
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
if (req.method === "GET" && pathname === "/state/check") {
|
|
6330
|
+
const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
|
|
6331
|
+
const predicates = predicatesParam.split(",").filter(Boolean);
|
|
6332
|
+
const results = {};
|
|
6333
|
+
for (const p of predicates) {
|
|
6334
|
+
results[p] = sessionHistory.checkPredicate(p);
|
|
6335
|
+
}
|
|
6336
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6337
|
+
return res.end(JSON.stringify(results));
|
|
6338
|
+
}
|
|
6046
6339
|
if (req.method === "POST" && pathname === "/settings") {
|
|
6047
6340
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
6048
6341
|
try {
|
|
@@ -6200,8 +6493,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6200
6493
|
const body = await readBody(req);
|
|
6201
6494
|
const data = body ? JSON.parse(body) : {};
|
|
6202
6495
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6203
|
-
const node9Dir =
|
|
6204
|
-
if (!
|
|
6496
|
+
const node9Dir = import_path17.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6497
|
+
if (!import_path17.default.resolve(configPath).startsWith(node9Dir + import_path17.default.sep)) {
|
|
6205
6498
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6206
6499
|
return res.end(
|
|
6207
6500
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6378,13 +6671,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
6378
6671
|
}
|
|
6379
6672
|
startActivitySocket();
|
|
6380
6673
|
}
|
|
6381
|
-
var import_http, import_fs15,
|
|
6674
|
+
var import_http, import_fs15, import_path17, import_crypto6, import_child_process4, import_chalk2;
|
|
6382
6675
|
var init_server = __esm({
|
|
6383
6676
|
"src/daemon/server.ts"() {
|
|
6384
6677
|
"use strict";
|
|
6385
6678
|
import_http = __toESM(require("http"));
|
|
6386
6679
|
import_fs15 = __toESM(require("fs"));
|
|
6387
|
-
|
|
6680
|
+
import_path17 = __toESM(require("path"));
|
|
6388
6681
|
import_crypto6 = require("crypto");
|
|
6389
6682
|
import_child_process4 = require("child_process");
|
|
6390
6683
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -6528,9 +6821,10 @@ async function ensureDaemon() {
|
|
|
6528
6821
|
}
|
|
6529
6822
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
6530
6823
|
return new Promise((resolve, reject) => {
|
|
6531
|
-
const bodyObj = { decision, source: "terminal" };
|
|
6824
|
+
const bodyObj = { decision, source: opts?.source ?? "terminal" };
|
|
6532
6825
|
if (opts?.persist) bodyObj.persist = true;
|
|
6533
6826
|
if (opts?.trustDuration) bodyObj.trustDuration = opts.trustDuration;
|
|
6827
|
+
if (opts?.reason) bodyObj.reason = opts.reason;
|
|
6534
6828
|
const body = JSON.stringify(bodyObj);
|
|
6535
6829
|
const req = import_http2.default.request(
|
|
6536
6830
|
{
|
|
@@ -6555,6 +6849,9 @@ function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
|
6555
6849
|
});
|
|
6556
6850
|
}
|
|
6557
6851
|
function buildCardLines(req, localCount = 0) {
|
|
6852
|
+
if (req.recoveryCommand) {
|
|
6853
|
+
return buildRecoveryCardLines(req);
|
|
6854
|
+
}
|
|
6558
6855
|
const argsStr = JSON.stringify(req.args ?? {}).replace(/\s+/g, " ");
|
|
6559
6856
|
const argsPreview = argsStr.length > 60 ? argsStr.slice(0, 60) + "\u2026" : argsStr;
|
|
6560
6857
|
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`;
|
|
@@ -6582,6 +6879,31 @@ function buildCardLines(req, localCount = 0) {
|
|
|
6582
6879
|
);
|
|
6583
6880
|
return lines;
|
|
6584
6881
|
}
|
|
6882
|
+
function buildRecoveryCardLines(req) {
|
|
6883
|
+
const argsObj = req.args;
|
|
6884
|
+
const command = typeof argsObj?.command === "string" ? argsObj.command : JSON.stringify(req.args ?? {}).replace(/\s+/g, " ").slice(0, 60);
|
|
6885
|
+
const ruleName = req.riskMetadata?.ruleName?.replace(/^Smart Rule:\s*/i, "") ?? "policy rule";
|
|
6886
|
+
const recoveryCommand = req.recoveryCommand;
|
|
6887
|
+
const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET}`] : [
|
|
6888
|
+
` ${BOLD}${GREEN}[1]${RESET} Allow anyway ${GRAY}(override policy)${RESET}`,
|
|
6889
|
+
` ${BOLD}${YELLOW}[2]${RESET} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
|
|
6890
|
+
` ${BOLD}${RED}[3]${RESET} Deny & stop ${GRAY}(hard block)${RESET}`,
|
|
6891
|
+
``,
|
|
6892
|
+
` ${GRAY}[Timeout: auto-deny]${RESET}`,
|
|
6893
|
+
` Select [1-3]: `
|
|
6894
|
+
];
|
|
6895
|
+
return [
|
|
6896
|
+
``,
|
|
6897
|
+
`${BOLD}${CYAN}${DIVIDER}${RESET}`,
|
|
6898
|
+
`\u{1F6E1}\uFE0F ${BOLD}NODE9 STATE GUARD:${RESET} '${BOLD}${command}${RESET}'`,
|
|
6899
|
+
`${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET}`,
|
|
6900
|
+
`${CYAN}${DIVIDER}${RESET}`,
|
|
6901
|
+
...!req.viewOnly ? [`${BOLD}What would you like to do?${RESET}`, ``] : [],
|
|
6902
|
+
...interactiveLines,
|
|
6903
|
+
`${CYAN}${DIVIDER}${RESET}`,
|
|
6904
|
+
``
|
|
6905
|
+
];
|
|
6906
|
+
}
|
|
6585
6907
|
async function startTail(options = {}) {
|
|
6586
6908
|
const port = await ensureDaemon();
|
|
6587
6909
|
if (options.clear) {
|
|
@@ -6680,14 +7002,14 @@ async function startTail(options = {}) {
|
|
|
6680
7002
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
6681
7003
|
)
|
|
6682
7004
|
);
|
|
6683
|
-
const decisionStamp = action === "always-allow" ? import_chalk16.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk16.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk16.default.green("\u2713 ALLOWED") : import_chalk16.default.red("\u2717 DENIED");
|
|
7005
|
+
const decisionStamp = action === "always-allow" ? import_chalk16.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk16.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk16.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk16.default.yellow("\u21A9 REDIRECT AI") : import_chalk16.default.red("\u2717 DENIED");
|
|
6684
7006
|
stampedLines.push(` ${BOLD}\u2192${RESET} ${decisionStamp} ${GRAY}(terminal)${RESET}`, ``);
|
|
6685
7007
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
6686
7008
|
process.stdout.write(SHOW_CURSOR);
|
|
6687
7009
|
cardLineCount = 0;
|
|
6688
7010
|
if (action === "allow" || action === "always-allow" || action === "trust") {
|
|
6689
7011
|
localAllowCounts.set(req2.toolName, (localAllowCounts.get(req2.toolName) ?? 0) + 1);
|
|
6690
|
-
} else if (action === "deny") {
|
|
7012
|
+
} else if (action === "deny" || action === "redirect") {
|
|
6691
7013
|
localAllowCounts.delete(req2.toolName);
|
|
6692
7014
|
}
|
|
6693
7015
|
let httpDecision;
|
|
@@ -6698,13 +7020,18 @@ async function startTail(options = {}) {
|
|
|
6698
7020
|
} else if (action === "trust") {
|
|
6699
7021
|
httpDecision = "trust";
|
|
6700
7022
|
httpOpts = { trustDuration: "30m" };
|
|
7023
|
+
} else if (action === "redirect") {
|
|
7024
|
+
httpDecision = "deny";
|
|
7025
|
+
const recoveryCommand = req2.recoveryCommand ?? "the required pre-condition";
|
|
7026
|
+
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}\`.`;
|
|
7027
|
+
httpOpts = { reason: redirectReason, source: "terminal-redirect" };
|
|
6701
7028
|
} else {
|
|
6702
7029
|
httpDecision = action;
|
|
6703
7030
|
}
|
|
6704
7031
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err) => {
|
|
6705
7032
|
try {
|
|
6706
7033
|
import_fs24.default.appendFileSync(
|
|
6707
|
-
|
|
7034
|
+
import_path25.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log"),
|
|
6708
7035
|
`[tail] POST /decision failed: ${String(err)}
|
|
6709
7036
|
`
|
|
6710
7037
|
);
|
|
@@ -6736,17 +7063,34 @@ async function startTail(options = {}) {
|
|
|
6736
7063
|
cardActive = false;
|
|
6737
7064
|
showNextCard();
|
|
6738
7065
|
};
|
|
7066
|
+
if (req2.viewOnly) {
|
|
7067
|
+
process.stdin.resume();
|
|
7068
|
+
onKeypress = () => {
|
|
7069
|
+
};
|
|
7070
|
+
process.stdin.on("keypress", onKeypress);
|
|
7071
|
+
return;
|
|
7072
|
+
}
|
|
6739
7073
|
process.stdin.resume();
|
|
6740
7074
|
onKeypress = (_str, key) => {
|
|
6741
7075
|
const name = key?.name ?? "";
|
|
6742
|
-
if (
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
7076
|
+
if (req2.recoveryCommand) {
|
|
7077
|
+
if (name === "1") {
|
|
7078
|
+
settle("allow");
|
|
7079
|
+
} else if (name === "2") {
|
|
7080
|
+
settle("redirect");
|
|
7081
|
+
} else if (name === "3" || key?.ctrl && name === "c") {
|
|
7082
|
+
settle("deny");
|
|
7083
|
+
}
|
|
7084
|
+
} else {
|
|
7085
|
+
if (name === "y" || name === "return") {
|
|
7086
|
+
settle("allow");
|
|
7087
|
+
} else if (name === "n" || name === "d" || key?.ctrl && name === "c") {
|
|
7088
|
+
settle("deny");
|
|
7089
|
+
} else if (name === "a") {
|
|
7090
|
+
settle("always-allow");
|
|
7091
|
+
} else if (name === "t") {
|
|
7092
|
+
settle("trust");
|
|
7093
|
+
}
|
|
6750
7094
|
}
|
|
6751
7095
|
};
|
|
6752
7096
|
process.stdin.on("keypress", onKeypress);
|
|
@@ -6916,21 +7260,21 @@ async function startTail(options = {}) {
|
|
|
6916
7260
|
process.exit(1);
|
|
6917
7261
|
});
|
|
6918
7262
|
}
|
|
6919
|
-
var import_http2, import_chalk16, import_fs24,
|
|
7263
|
+
var import_http2, import_chalk16, import_fs24, import_os20, import_path25, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
|
|
6920
7264
|
var init_tail = __esm({
|
|
6921
7265
|
"src/tui/tail.ts"() {
|
|
6922
7266
|
"use strict";
|
|
6923
7267
|
import_http2 = __toESM(require("http"));
|
|
6924
7268
|
import_chalk16 = __toESM(require("chalk"));
|
|
6925
7269
|
import_fs24 = __toESM(require("fs"));
|
|
6926
|
-
|
|
6927
|
-
|
|
7270
|
+
import_os20 = __toESM(require("os"));
|
|
7271
|
+
import_path25 = __toESM(require("path"));
|
|
6928
7272
|
import_readline3 = __toESM(require("readline"));
|
|
6929
7273
|
import_child_process13 = require("child_process");
|
|
6930
7274
|
init_daemon2();
|
|
6931
7275
|
init_daemon();
|
|
6932
7276
|
init_core();
|
|
6933
|
-
PID_FILE =
|
|
7277
|
+
PID_FILE = import_path25.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
|
|
6934
7278
|
ICONS = {
|
|
6935
7279
|
bash: "\u{1F4BB}",
|
|
6936
7280
|
shell: "\u{1F4BB}",
|
|
@@ -6958,6 +7302,326 @@ var init_tail = __esm({
|
|
|
6958
7302
|
HIDE_CURSOR = "\x1B[?25l";
|
|
6959
7303
|
SHOW_CURSOR = "\x1B[?25h";
|
|
6960
7304
|
ERASE_DOWN = "\x1B[J";
|
|
7305
|
+
DIVIDER = "\u2500".repeat(60);
|
|
7306
|
+
}
|
|
7307
|
+
});
|
|
7308
|
+
|
|
7309
|
+
// src/cli/hud.ts
|
|
7310
|
+
var hud_exports = {};
|
|
7311
|
+
__export(hud_exports, {
|
|
7312
|
+
countConfigs: () => countConfigs,
|
|
7313
|
+
main: () => main,
|
|
7314
|
+
renderEnvironmentLine: () => renderEnvironmentLine
|
|
7315
|
+
});
|
|
7316
|
+
async function readStdin() {
|
|
7317
|
+
const chunks = [];
|
|
7318
|
+
for await (const chunk of process.stdin) {
|
|
7319
|
+
chunks.push(chunk);
|
|
7320
|
+
}
|
|
7321
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
7322
|
+
if (!raw) return {};
|
|
7323
|
+
try {
|
|
7324
|
+
return JSON.parse(raw);
|
|
7325
|
+
} catch {
|
|
7326
|
+
return {};
|
|
7327
|
+
}
|
|
7328
|
+
}
|
|
7329
|
+
function queryDaemon() {
|
|
7330
|
+
return new Promise((resolve) => {
|
|
7331
|
+
const timeout = setTimeout(() => resolve(null), 50);
|
|
7332
|
+
try {
|
|
7333
|
+
const req = import_http3.default.get(
|
|
7334
|
+
`http://${DAEMON_HOST}:${DAEMON_PORT}/status`,
|
|
7335
|
+
{ timeout: 50 },
|
|
7336
|
+
(res) => {
|
|
7337
|
+
const chunks = [];
|
|
7338
|
+
res.on("data", (c) => chunks.push(c));
|
|
7339
|
+
res.on("end", () => {
|
|
7340
|
+
clearTimeout(timeout);
|
|
7341
|
+
try {
|
|
7342
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
7343
|
+
} catch {
|
|
7344
|
+
resolve(null);
|
|
7345
|
+
}
|
|
7346
|
+
});
|
|
7347
|
+
}
|
|
7348
|
+
);
|
|
7349
|
+
req.on("error", () => {
|
|
7350
|
+
clearTimeout(timeout);
|
|
7351
|
+
resolve(null);
|
|
7352
|
+
});
|
|
7353
|
+
req.on("timeout", () => {
|
|
7354
|
+
clearTimeout(timeout);
|
|
7355
|
+
req.destroy();
|
|
7356
|
+
resolve(null);
|
|
7357
|
+
});
|
|
7358
|
+
} catch {
|
|
7359
|
+
clearTimeout(timeout);
|
|
7360
|
+
resolve(null);
|
|
7361
|
+
}
|
|
7362
|
+
});
|
|
7363
|
+
}
|
|
7364
|
+
function dim(s) {
|
|
7365
|
+
return `${DIM}${s}${RESET2}`;
|
|
7366
|
+
}
|
|
7367
|
+
function bold(s) {
|
|
7368
|
+
return `${BOLD2}${s}${RESET2}`;
|
|
7369
|
+
}
|
|
7370
|
+
function color(c, s) {
|
|
7371
|
+
return `${c}${s}${RESET2}`;
|
|
7372
|
+
}
|
|
7373
|
+
function progressBar(pct, warnAt = 70, critAt = 85) {
|
|
7374
|
+
const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
|
|
7375
|
+
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
7376
|
+
const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
|
|
7377
|
+
return `${c}${bar}${RESET2}`;
|
|
7378
|
+
}
|
|
7379
|
+
function formatTimeLeft(resetsAt) {
|
|
7380
|
+
if (!resetsAt) return "";
|
|
7381
|
+
const ms = new Date(resetsAt).getTime() - Date.now();
|
|
7382
|
+
if (ms <= 0) return "";
|
|
7383
|
+
const totalMin = Math.ceil(ms / 6e4);
|
|
7384
|
+
const h = Math.floor(totalMin / 60);
|
|
7385
|
+
const m = totalMin % 60;
|
|
7386
|
+
if (h > 0) return ` (${h}h ${m}m left)`;
|
|
7387
|
+
return ` (${m}m left)`;
|
|
7388
|
+
}
|
|
7389
|
+
function safeReadJson(filePath) {
|
|
7390
|
+
if (!import_fs25.default.existsSync(filePath)) return null;
|
|
7391
|
+
try {
|
|
7392
|
+
return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
|
|
7393
|
+
} catch {
|
|
7394
|
+
return null;
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7397
|
+
function getMcpServerNames(filePath) {
|
|
7398
|
+
const cfg = safeReadJson(filePath);
|
|
7399
|
+
if (!cfg || typeof cfg.mcpServers !== "object" || cfg.mcpServers === null) return /* @__PURE__ */ new Set();
|
|
7400
|
+
return new Set(Object.keys(cfg.mcpServers));
|
|
7401
|
+
}
|
|
7402
|
+
function getDisabledMcpServers(filePath, key) {
|
|
7403
|
+
const cfg = safeReadJson(filePath);
|
|
7404
|
+
if (!cfg || !Array.isArray(cfg[key])) return /* @__PURE__ */ new Set();
|
|
7405
|
+
return new Set(cfg[key].filter((s) => typeof s === "string"));
|
|
7406
|
+
}
|
|
7407
|
+
function countHooksInFile(filePath) {
|
|
7408
|
+
const cfg = safeReadJson(filePath);
|
|
7409
|
+
if (!cfg || typeof cfg.hooks !== "object" || cfg.hooks === null) return 0;
|
|
7410
|
+
return Object.keys(cfg.hooks).length;
|
|
7411
|
+
}
|
|
7412
|
+
function countRulesInDir(rulesDir) {
|
|
7413
|
+
if (!import_fs25.default.existsSync(rulesDir)) return 0;
|
|
7414
|
+
let count = 0;
|
|
7415
|
+
try {
|
|
7416
|
+
for (const entry of import_fs25.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7417
|
+
if (entry.isDirectory()) {
|
|
7418
|
+
count += countRulesInDir(import_path26.default.join(rulesDir, entry.name));
|
|
7419
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7420
|
+
count++;
|
|
7421
|
+
}
|
|
7422
|
+
}
|
|
7423
|
+
} catch {
|
|
7424
|
+
}
|
|
7425
|
+
return count;
|
|
7426
|
+
}
|
|
7427
|
+
function isSamePath(a, b) {
|
|
7428
|
+
try {
|
|
7429
|
+
return import_path26.default.resolve(a) === import_path26.default.resolve(b);
|
|
7430
|
+
} catch {
|
|
7431
|
+
return false;
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
function countConfigs(cwd) {
|
|
7435
|
+
const homeDir2 = import_os21.default.homedir();
|
|
7436
|
+
const claudeDir = import_path26.default.join(homeDir2, ".claude");
|
|
7437
|
+
let claudeMdCount = 0;
|
|
7438
|
+
let rulesCount = 0;
|
|
7439
|
+
let hooksCount = 0;
|
|
7440
|
+
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7441
|
+
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7442
|
+
if (import_fs25.default.existsSync(import_path26.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7443
|
+
rulesCount += countRulesInDir(import_path26.default.join(claudeDir, "rules"));
|
|
7444
|
+
const userSettings = import_path26.default.join(claudeDir, "settings.json");
|
|
7445
|
+
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7446
|
+
hooksCount += countHooksInFile(userSettings);
|
|
7447
|
+
const userClaudeJson = import_path26.default.join(homeDir2, ".claude.json");
|
|
7448
|
+
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7449
|
+
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7450
|
+
userMcpServers.delete(name);
|
|
7451
|
+
}
|
|
7452
|
+
if (cwd) {
|
|
7453
|
+
if (import_fs25.default.existsSync(import_path26.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7454
|
+
if (import_fs25.default.existsSync(import_path26.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7455
|
+
const projectClaudeDir = import_path26.default.join(cwd, ".claude");
|
|
7456
|
+
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7457
|
+
if (!overlapsUserScope) {
|
|
7458
|
+
if (import_fs25.default.existsSync(import_path26.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7459
|
+
rulesCount += countRulesInDir(import_path26.default.join(projectClaudeDir, "rules"));
|
|
7460
|
+
const projSettings = import_path26.default.join(projectClaudeDir, "settings.json");
|
|
7461
|
+
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7462
|
+
hooksCount += countHooksInFile(projSettings);
|
|
7463
|
+
}
|
|
7464
|
+
if (import_fs25.default.existsSync(import_path26.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7465
|
+
const localSettings = import_path26.default.join(projectClaudeDir, "settings.local.json");
|
|
7466
|
+
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7467
|
+
hooksCount += countHooksInFile(localSettings);
|
|
7468
|
+
const mcpJsonServers = getMcpServerNames(import_path26.default.join(cwd, ".mcp.json"));
|
|
7469
|
+
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7470
|
+
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7471
|
+
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
7472
|
+
}
|
|
7473
|
+
return {
|
|
7474
|
+
claudeMdCount,
|
|
7475
|
+
rulesCount,
|
|
7476
|
+
mcpCount: userMcpServers.size + projectMcpServers.size,
|
|
7477
|
+
hooksCount
|
|
7478
|
+
};
|
|
7479
|
+
}
|
|
7480
|
+
function renderEnvironmentLine(counts) {
|
|
7481
|
+
const { claudeMdCount, rulesCount, mcpCount, hooksCount } = counts;
|
|
7482
|
+
if (claudeMdCount === 0 && rulesCount === 0 && mcpCount === 0 && hooksCount === 0) return null;
|
|
7483
|
+
const parts = [
|
|
7484
|
+
`${claudeMdCount} CLAUDE.md`,
|
|
7485
|
+
`${rulesCount} rules`,
|
|
7486
|
+
`${mcpCount} MCPs`,
|
|
7487
|
+
`${hooksCount} hooks`
|
|
7488
|
+
];
|
|
7489
|
+
return color(DIM, parts.join(` ${dim("|")} `));
|
|
7490
|
+
}
|
|
7491
|
+
function renderOffline() {
|
|
7492
|
+
process.stdout.write(`${color(BLUE, "\u{1F6E1}")} ${bold("node9")} ${dim("|")} ${dim("offline")}
|
|
7493
|
+
`);
|
|
7494
|
+
}
|
|
7495
|
+
function renderSecurityLine(status) {
|
|
7496
|
+
const parts = [];
|
|
7497
|
+
parts.push(`${color(BLUE, "\u{1F6E1}")} ${bold("node9")}`);
|
|
7498
|
+
const modeColors = {
|
|
7499
|
+
standard: GREEN2,
|
|
7500
|
+
strict: RED2,
|
|
7501
|
+
observe: MAGENTA,
|
|
7502
|
+
audit: YELLOW2
|
|
7503
|
+
};
|
|
7504
|
+
const modeIcon = {
|
|
7505
|
+
standard: "",
|
|
7506
|
+
strict: "",
|
|
7507
|
+
observe: "\u{1F441} ",
|
|
7508
|
+
audit: ""
|
|
7509
|
+
};
|
|
7510
|
+
const mc = modeColors[status.mode] ?? WHITE;
|
|
7511
|
+
parts.push(`${dim("|")} ${color(mc, modeIcon[status.mode] ?? "")}${color(mc, status.mode)}`);
|
|
7512
|
+
if (status.mode === "observe") {
|
|
7513
|
+
parts.push(`${dim("|")} ${color(GREEN2, `\u2705 ${status.session.allowed} passed`)}`);
|
|
7514
|
+
if (status.session.wouldBlock > 0) {
|
|
7515
|
+
parts.push(color(YELLOW2, `\u26A0 ${status.session.wouldBlock} would-block`));
|
|
7516
|
+
}
|
|
7517
|
+
} else {
|
|
7518
|
+
parts.push(`${dim("|")} ${color(GREEN2, `\u2705 ${status.session.allowed} allowed`)}`);
|
|
7519
|
+
if (status.session.blocked > 0) {
|
|
7520
|
+
parts.push(color(RED2, `\u{1F6D1} ${status.session.blocked} blocked`));
|
|
7521
|
+
}
|
|
7522
|
+
if (status.session.dlpHits > 0) {
|
|
7523
|
+
parts.push(color(RED2, `\u{1F6A8} ${status.session.dlpHits} dlp`));
|
|
7524
|
+
}
|
|
7525
|
+
}
|
|
7526
|
+
if (status.taintedCount > 0) {
|
|
7527
|
+
parts.push(color(YELLOW2, `\u{1F4A7} ${status.taintedCount} tainted`));
|
|
7528
|
+
}
|
|
7529
|
+
if (status.lastRuleHit) {
|
|
7530
|
+
const ruleName = status.lastRuleHit.replace(/^Smart Rule:\s*/i, "");
|
|
7531
|
+
parts.push(color(CYAN2, `\u26A1 ${ruleName}`));
|
|
7532
|
+
}
|
|
7533
|
+
return parts.join(" ");
|
|
7534
|
+
}
|
|
7535
|
+
function renderContextLine(stdin) {
|
|
7536
|
+
const cw = stdin.context_window;
|
|
7537
|
+
if (!cw) return null;
|
|
7538
|
+
const parts = [];
|
|
7539
|
+
const modelName = typeof stdin.model === "string" ? stdin.model : stdin.model?.display_name ?? "";
|
|
7540
|
+
if (modelName) {
|
|
7541
|
+
parts.push(color(CYAN2, modelName));
|
|
7542
|
+
}
|
|
7543
|
+
const usedPct = cw.used_percentage ?? (cw.current_usage && cw.context_window_size ? Math.round(
|
|
7544
|
+
((cw.current_usage.input_tokens ?? 0) + (cw.current_usage.output_tokens ?? 0)) / cw.context_window_size * 100
|
|
7545
|
+
) : null);
|
|
7546
|
+
if (usedPct !== null) {
|
|
7547
|
+
const bar = progressBar(usedPct);
|
|
7548
|
+
parts.push(`${dim("\u2502")} ctx ${bar} ${usedPct}%`);
|
|
7549
|
+
}
|
|
7550
|
+
const rl = stdin.rate_limits;
|
|
7551
|
+
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
7552
|
+
const pct = Math.round(rl.five_hour.used_percentage);
|
|
7553
|
+
const bar = progressBar(pct, 60, 80);
|
|
7554
|
+
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
7555
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
|
|
7556
|
+
}
|
|
7557
|
+
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
7558
|
+
const pct = Math.round(rl.seven_day.used_percentage);
|
|
7559
|
+
const bar = progressBar(pct, 60, 80);
|
|
7560
|
+
parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
|
|
7561
|
+
}
|
|
7562
|
+
if (parts.length === 0) return null;
|
|
7563
|
+
return parts.join(" ");
|
|
7564
|
+
}
|
|
7565
|
+
async function main() {
|
|
7566
|
+
try {
|
|
7567
|
+
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7568
|
+
if (!daemonStatus2) {
|
|
7569
|
+
renderOffline();
|
|
7570
|
+
return;
|
|
7571
|
+
}
|
|
7572
|
+
process.stdout.write(renderSecurityLine(daemonStatus2) + "\n");
|
|
7573
|
+
const ctxLine = renderContextLine(stdin);
|
|
7574
|
+
if (ctxLine) {
|
|
7575
|
+
process.stdout.write(ctxLine + "\n");
|
|
7576
|
+
}
|
|
7577
|
+
const showEnvCounts = (() => {
|
|
7578
|
+
try {
|
|
7579
|
+
const cwd = stdin.cwd ?? process.cwd();
|
|
7580
|
+
for (const configPath of [
|
|
7581
|
+
import_path26.default.join(cwd, "node9.config.json"),
|
|
7582
|
+
import_path26.default.join(import_os21.default.homedir(), ".node9", "config.json")
|
|
7583
|
+
]) {
|
|
7584
|
+
if (!import_fs25.default.existsSync(configPath)) continue;
|
|
7585
|
+
const cfg = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
7586
|
+
const hud = cfg.settings?.hud;
|
|
7587
|
+
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7588
|
+
}
|
|
7589
|
+
} catch {
|
|
7590
|
+
}
|
|
7591
|
+
return true;
|
|
7592
|
+
})();
|
|
7593
|
+
if (showEnvCounts) {
|
|
7594
|
+
const envLine = renderEnvironmentLine(countConfigs(stdin.cwd));
|
|
7595
|
+
if (envLine) {
|
|
7596
|
+
process.stdout.write(envLine + "\n");
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
} catch {
|
|
7600
|
+
renderOffline();
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
var import_fs25, import_path26, import_os21, import_http3, RESET2, BOLD2, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
|
|
7604
|
+
var init_hud = __esm({
|
|
7605
|
+
"src/cli/hud.ts"() {
|
|
7606
|
+
"use strict";
|
|
7607
|
+
import_fs25 = __toESM(require("fs"));
|
|
7608
|
+
import_path26 = __toESM(require("path"));
|
|
7609
|
+
import_os21 = __toESM(require("os"));
|
|
7610
|
+
import_http3 = __toESM(require("http"));
|
|
7611
|
+
init_daemon();
|
|
7612
|
+
RESET2 = "\x1B[0m";
|
|
7613
|
+
BOLD2 = "\x1B[1m";
|
|
7614
|
+
DIM = "\x1B[2m";
|
|
7615
|
+
RED2 = "\x1B[31m";
|
|
7616
|
+
GREEN2 = "\x1B[32m";
|
|
7617
|
+
YELLOW2 = "\x1B[33m";
|
|
7618
|
+
BLUE = "\x1B[34m";
|
|
7619
|
+
MAGENTA = "\x1B[35m";
|
|
7620
|
+
CYAN2 = "\x1B[36m";
|
|
7621
|
+
WHITE = "\x1B[37m";
|
|
7622
|
+
BAR_FILLED = "\u2588";
|
|
7623
|
+
BAR_EMPTY = "\u2591";
|
|
7624
|
+
BAR_WIDTH = 10;
|
|
6961
7625
|
}
|
|
6962
7626
|
});
|
|
6963
7627
|
|
|
@@ -6967,8 +7631,8 @@ init_core();
|
|
|
6967
7631
|
|
|
6968
7632
|
// src/setup.ts
|
|
6969
7633
|
var import_fs11 = __toESM(require("fs"));
|
|
6970
|
-
var
|
|
6971
|
-
var
|
|
7634
|
+
var import_path13 = __toESM(require("path"));
|
|
7635
|
+
var import_os10 = __toESM(require("os"));
|
|
6972
7636
|
var import_chalk = __toESM(require("chalk"));
|
|
6973
7637
|
var import_prompts = require("@inquirer/prompts");
|
|
6974
7638
|
function printDaemonTip() {
|
|
@@ -6993,7 +7657,7 @@ function readJson(filePath) {
|
|
|
6993
7657
|
return null;
|
|
6994
7658
|
}
|
|
6995
7659
|
function writeJson(filePath, data) {
|
|
6996
|
-
const dir =
|
|
7660
|
+
const dir = import_path13.default.dirname(filePath);
|
|
6997
7661
|
if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
|
|
6998
7662
|
import_fs11.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
6999
7663
|
}
|
|
@@ -7002,9 +7666,9 @@ function isNode9Hook(cmd) {
|
|
|
7002
7666
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
7003
7667
|
}
|
|
7004
7668
|
function teardownClaude() {
|
|
7005
|
-
const homeDir2 =
|
|
7006
|
-
const hooksPath =
|
|
7007
|
-
const mcpPath =
|
|
7669
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7670
|
+
const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
|
|
7671
|
+
const mcpPath = import_path13.default.join(homeDir2, ".claude.json");
|
|
7008
7672
|
let changed = false;
|
|
7009
7673
|
const settings = readJson(hooksPath);
|
|
7010
7674
|
if (settings?.hooks) {
|
|
@@ -7052,8 +7716,8 @@ function teardownClaude() {
|
|
|
7052
7716
|
}
|
|
7053
7717
|
}
|
|
7054
7718
|
function teardownGemini() {
|
|
7055
|
-
const homeDir2 =
|
|
7056
|
-
const settingsPath =
|
|
7719
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7720
|
+
const settingsPath = import_path13.default.join(homeDir2, ".gemini", "settings.json");
|
|
7057
7721
|
const settings = readJson(settingsPath);
|
|
7058
7722
|
if (!settings) {
|
|
7059
7723
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -7091,8 +7755,8 @@ function teardownGemini() {
|
|
|
7091
7755
|
}
|
|
7092
7756
|
}
|
|
7093
7757
|
function teardownCursor() {
|
|
7094
|
-
const homeDir2 =
|
|
7095
|
-
const mcpPath =
|
|
7758
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7759
|
+
const mcpPath = import_path13.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7096
7760
|
const mcpConfig = readJson(mcpPath);
|
|
7097
7761
|
if (!mcpConfig?.mcpServers) {
|
|
7098
7762
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -7118,9 +7782,9 @@ function teardownCursor() {
|
|
|
7118
7782
|
}
|
|
7119
7783
|
}
|
|
7120
7784
|
async function setupClaude() {
|
|
7121
|
-
const homeDir2 =
|
|
7122
|
-
const mcpPath =
|
|
7123
|
-
const hooksPath =
|
|
7785
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7786
|
+
const mcpPath = import_path13.default.join(homeDir2, ".claude.json");
|
|
7787
|
+
const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
|
|
7124
7788
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
7125
7789
|
const settings = readJson(hooksPath) ?? {};
|
|
7126
7790
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -7194,8 +7858,8 @@ async function setupClaude() {
|
|
|
7194
7858
|
}
|
|
7195
7859
|
}
|
|
7196
7860
|
async function setupGemini() {
|
|
7197
|
-
const homeDir2 =
|
|
7198
|
-
const settingsPath =
|
|
7861
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7862
|
+
const settingsPath = import_path13.default.join(homeDir2, ".gemini", "settings.json");
|
|
7199
7863
|
const settings = readJson(settingsPath) ?? {};
|
|
7200
7864
|
const servers = settings.mcpServers ?? {};
|
|
7201
7865
|
let anythingChanged = false;
|
|
@@ -7276,7 +7940,7 @@ async function setupGemini() {
|
|
|
7276
7940
|
printDaemonTip();
|
|
7277
7941
|
}
|
|
7278
7942
|
}
|
|
7279
|
-
function detectAgents(homeDir2 =
|
|
7943
|
+
function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
7280
7944
|
const exists = (p) => {
|
|
7281
7945
|
try {
|
|
7282
7946
|
return import_fs11.default.existsSync(p);
|
|
@@ -7290,14 +7954,14 @@ function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
|
7290
7954
|
}
|
|
7291
7955
|
};
|
|
7292
7956
|
return {
|
|
7293
|
-
claude: exists(
|
|
7294
|
-
gemini: exists(
|
|
7295
|
-
cursor: exists(
|
|
7957
|
+
claude: exists(import_path13.default.join(homeDir2, ".claude")) || exists(import_path13.default.join(homeDir2, ".claude.json")),
|
|
7958
|
+
gemini: exists(import_path13.default.join(homeDir2, ".gemini")),
|
|
7959
|
+
cursor: exists(import_path13.default.join(homeDir2, ".cursor"))
|
|
7296
7960
|
};
|
|
7297
7961
|
}
|
|
7298
7962
|
async function setupCursor() {
|
|
7299
|
-
const homeDir2 =
|
|
7300
|
-
const mcpPath =
|
|
7963
|
+
const homeDir2 = import_os10.default.homedir();
|
|
7964
|
+
const mcpPath = import_path13.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7301
7965
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
7302
7966
|
const servers = mcpConfig.mcpServers ?? {};
|
|
7303
7967
|
let anythingChanged = false;
|
|
@@ -7350,11 +8014,57 @@ async function setupCursor() {
|
|
|
7350
8014
|
printDaemonTip();
|
|
7351
8015
|
}
|
|
7352
8016
|
}
|
|
8017
|
+
function setupHud() {
|
|
8018
|
+
const homeDir2 = import_os10.default.homedir();
|
|
8019
|
+
const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
|
|
8020
|
+
const settings = readJson(hooksPath) ?? {};
|
|
8021
|
+
const hudCommand = fullPathCommand("hud");
|
|
8022
|
+
const statusLineObj = { type: "command", command: hudCommand };
|
|
8023
|
+
const existing = settings.statusLine;
|
|
8024
|
+
const existingCommand = typeof existing === "object" ? existing?.command : existing;
|
|
8025
|
+
if (existingCommand === hudCommand) {
|
|
8026
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F node9 HUD is already configured in ~/.claude/settings.json"));
|
|
8027
|
+
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8028
|
+
return;
|
|
8029
|
+
}
|
|
8030
|
+
if (existing && existingCommand !== hudCommand) {
|
|
8031
|
+
console.log(
|
|
8032
|
+
import_chalk.default.yellow(
|
|
8033
|
+
` \u26A0\uFE0F statusLine is already set to: "${existingCommand}"
|
|
8034
|
+
Overwriting with node9 HUD.`
|
|
8035
|
+
)
|
|
8036
|
+
);
|
|
8037
|
+
}
|
|
8038
|
+
settings.statusLine = statusLineObj;
|
|
8039
|
+
writeJson(hooksPath, settings);
|
|
8040
|
+
console.log(import_chalk.default.green.bold("\u2705 node9 HUD added to Claude Code statusline"));
|
|
8041
|
+
console.log(import_chalk.default.gray(" Settings: ~/.claude/settings.json"));
|
|
8042
|
+
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8043
|
+
}
|
|
8044
|
+
function teardownHud() {
|
|
8045
|
+
const homeDir2 = import_os10.default.homedir();
|
|
8046
|
+
const hooksPath = import_path13.default.join(homeDir2, ".claude", "settings.json");
|
|
8047
|
+
const settings = readJson(hooksPath);
|
|
8048
|
+
if (!settings) {
|
|
8049
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
|
|
8050
|
+
return;
|
|
8051
|
+
}
|
|
8052
|
+
const existing = settings.statusLine;
|
|
8053
|
+
const existingCommand = typeof existing === "object" ? existing?.command : existing;
|
|
8054
|
+
if (!existingCommand || !String(existingCommand).includes("node9")) {
|
|
8055
|
+
console.log(import_chalk.default.blue(" \u2139\uFE0F node9 HUD not found in ~/.claude/settings.json"));
|
|
8056
|
+
return;
|
|
8057
|
+
}
|
|
8058
|
+
delete settings.statusLine;
|
|
8059
|
+
writeJson(hooksPath, settings);
|
|
8060
|
+
console.log(import_chalk.default.green(" \u2705 node9 HUD removed from ~/.claude/settings.json"));
|
|
8061
|
+
console.log(import_chalk.default.gray(" Restart Claude Code for changes to take effect."));
|
|
8062
|
+
}
|
|
7353
8063
|
|
|
7354
8064
|
// src/cli.ts
|
|
7355
8065
|
init_daemon2();
|
|
7356
8066
|
var import_chalk17 = __toESM(require("chalk"));
|
|
7357
|
-
var
|
|
8067
|
+
var import_fs26 = __toESM(require("fs"));
|
|
7358
8068
|
var import_path27 = __toESM(require("path"));
|
|
7359
8069
|
var import_os22 = __toESM(require("os"));
|
|
7360
8070
|
var import_prompts3 = require("@inquirer/prompts");
|
|
@@ -7387,7 +8097,7 @@ var import_execa2 = require("execa");
|
|
|
7387
8097
|
init_orchestrator();
|
|
7388
8098
|
|
|
7389
8099
|
// src/policy/negotiation.ts
|
|
7390
|
-
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason) {
|
|
8100
|
+
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand) {
|
|
7391
8101
|
if (isHumanDecision) {
|
|
7392
8102
|
return `NODE9: The human user rejected this action.
|
|
7393
8103
|
REASON: ${humanReason || "No specific reason provided."}
|
|
@@ -7443,10 +8153,11 @@ INSTRUCTION: Inform the user this action is pending approval. Wait for them to a
|
|
|
7443
8153
|
INSTRUCTION: Do NOT use "${rule}". Find a read-only or non-destructive alternative.
|
|
7444
8154
|
Do NOT attempt to bypass this rule.`;
|
|
7445
8155
|
}
|
|
8156
|
+
const recovery = recoveryCommand ? `
|
|
8157
|
+
REQUIRED ACTION: Run \`${recoveryCommand}\` first, then retry your original command.` : "\n- Pivot to a non-destructive or read-only alternative.";
|
|
7446
8158
|
return `NODE9: Action blocked by security policy [${blockedByLabel}].
|
|
7447
8159
|
INSTRUCTIONS:
|
|
7448
|
-
- Do NOT retry this exact command or attempt to bypass the rule
|
|
7449
|
-
- Pivot to a non-destructive or read-only alternative.
|
|
8160
|
+
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
7450
8161
|
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
7451
8162
|
}
|
|
7452
8163
|
|
|
@@ -7578,8 +8289,8 @@ async function autoStartDaemonAndWait() {
|
|
|
7578
8289
|
// src/cli/commands/check.ts
|
|
7579
8290
|
var import_chalk5 = __toESM(require("chalk"));
|
|
7580
8291
|
var import_fs18 = __toESM(require("fs"));
|
|
7581
|
-
var
|
|
7582
|
-
var
|
|
8292
|
+
var import_path19 = __toESM(require("path"));
|
|
8293
|
+
var import_os14 = __toESM(require("os"));
|
|
7583
8294
|
init_orchestrator();
|
|
7584
8295
|
init_daemon();
|
|
7585
8296
|
init_config();
|
|
@@ -7589,10 +8300,10 @@ init_policy();
|
|
|
7589
8300
|
var import_child_process8 = require("child_process");
|
|
7590
8301
|
var import_crypto7 = __toESM(require("crypto"));
|
|
7591
8302
|
var import_fs17 = __toESM(require("fs"));
|
|
7592
|
-
var
|
|
7593
|
-
var
|
|
7594
|
-
var SNAPSHOT_STACK_PATH =
|
|
7595
|
-
var UNDO_LATEST_PATH =
|
|
8303
|
+
var import_path18 = __toESM(require("path"));
|
|
8304
|
+
var import_os13 = __toESM(require("os"));
|
|
8305
|
+
var SNAPSHOT_STACK_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
|
|
8306
|
+
var UNDO_LATEST_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
|
|
7596
8307
|
var MAX_SNAPSHOTS = 10;
|
|
7597
8308
|
var GIT_TIMEOUT = 15e3;
|
|
7598
8309
|
function readStack() {
|
|
@@ -7604,7 +8315,7 @@ function readStack() {
|
|
|
7604
8315
|
return [];
|
|
7605
8316
|
}
|
|
7606
8317
|
function writeStack(stack) {
|
|
7607
|
-
const dir =
|
|
8318
|
+
const dir = import_path18.default.dirname(SNAPSHOT_STACK_PATH);
|
|
7608
8319
|
if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
|
|
7609
8320
|
import_fs17.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
7610
8321
|
}
|
|
@@ -7632,14 +8343,14 @@ function normalizeCwdForHash(cwd) {
|
|
|
7632
8343
|
}
|
|
7633
8344
|
function getShadowRepoDir(cwd) {
|
|
7634
8345
|
const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
7635
|
-
return
|
|
8346
|
+
return import_path18.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
|
|
7636
8347
|
}
|
|
7637
8348
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
7638
8349
|
try {
|
|
7639
8350
|
const cutoff = Date.now() - 6e4;
|
|
7640
8351
|
for (const f of import_fs17.default.readdirSync(shadowDir)) {
|
|
7641
8352
|
if (f.startsWith("index_")) {
|
|
7642
|
-
const fp =
|
|
8353
|
+
const fp = import_path18.default.join(shadowDir, f);
|
|
7643
8354
|
try {
|
|
7644
8355
|
if (import_fs17.default.statSync(fp).mtimeMs < cutoff) import_fs17.default.unlinkSync(fp);
|
|
7645
8356
|
} catch {
|
|
@@ -7653,7 +8364,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
7653
8364
|
const hardcoded = [".git", ".node9"];
|
|
7654
8365
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
7655
8366
|
try {
|
|
7656
|
-
import_fs17.default.writeFileSync(
|
|
8367
|
+
import_fs17.default.writeFileSync(import_path18.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
7657
8368
|
} catch {
|
|
7658
8369
|
}
|
|
7659
8370
|
}
|
|
@@ -7666,7 +8377,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7666
8377
|
timeout: 3e3
|
|
7667
8378
|
});
|
|
7668
8379
|
if (check.status === 0) {
|
|
7669
|
-
const ptPath =
|
|
8380
|
+
const ptPath = import_path18.default.join(shadowDir, "project-path.txt");
|
|
7670
8381
|
try {
|
|
7671
8382
|
const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
|
|
7672
8383
|
if (stored === normalizedCwd) return true;
|
|
@@ -7693,7 +8404,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7693
8404
|
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
7694
8405
|
return false;
|
|
7695
8406
|
}
|
|
7696
|
-
const configFile =
|
|
8407
|
+
const configFile = import_path18.default.join(shadowDir, "config");
|
|
7697
8408
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
7698
8409
|
timeout: 3e3
|
|
7699
8410
|
});
|
|
@@ -7701,7 +8412,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7701
8412
|
timeout: 3e3
|
|
7702
8413
|
});
|
|
7703
8414
|
try {
|
|
7704
|
-
import_fs17.default.writeFileSync(
|
|
8415
|
+
import_fs17.default.writeFileSync(import_path18.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
7705
8416
|
} catch {
|
|
7706
8417
|
}
|
|
7707
8418
|
return true;
|
|
@@ -7724,7 +8435,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
7724
8435
|
const shadowDir = getShadowRepoDir(cwd);
|
|
7725
8436
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
7726
8437
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
7727
|
-
indexFile =
|
|
8438
|
+
indexFile = import_path18.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
7728
8439
|
const shadowEnv = {
|
|
7729
8440
|
...process.env,
|
|
7730
8441
|
GIT_DIR: shadowDir,
|
|
@@ -7833,7 +8544,7 @@ function applyUndo(hash, cwd) {
|
|
|
7833
8544
|
timeout: GIT_TIMEOUT
|
|
7834
8545
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
7835
8546
|
for (const file of [...tracked, ...untracked]) {
|
|
7836
|
-
const fullPath =
|
|
8547
|
+
const fullPath = import_path18.default.join(dir, file);
|
|
7837
8548
|
if (!snapshotFiles.has(file) && import_fs17.default.existsSync(fullPath)) {
|
|
7838
8549
|
import_fs17.default.unlinkSync(fullPath);
|
|
7839
8550
|
}
|
|
@@ -7859,7 +8570,7 @@ function registerCheckCommand(program2) {
|
|
|
7859
8570
|
} catch (err) {
|
|
7860
8571
|
const tempConfig = getConfig();
|
|
7861
8572
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
7862
|
-
const logPath =
|
|
8573
|
+
const logPath = import_path19.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
7863
8574
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7864
8575
|
import_fs18.default.appendFileSync(
|
|
7865
8576
|
logPath,
|
|
@@ -7872,9 +8583,9 @@ RAW: ${raw}
|
|
|
7872
8583
|
}
|
|
7873
8584
|
const config = getConfig(payload.cwd || void 0);
|
|
7874
8585
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
7875
|
-
const logPath =
|
|
7876
|
-
if (!import_fs18.default.existsSync(
|
|
7877
|
-
import_fs18.default.mkdirSync(
|
|
8586
|
+
const logPath = import_path19.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
8587
|
+
if (!import_fs18.default.existsSync(import_path19.default.dirname(logPath)))
|
|
8588
|
+
import_fs18.default.mkdirSync(import_path19.default.dirname(logPath), { recursive: true });
|
|
7878
8589
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
7879
8590
|
`);
|
|
7880
8591
|
}
|
|
@@ -7900,6 +8611,8 @@ RAW: ${raw}
|
|
|
7900
8611
|
}
|
|
7901
8612
|
writeTty(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
|
|
7902
8613
|
if (result2?.changeHint) writeTty(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
|
|
8614
|
+
if (result2?.recoveryCommand)
|
|
8615
|
+
writeTty(import_chalk5.default.green(` \u{1F4A1} Run: ${result2.recoveryCommand}`));
|
|
7903
8616
|
writeTty("");
|
|
7904
8617
|
} catch {
|
|
7905
8618
|
} finally {
|
|
@@ -7912,7 +8625,8 @@ RAW: ${raw}
|
|
|
7912
8625
|
const aiFeedbackMessage = buildNegotiationMessage(
|
|
7913
8626
|
blockedByContext,
|
|
7914
8627
|
isHumanDecision,
|
|
7915
|
-
msg
|
|
8628
|
+
msg,
|
|
8629
|
+
result2?.recoveryCommand
|
|
7916
8630
|
);
|
|
7917
8631
|
process.stdout.write(
|
|
7918
8632
|
JSON.stringify({
|
|
@@ -7936,7 +8650,7 @@ RAW: ${raw}
|
|
|
7936
8650
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
7937
8651
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
7938
8652
|
}
|
|
7939
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
8653
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path19.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
7940
8654
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
7941
8655
|
cwd: safeCwdForAuth
|
|
7942
8656
|
});
|
|
@@ -7980,7 +8694,7 @@ RAW: ${raw}
|
|
|
7980
8694
|
});
|
|
7981
8695
|
} catch (err) {
|
|
7982
8696
|
if (process.env.NODE9_DEBUG === "1") {
|
|
7983
|
-
const logPath =
|
|
8697
|
+
const logPath = import_path19.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
7984
8698
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7985
8699
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
7986
8700
|
`);
|
|
@@ -8017,8 +8731,8 @@ RAW: ${raw}
|
|
|
8017
8731
|
|
|
8018
8732
|
// src/cli/commands/log.ts
|
|
8019
8733
|
var import_fs19 = __toESM(require("fs"));
|
|
8020
|
-
var
|
|
8021
|
-
var
|
|
8734
|
+
var import_path20 = __toESM(require("path"));
|
|
8735
|
+
var import_os15 = __toESM(require("os"));
|
|
8022
8736
|
init_audit();
|
|
8023
8737
|
init_config();
|
|
8024
8738
|
init_policy();
|
|
@@ -8062,6 +8776,20 @@ function containsShellMetachar(token) {
|
|
|
8062
8776
|
}
|
|
8063
8777
|
|
|
8064
8778
|
// src/cli/commands/log.ts
|
|
8779
|
+
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;
|
|
8780
|
+
function detectTestResult(command, output) {
|
|
8781
|
+
if (!TEST_COMMAND_RE.test(command)) return null;
|
|
8782
|
+
const out = output.toLowerCase();
|
|
8783
|
+
if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
|
|
8784
|
+
out
|
|
8785
|
+
) && !/\b(fail|error|failed)\b/.test(out)) {
|
|
8786
|
+
return "pass";
|
|
8787
|
+
}
|
|
8788
|
+
if (/\b(tests?\s+failed|failing|failed|error|assertion\s+error|\d+\s+failed)\b/i.test(out)) {
|
|
8789
|
+
return "fail";
|
|
8790
|
+
}
|
|
8791
|
+
return null;
|
|
8792
|
+
}
|
|
8065
8793
|
function sanitize3(value) {
|
|
8066
8794
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
8067
8795
|
}
|
|
@@ -8080,9 +8808,9 @@ function registerLogCommand(program2) {
|
|
|
8080
8808
|
decision: "allowed",
|
|
8081
8809
|
source: "post-hook"
|
|
8082
8810
|
};
|
|
8083
|
-
const logPath =
|
|
8084
|
-
if (!import_fs19.default.existsSync(
|
|
8085
|
-
import_fs19.default.mkdirSync(
|
|
8811
|
+
const logPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "audit.log");
|
|
8812
|
+
if (!import_fs19.default.existsSync(import_path20.default.dirname(logPath)))
|
|
8813
|
+
import_fs19.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
|
|
8086
8814
|
import_fs19.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
8087
8815
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
8088
8816
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
@@ -8093,7 +8821,22 @@ function registerLogCommand(program2) {
|
|
|
8093
8821
|
}
|
|
8094
8822
|
}
|
|
8095
8823
|
}
|
|
8096
|
-
|
|
8824
|
+
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
8825
|
+
const bashCommand = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
8826
|
+
const output = payload.tool_response?.output ?? "";
|
|
8827
|
+
if (bashCommand && output) {
|
|
8828
|
+
const testResult = detectTestResult(bashCommand, output);
|
|
8829
|
+
if (testResult) {
|
|
8830
|
+
await notifyActivitySocket({
|
|
8831
|
+
id: "test-result",
|
|
8832
|
+
ts: Date.now(),
|
|
8833
|
+
tool,
|
|
8834
|
+
status: testResult === "pass" ? "test_pass" : "test_fail"
|
|
8835
|
+
});
|
|
8836
|
+
}
|
|
8837
|
+
}
|
|
8838
|
+
}
|
|
8839
|
+
const safeCwd = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
8097
8840
|
const config = getConfig(safeCwd);
|
|
8098
8841
|
if (shouldSnapshot(tool, {}, config)) {
|
|
8099
8842
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -8102,7 +8845,7 @@ function registerLogCommand(program2) {
|
|
|
8102
8845
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8103
8846
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
8104
8847
|
`);
|
|
8105
|
-
const debugPath =
|
|
8848
|
+
const debugPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
8106
8849
|
try {
|
|
8107
8850
|
import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
8108
8851
|
`);
|
|
@@ -8409,13 +9152,13 @@ function registerConfigShowCommand(program2) {
|
|
|
8409
9152
|
// src/cli/commands/doctor.ts
|
|
8410
9153
|
var import_chalk7 = __toESM(require("chalk"));
|
|
8411
9154
|
var import_fs20 = __toESM(require("fs"));
|
|
8412
|
-
var
|
|
8413
|
-
var
|
|
9155
|
+
var import_path21 = __toESM(require("path"));
|
|
9156
|
+
var import_os16 = __toESM(require("os"));
|
|
8414
9157
|
var import_child_process9 = require("child_process");
|
|
8415
9158
|
init_daemon();
|
|
8416
9159
|
function registerDoctorCommand(program2, version2) {
|
|
8417
9160
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
8418
|
-
const homeDir2 =
|
|
9161
|
+
const homeDir2 = import_os16.default.homedir();
|
|
8419
9162
|
let failures = 0;
|
|
8420
9163
|
function pass(msg) {
|
|
8421
9164
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -8464,7 +9207,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8464
9207
|
);
|
|
8465
9208
|
}
|
|
8466
9209
|
section("Configuration");
|
|
8467
|
-
const globalConfigPath =
|
|
9210
|
+
const globalConfigPath = import_path21.default.join(homeDir2, ".node9", "config.json");
|
|
8468
9211
|
if (import_fs20.default.existsSync(globalConfigPath)) {
|
|
8469
9212
|
try {
|
|
8470
9213
|
JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
|
|
@@ -8475,7 +9218,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8475
9218
|
} else {
|
|
8476
9219
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
8477
9220
|
}
|
|
8478
|
-
const projectConfigPath =
|
|
9221
|
+
const projectConfigPath = import_path21.default.join(process.cwd(), "node9.config.json");
|
|
8479
9222
|
if (import_fs20.default.existsSync(projectConfigPath)) {
|
|
8480
9223
|
try {
|
|
8481
9224
|
JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
|
|
@@ -8487,7 +9230,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8487
9230
|
);
|
|
8488
9231
|
}
|
|
8489
9232
|
}
|
|
8490
|
-
const credsPath =
|
|
9233
|
+
const credsPath = import_path21.default.join(homeDir2, ".node9", "credentials.json");
|
|
8491
9234
|
if (import_fs20.default.existsSync(credsPath)) {
|
|
8492
9235
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
8493
9236
|
} else {
|
|
@@ -8497,7 +9240,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8497
9240
|
);
|
|
8498
9241
|
}
|
|
8499
9242
|
section("Agent Hooks");
|
|
8500
|
-
const claudeSettingsPath =
|
|
9243
|
+
const claudeSettingsPath = import_path21.default.join(homeDir2, ".claude", "settings.json");
|
|
8501
9244
|
if (import_fs20.default.existsSync(claudeSettingsPath)) {
|
|
8502
9245
|
try {
|
|
8503
9246
|
const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
@@ -8516,7 +9259,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8516
9259
|
} else {
|
|
8517
9260
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
8518
9261
|
}
|
|
8519
|
-
const geminiSettingsPath =
|
|
9262
|
+
const geminiSettingsPath = import_path21.default.join(homeDir2, ".gemini", "settings.json");
|
|
8520
9263
|
if (import_fs20.default.existsSync(geminiSettingsPath)) {
|
|
8521
9264
|
try {
|
|
8522
9265
|
const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
@@ -8535,7 +9278,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8535
9278
|
} else {
|
|
8536
9279
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
8537
9280
|
}
|
|
8538
|
-
const cursorHooksPath =
|
|
9281
|
+
const cursorHooksPath = import_path21.default.join(homeDir2, ".cursor", "hooks.json");
|
|
8539
9282
|
if (import_fs20.default.existsSync(cursorHooksPath)) {
|
|
8540
9283
|
try {
|
|
8541
9284
|
const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
@@ -8577,8 +9320,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8577
9320
|
// src/cli/commands/audit.ts
|
|
8578
9321
|
var import_chalk8 = __toESM(require("chalk"));
|
|
8579
9322
|
var import_fs21 = __toESM(require("fs"));
|
|
8580
|
-
var
|
|
8581
|
-
var
|
|
9323
|
+
var import_path22 = __toESM(require("path"));
|
|
9324
|
+
var import_os17 = __toESM(require("os"));
|
|
8582
9325
|
function formatRelativeTime(timestamp) {
|
|
8583
9326
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
8584
9327
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -8591,7 +9334,7 @@ function formatRelativeTime(timestamp) {
|
|
|
8591
9334
|
}
|
|
8592
9335
|
function registerAuditCommand(program2) {
|
|
8593
9336
|
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 =
|
|
9337
|
+
const logPath = import_path22.default.join(import_os17.default.homedir(), ".node9", "audit.log");
|
|
8595
9338
|
if (!import_fs21.default.existsSync(logPath)) {
|
|
8596
9339
|
console.log(
|
|
8597
9340
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
@@ -8717,8 +9460,8 @@ function registerDaemonCommand(program2) {
|
|
|
8717
9460
|
// src/cli/commands/status.ts
|
|
8718
9461
|
var import_chalk10 = __toESM(require("chalk"));
|
|
8719
9462
|
var import_fs22 = __toESM(require("fs"));
|
|
8720
|
-
var
|
|
8721
|
-
var
|
|
9463
|
+
var import_path23 = __toESM(require("path"));
|
|
9464
|
+
var import_os18 = __toESM(require("os"));
|
|
8722
9465
|
init_core();
|
|
8723
9466
|
init_daemon();
|
|
8724
9467
|
function readJson2(filePath) {
|
|
@@ -8788,8 +9531,8 @@ function registerStatusCommand(program2) {
|
|
|
8788
9531
|
console.log("");
|
|
8789
9532
|
const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
|
|
8790
9533
|
console.log(` Mode: ${modeLabel}`);
|
|
8791
|
-
const projectConfig =
|
|
8792
|
-
const globalConfig =
|
|
9534
|
+
const projectConfig = import_path23.default.join(process.cwd(), "node9.config.json");
|
|
9535
|
+
const globalConfig = import_path23.default.join(import_os18.default.homedir(), ".node9", "config.json");
|
|
8793
9536
|
console.log(
|
|
8794
9537
|
` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
|
|
8795
9538
|
);
|
|
@@ -8801,15 +9544,15 @@ function registerStatusCommand(program2) {
|
|
|
8801
9544
|
` Sandbox: ${import_chalk10.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
8802
9545
|
);
|
|
8803
9546
|
}
|
|
8804
|
-
const homeDir2 =
|
|
9547
|
+
const homeDir2 = import_os18.default.homedir();
|
|
8805
9548
|
const claudeSettings = readJson2(
|
|
8806
|
-
|
|
9549
|
+
import_path23.default.join(homeDir2, ".claude", "settings.json")
|
|
8807
9550
|
);
|
|
8808
|
-
const claudeConfig = readJson2(
|
|
9551
|
+
const claudeConfig = readJson2(import_path23.default.join(homeDir2, ".claude.json"));
|
|
8809
9552
|
const geminiSettings = readJson2(
|
|
8810
|
-
|
|
9553
|
+
import_path23.default.join(homeDir2, ".gemini", "settings.json")
|
|
8811
9554
|
);
|
|
8812
|
-
const cursorConfig = readJson2(
|
|
9555
|
+
const cursorConfig = readJson2(import_path23.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
8813
9556
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
8814
9557
|
if (agentFound) {
|
|
8815
9558
|
console.log("");
|
|
@@ -8869,13 +9612,13 @@ function registerStatusCommand(program2) {
|
|
|
8869
9612
|
// src/cli/commands/init.ts
|
|
8870
9613
|
var import_chalk11 = __toESM(require("chalk"));
|
|
8871
9614
|
var import_fs23 = __toESM(require("fs"));
|
|
8872
|
-
var
|
|
8873
|
-
var
|
|
9615
|
+
var import_path24 = __toESM(require("path"));
|
|
9616
|
+
var import_os19 = __toESM(require("os"));
|
|
8874
9617
|
init_core();
|
|
8875
9618
|
function registerInitCommand(program2) {
|
|
8876
9619
|
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
9620
|
console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
8878
|
-
const configPath =
|
|
9621
|
+
const configPath = import_path24.default.join(import_os19.default.homedir(), ".node9", "config.json");
|
|
8879
9622
|
if (import_fs23.default.existsSync(configPath) && !options.force) {
|
|
8880
9623
|
console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
8881
9624
|
} else {
|
|
@@ -8885,7 +9628,7 @@ function registerInitCommand(program2) {
|
|
|
8885
9628
|
...DEFAULT_CONFIG,
|
|
8886
9629
|
settings: { ...DEFAULT_CONFIG.settings, mode: safeMode }
|
|
8887
9630
|
};
|
|
8888
|
-
const dir =
|
|
9631
|
+
const dir = import_path24.default.dirname(configPath);
|
|
8889
9632
|
if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
|
|
8890
9633
|
import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
8891
9634
|
console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
|
|
@@ -9343,20 +10086,20 @@ function registerTrustCommand(program2) {
|
|
|
9343
10086
|
|
|
9344
10087
|
// src/cli.ts
|
|
9345
10088
|
var { version } = JSON.parse(
|
|
9346
|
-
|
|
10089
|
+
import_fs26.default.readFileSync(import_path27.default.join(__dirname, "../package.json"), "utf-8")
|
|
9347
10090
|
);
|
|
9348
10091
|
var program = new import_commander.Command();
|
|
9349
10092
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
9350
10093
|
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
10094
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
9352
10095
|
const credPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "credentials.json");
|
|
9353
|
-
if (!
|
|
9354
|
-
|
|
10096
|
+
if (!import_fs26.default.existsSync(import_path27.default.dirname(credPath)))
|
|
10097
|
+
import_fs26.default.mkdirSync(import_path27.default.dirname(credPath), { recursive: true });
|
|
9355
10098
|
const profileName = options.profile || "default";
|
|
9356
10099
|
let existingCreds = {};
|
|
9357
10100
|
try {
|
|
9358
|
-
if (
|
|
9359
|
-
const raw = JSON.parse(
|
|
10101
|
+
if (import_fs26.default.existsSync(credPath)) {
|
|
10102
|
+
const raw = JSON.parse(import_fs26.default.readFileSync(credPath, "utf-8"));
|
|
9360
10103
|
if (raw.apiKey) {
|
|
9361
10104
|
existingCreds = {
|
|
9362
10105
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -9368,13 +10111,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
9368
10111
|
} catch {
|
|
9369
10112
|
}
|
|
9370
10113
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
9371
|
-
|
|
10114
|
+
import_fs26.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
9372
10115
|
if (profileName === "default") {
|
|
9373
10116
|
const configPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
9374
10117
|
let config = {};
|
|
9375
10118
|
try {
|
|
9376
|
-
if (
|
|
9377
|
-
config = JSON.parse(
|
|
10119
|
+
if (import_fs26.default.existsSync(configPath))
|
|
10120
|
+
config = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
|
|
9378
10121
|
} catch {
|
|
9379
10122
|
}
|
|
9380
10123
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -9389,9 +10132,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
9389
10132
|
approvers.cloud = false;
|
|
9390
10133
|
}
|
|
9391
10134
|
s.approvers = approvers;
|
|
9392
|
-
if (!
|
|
9393
|
-
|
|
9394
|
-
|
|
10135
|
+
if (!import_fs26.default.existsSync(import_path27.default.dirname(configPath)))
|
|
10136
|
+
import_fs26.default.mkdirSync(import_path27.default.dirname(configPath), { recursive: true });
|
|
10137
|
+
import_fs26.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
9395
10138
|
}
|
|
9396
10139
|
if (options.profile && profileName !== "default") {
|
|
9397
10140
|
console.log(import_chalk17.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -9404,14 +10147,15 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
9404
10147
|
console.log(import_chalk17.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
9405
10148
|
}
|
|
9406
10149
|
});
|
|
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) => {
|
|
10150
|
+
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
10151
|
if (target === "gemini") return await setupGemini();
|
|
9409
10152
|
if (target === "claude") return await setupClaude();
|
|
9410
10153
|
if (target === "cursor") return await setupCursor();
|
|
9411
|
-
|
|
10154
|
+
if (target === "hud") return setupHud();
|
|
10155
|
+
console.error(import_chalk17.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
9412
10156
|
process.exit(1);
|
|
9413
10157
|
});
|
|
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) => {
|
|
10158
|
+
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
10159
|
if (!target) {
|
|
9416
10160
|
console.log(import_chalk17.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
9417
10161
|
console.log(" Usage: " + import_chalk17.default.white("node9 setup <target>") + "\n");
|
|
@@ -9419,6 +10163,9 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
9419
10163
|
console.log(" " + import_chalk17.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
9420
10164
|
console.log(" " + import_chalk17.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
9421
10165
|
console.log(" " + import_chalk17.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
10166
|
+
process.stdout.write(
|
|
10167
|
+
" " + import_chalk17.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
10168
|
+
);
|
|
9422
10169
|
console.log("");
|
|
9423
10170
|
return;
|
|
9424
10171
|
}
|
|
@@ -9426,7 +10173,8 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
9426
10173
|
if (t === "gemini") return await setupGemini();
|
|
9427
10174
|
if (t === "claude") return await setupClaude();
|
|
9428
10175
|
if (t === "cursor") return await setupCursor();
|
|
9429
|
-
|
|
10176
|
+
if (t === "hud") return setupHud();
|
|
10177
|
+
console.error(import_chalk17.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
9430
10178
|
process.exit(1);
|
|
9431
10179
|
});
|
|
9432
10180
|
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,8 +10182,11 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
9434
10182
|
if (target === "claude") fn = teardownClaude;
|
|
9435
10183
|
else if (target === "gemini") fn = teardownGemini;
|
|
9436
10184
|
else if (target === "cursor") fn = teardownCursor;
|
|
10185
|
+
else if (target === "hud") fn = teardownHud;
|
|
9437
10186
|
else {
|
|
9438
|
-
console.error(
|
|
10187
|
+
console.error(
|
|
10188
|
+
import_chalk17.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
10189
|
+
);
|
|
9439
10190
|
process.exit(1);
|
|
9440
10191
|
}
|
|
9441
10192
|
console.log(import_chalk17.default.cyan(`
|
|
@@ -9478,14 +10229,14 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
9478
10229
|
}
|
|
9479
10230
|
if (options.purge) {
|
|
9480
10231
|
const node9Dir = import_path27.default.join(import_os22.default.homedir(), ".node9");
|
|
9481
|
-
if (
|
|
10232
|
+
if (import_fs26.default.existsSync(node9Dir)) {
|
|
9482
10233
|
const confirmed = await (0, import_prompts3.confirm)({
|
|
9483
10234
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
9484
10235
|
default: false
|
|
9485
10236
|
});
|
|
9486
10237
|
if (confirmed) {
|
|
9487
|
-
|
|
9488
|
-
if (
|
|
10238
|
+
import_fs26.default.rmSync(node9Dir, { recursive: true });
|
|
10239
|
+
if (import_fs26.default.existsSync(node9Dir)) {
|
|
9489
10240
|
console.error(
|
|
9490
10241
|
import_chalk17.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
9491
10242
|
);
|
|
@@ -9601,6 +10352,10 @@ registerWatchCommand(program);
|
|
|
9601
10352
|
registerMcpGatewayCommand(program);
|
|
9602
10353
|
registerCheckCommand(program);
|
|
9603
10354
|
registerLogCommand(program);
|
|
10355
|
+
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").action(async () => {
|
|
10356
|
+
const { main: main2 } = await Promise.resolve().then(() => (init_hud(), hud_exports));
|
|
10357
|
+
await main2();
|
|
10358
|
+
});
|
|
9604
10359
|
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
10360
|
const ms = parseDuration(options.duration);
|
|
9606
10361
|
if (ms === null) {
|
|
@@ -9699,7 +10454,7 @@ if (process.argv[2] !== "daemon") {
|
|
|
9699
10454
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
9700
10455
|
const logPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log");
|
|
9701
10456
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
9702
|
-
|
|
10457
|
+
import_fs26.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
9703
10458
|
`);
|
|
9704
10459
|
}
|
|
9705
10460
|
process.exit(0);
|