@node9/proxy 1.9.3 → 1.10.1
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 +32 -3
- package/dist/cli.js +1372 -625
- package/dist/cli.mjs +1354 -607
- package/dist/index.js +128 -25
- package/dist/index.mjs +126 -23
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -76,6 +76,11 @@ __export(audit_exports, {
|
|
|
76
76
|
appendToLog: () => appendToLog,
|
|
77
77
|
redactSecrets: () => redactSecrets
|
|
78
78
|
});
|
|
79
|
+
function isTestCall(toolName, args) {
|
|
80
|
+
if (toolName !== "Bash" && toolName !== "bash") return false;
|
|
81
|
+
const cmd = args?.command;
|
|
82
|
+
return typeof cmd === "string" && TEST_COMMAND_RE.test(cmd);
|
|
83
|
+
}
|
|
79
84
|
function redactSecrets(text) {
|
|
80
85
|
if (!text) return text;
|
|
81
86
|
let redacted = text;
|
|
@@ -111,12 +116,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
111
116
|
}
|
|
112
117
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
113
118
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
119
|
+
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
114
120
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
115
121
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
116
122
|
tool: toolName,
|
|
117
123
|
...argsField,
|
|
118
124
|
decision,
|
|
119
125
|
checkedBy,
|
|
126
|
+
...testRun,
|
|
120
127
|
agent: meta?.agent,
|
|
121
128
|
mcpServer: meta?.mcpServer,
|
|
122
129
|
hostname: import_os.default.hostname()
|
|
@@ -129,7 +136,7 @@ function appendConfigAudit(entry) {
|
|
|
129
136
|
hostname: import_os.default.hostname()
|
|
130
137
|
});
|
|
131
138
|
}
|
|
132
|
-
var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG;
|
|
139
|
+
var import_fs, import_path, import_os, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, TEST_COMMAND_RE;
|
|
133
140
|
var init_audit = __esm({
|
|
134
141
|
"src/audit/index.ts"() {
|
|
135
142
|
"use strict";
|
|
@@ -139,6 +146,7 @@ var init_audit = __esm({
|
|
|
139
146
|
init_hasher();
|
|
140
147
|
LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
|
|
141
148
|
HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
|
|
149
|
+
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;
|
|
142
150
|
}
|
|
143
151
|
});
|
|
144
152
|
|
|
@@ -160,8 +168,8 @@ function sanitizeConfig(raw) {
|
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
const lines = result.error.issues.map((issue) => {
|
|
163
|
-
const
|
|
164
|
-
return ` \u2022 ${
|
|
171
|
+
const path35 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
172
|
+
return ` \u2022 ${path35}: ${issue.message}`;
|
|
165
173
|
});
|
|
166
174
|
return {
|
|
167
175
|
sanitized,
|
|
@@ -245,7 +253,8 @@ var init_config_schema = __esm({
|
|
|
245
253
|
slackEnabled: import_zod.z.boolean().optional(),
|
|
246
254
|
enableTrustSessions: import_zod.z.boolean().optional(),
|
|
247
255
|
allowGlobalPause: import_zod.z.boolean().optional(),
|
|
248
|
-
auditHashArgs: import_zod.z.boolean().optional()
|
|
256
|
+
auditHashArgs: import_zod.z.boolean().optional(),
|
|
257
|
+
agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
|
|
249
258
|
}).optional(),
|
|
250
259
|
policy: import_zod.z.object({
|
|
251
260
|
sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
@@ -261,6 +270,11 @@ var init_config_schema = __esm({
|
|
|
261
270
|
dlp: import_zod.z.object({
|
|
262
271
|
enabled: import_zod.z.boolean().optional(),
|
|
263
272
|
scanIgnoredTools: import_zod.z.boolean().optional()
|
|
273
|
+
}).optional(),
|
|
274
|
+
loopDetection: import_zod.z.object({
|
|
275
|
+
enabled: import_zod.z.boolean().optional(),
|
|
276
|
+
threshold: import_zod.z.number().min(2).optional(),
|
|
277
|
+
windowSeconds: import_zod.z.number().min(10).optional()
|
|
264
278
|
}).optional()
|
|
265
279
|
}).optional(),
|
|
266
280
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -555,7 +569,8 @@ function getConfig(cwd) {
|
|
|
555
569
|
onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
|
|
556
570
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
557
571
|
},
|
|
558
|
-
dlp: { ...DEFAULT_CONFIG.policy.dlp }
|
|
572
|
+
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
573
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
559
574
|
};
|
|
560
575
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
561
576
|
const applyLayer = (source) => {
|
|
@@ -594,6 +609,13 @@ function getConfig(cwd) {
|
|
|
594
609
|
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
595
610
|
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
596
611
|
}
|
|
612
|
+
if (p.loopDetection) {
|
|
613
|
+
const ld = p.loopDetection;
|
|
614
|
+
if (ld.enabled !== void 0) mergedPolicy.loopDetection.enabled = ld.enabled;
|
|
615
|
+
if (ld.threshold !== void 0) mergedPolicy.loopDetection.threshold = ld.threshold;
|
|
616
|
+
if (ld.windowSeconds !== void 0)
|
|
617
|
+
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
618
|
+
}
|
|
597
619
|
const envs = source.environments || {};
|
|
598
620
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
599
621
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -900,7 +922,8 @@ var init_config = __esm({
|
|
|
900
922
|
description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
|
|
901
923
|
}
|
|
902
924
|
],
|
|
903
|
-
dlp: { enabled: true, scanIgnoredTools: true }
|
|
925
|
+
dlp: { enabled: true, scanIgnoredTools: true },
|
|
926
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
904
927
|
},
|
|
905
928
|
environments: {}
|
|
906
929
|
};
|
|
@@ -1704,9 +1727,9 @@ function matchesPattern(text, patterns) {
|
|
|
1704
1727
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1705
1728
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1706
1729
|
}
|
|
1707
|
-
function getNestedValue(obj,
|
|
1730
|
+
function getNestedValue(obj, path35) {
|
|
1708
1731
|
if (!obj || typeof obj !== "object") return null;
|
|
1709
|
-
return
|
|
1732
|
+
return path35.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1710
1733
|
}
|
|
1711
1734
|
function shouldSnapshot(toolName, args, config) {
|
|
1712
1735
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2789,11 +2812,12 @@ ${smartTruncate(str, 500)}`
|
|
|
2789
2812
|
function escapePango(text) {
|
|
2790
2813
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2791
2814
|
}
|
|
2792
|
-
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2815
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2793
2816
|
const lines = [];
|
|
2794
2817
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2795
2818
|
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
2796
2819
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2820
|
+
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2797
2821
|
lines.push("");
|
|
2798
2822
|
lines.push(formattedArgs);
|
|
2799
2823
|
if (allowCount >= 3) {
|
|
@@ -2806,7 +2830,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2806
2830
|
}
|
|
2807
2831
|
return lines.join("\n");
|
|
2808
2832
|
}
|
|
2809
|
-
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
|
|
2833
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2810
2834
|
const lines = [];
|
|
2811
2835
|
if (locked) {
|
|
2812
2836
|
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
@@ -2816,6 +2840,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2816
2840
|
`<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
|
|
2817
2841
|
);
|
|
2818
2842
|
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
2843
|
+
if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
|
|
2819
2844
|
lines.push("");
|
|
2820
2845
|
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
2821
2846
|
if (allowCount >= 3) {
|
|
@@ -2832,7 +2857,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
|
|
|
2832
2857
|
}
|
|
2833
2858
|
return lines.join("\n");
|
|
2834
2859
|
}
|
|
2835
|
-
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
|
|
2860
|
+
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
|
|
2836
2861
|
if (isTestEnv()) return "deny";
|
|
2837
2862
|
const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
|
|
2838
2863
|
const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
|
|
@@ -2843,7 +2868,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
2843
2868
|
agent,
|
|
2844
2869
|
explainableLabel,
|
|
2845
2870
|
locked,
|
|
2846
|
-
allowCount
|
|
2871
|
+
allowCount,
|
|
2872
|
+
ruleDescription
|
|
2847
2873
|
);
|
|
2848
2874
|
return new Promise((resolve) => {
|
|
2849
2875
|
let childProcess = null;
|
|
@@ -2877,7 +2903,8 @@ end run`;
|
|
|
2877
2903
|
agent,
|
|
2878
2904
|
explainableLabel,
|
|
2879
2905
|
locked,
|
|
2880
|
-
allowCount
|
|
2906
|
+
allowCount,
|
|
2907
|
+
ruleDescription
|
|
2881
2908
|
);
|
|
2882
2909
|
const argsList = [
|
|
2883
2910
|
locked ? "--info" : "--question",
|
|
@@ -2950,7 +2977,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
2950
2977
|
}).catch(() => {
|
|
2951
2978
|
});
|
|
2952
2979
|
}
|
|
2953
|
-
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
2980
|
+
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
2954
2981
|
const controller = new AbortController();
|
|
2955
2982
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2956
2983
|
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
@@ -2995,7 +3022,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
2995
3022
|
platform: import_os9.default.platform()
|
|
2996
3023
|
},
|
|
2997
3024
|
...riskMetadata && { riskMetadata },
|
|
2998
|
-
...ciContext && { ciContext }
|
|
3025
|
+
...ciContext && { ciContext },
|
|
3026
|
+
...agentPolicy && { policy: agentPolicy },
|
|
3027
|
+
...forceReview && { forceReview: true }
|
|
2999
3028
|
}),
|
|
3000
3029
|
signal: controller.signal
|
|
3001
3030
|
});
|
|
@@ -3079,6 +3108,58 @@ var init_cloud = __esm({
|
|
|
3079
3108
|
}
|
|
3080
3109
|
});
|
|
3081
3110
|
|
|
3111
|
+
// src/loop-detector.ts
|
|
3112
|
+
function loopStateFile() {
|
|
3113
|
+
return import_path14.default.join(import_os10.default.homedir(), ".node9", "loop-state.json");
|
|
3114
|
+
}
|
|
3115
|
+
function computeArgsHash(args) {
|
|
3116
|
+
const str = JSON.stringify(args ?? "");
|
|
3117
|
+
return import_crypto3.default.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
3118
|
+
}
|
|
3119
|
+
function readState() {
|
|
3120
|
+
try {
|
|
3121
|
+
if (!import_fs11.default.existsSync(loopStateFile())) return [];
|
|
3122
|
+
const raw = import_fs11.default.readFileSync(loopStateFile(), "utf-8");
|
|
3123
|
+
const parsed = JSON.parse(raw);
|
|
3124
|
+
if (!Array.isArray(parsed)) return [];
|
|
3125
|
+
return parsed;
|
|
3126
|
+
} catch {
|
|
3127
|
+
return [];
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
function writeState(records) {
|
|
3131
|
+
const dir = import_path14.default.dirname(loopStateFile());
|
|
3132
|
+
if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
|
|
3133
|
+
const tmpPath = `${loopStateFile()}.${import_os10.default.hostname()}.${process.pid}.tmp`;
|
|
3134
|
+
import_fs11.default.writeFileSync(tmpPath, JSON.stringify(records));
|
|
3135
|
+
import_fs11.default.renameSync(tmpPath, loopStateFile());
|
|
3136
|
+
}
|
|
3137
|
+
function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
3138
|
+
try {
|
|
3139
|
+
const hash = computeArgsHash(args);
|
|
3140
|
+
const now = Date.now();
|
|
3141
|
+
const cutoff = now - windowMs;
|
|
3142
|
+
const records = readState().filter((r) => r.ts >= cutoff);
|
|
3143
|
+
records.push({ t: tool, h: hash, ts: now });
|
|
3144
|
+
const count = records.filter((r) => r.t === tool && r.h === hash).length;
|
|
3145
|
+
writeState(records.slice(-MAX_RECORDS));
|
|
3146
|
+
return { looping: count >= threshold, count };
|
|
3147
|
+
} catch {
|
|
3148
|
+
return { looping: false, count: 0 };
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
var import_fs11, import_path14, import_os10, import_crypto3, MAX_RECORDS;
|
|
3152
|
+
var init_loop_detector = __esm({
|
|
3153
|
+
"src/loop-detector.ts"() {
|
|
3154
|
+
"use strict";
|
|
3155
|
+
import_fs11 = __toESM(require("fs"));
|
|
3156
|
+
import_path14 = __toESM(require("path"));
|
|
3157
|
+
import_os10 = __toESM(require("os"));
|
|
3158
|
+
import_crypto3 = __toESM(require("crypto"));
|
|
3159
|
+
MAX_RECORDS = 500;
|
|
3160
|
+
}
|
|
3161
|
+
});
|
|
3162
|
+
|
|
3082
3163
|
// src/auth/orchestrator.ts
|
|
3083
3164
|
function isWriteTool(toolName) {
|
|
3084
3165
|
const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
|
|
@@ -3119,7 +3200,7 @@ function notifyActivity(data) {
|
|
|
3119
3200
|
}
|
|
3120
3201
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
3121
3202
|
if (!options?.calledFromDaemon) {
|
|
3122
|
-
const actId = (0,
|
|
3203
|
+
const actId = (0, import_crypto4.randomUUID)();
|
|
3123
3204
|
const actTs = Date.now();
|
|
3124
3205
|
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
3125
3206
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
@@ -3166,6 +3247,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3166
3247
|
let explainableLabel = "Local Config";
|
|
3167
3248
|
let policyMatchedField;
|
|
3168
3249
|
let policyMatchedWord;
|
|
3250
|
+
let policyRuleDescription;
|
|
3169
3251
|
let riskMetadata;
|
|
3170
3252
|
let statefulRecoveryCommand;
|
|
3171
3253
|
let localSmartRuleMatched = false;
|
|
@@ -3259,6 +3341,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3259
3341
|
return { approved: true, checkedBy: "audit" };
|
|
3260
3342
|
}
|
|
3261
3343
|
if (!taintWarning && !isIgnoredTool(toolName)) {
|
|
3344
|
+
const ld = config.policy.loopDetection;
|
|
3345
|
+
if (ld.enabled) {
|
|
3346
|
+
const loopResult = recordAndCheck(toolName, args, ld.threshold, ld.windowSeconds * 1e3);
|
|
3347
|
+
if (loopResult.looping) {
|
|
3348
|
+
const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
|
|
3349
|
+
if (!isManual)
|
|
3350
|
+
appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
|
|
3351
|
+
return {
|
|
3352
|
+
approved: false,
|
|
3353
|
+
reason,
|
|
3354
|
+
blockedBy: "loop-detection",
|
|
3355
|
+
blockedByLabel: "\u{1F504} Loop Detected"
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3262
3359
|
if (getActiveTrustSession(toolName)) {
|
|
3263
3360
|
if (approvers.cloud && creds?.apiKey)
|
|
3264
3361
|
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
@@ -3314,6 +3411,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3314
3411
|
policyMatchedField = policyResult.matchedField;
|
|
3315
3412
|
policyMatchedWord = policyResult.matchedWord;
|
|
3316
3413
|
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
3414
|
+
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
3415
|
+
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
3317
3416
|
riskMetadata = computeRiskMetadata(
|
|
3318
3417
|
args,
|
|
3319
3418
|
policyResult.tier ?? 6,
|
|
@@ -3322,6 +3421,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3322
3421
|
policyMatchedWord,
|
|
3323
3422
|
policyResult.ruleName
|
|
3324
3423
|
);
|
|
3424
|
+
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
3325
3425
|
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
3326
3426
|
if (persistent === "allow") {
|
|
3327
3427
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -3356,9 +3456,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3356
3456
|
}
|
|
3357
3457
|
let cloudRequestId = null;
|
|
3358
3458
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
3359
|
-
|
|
3459
|
+
const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
|
|
3460
|
+
if (cloudEnforced) {
|
|
3360
3461
|
try {
|
|
3361
|
-
const initResult = await initNode9SaaS(
|
|
3462
|
+
const initResult = await initNode9SaaS(
|
|
3463
|
+
toolName,
|
|
3464
|
+
args,
|
|
3465
|
+
creds,
|
|
3466
|
+
meta,
|
|
3467
|
+
riskMetadata,
|
|
3468
|
+
config.settings.agentPolicy,
|
|
3469
|
+
forceReview
|
|
3470
|
+
);
|
|
3362
3471
|
if (!initResult.pending) {
|
|
3363
3472
|
if (initResult.shadowMode) {
|
|
3364
3473
|
return { approved: true, checkedBy: "cloud" };
|
|
@@ -3373,9 +3482,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3373
3482
|
};
|
|
3374
3483
|
}
|
|
3375
3484
|
}
|
|
3376
|
-
if (
|
|
3377
|
-
cloudRequestId = initResult.requestId || null;
|
|
3378
|
-
}
|
|
3485
|
+
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
3379
3486
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
3380
3487
|
} catch {
|
|
3381
3488
|
}
|
|
@@ -3432,7 +3539,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3432
3539
|
}
|
|
3433
3540
|
}
|
|
3434
3541
|
}
|
|
3435
|
-
if (cloudEnforced && cloudRequestId
|
|
3542
|
+
if (cloudEnforced && cloudRequestId) {
|
|
3436
3543
|
racePromises.push(
|
|
3437
3544
|
(async () => {
|
|
3438
3545
|
try {
|
|
@@ -3464,7 +3571,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3464
3571
|
signal,
|
|
3465
3572
|
policyMatchedField,
|
|
3466
3573
|
policyMatchedWord,
|
|
3467
|
-
daemonAllowCount
|
|
3574
|
+
daemonAllowCount,
|
|
3575
|
+
riskMetadata?.ruleDescription
|
|
3468
3576
|
);
|
|
3469
3577
|
if (decision === "always_allow") {
|
|
3470
3578
|
writeTrustSession(toolName, 36e5);
|
|
@@ -3577,13 +3685,14 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3577
3685
|
hashAuditArgs
|
|
3578
3686
|
);
|
|
3579
3687
|
}
|
|
3580
|
-
|
|
3688
|
+
const enrichedResult = !finalResult.approved && policyRuleDescription && !finalResult.ruleDescription ? { ...finalResult, ruleDescription: policyRuleDescription } : finalResult;
|
|
3689
|
+
return enrichedResult;
|
|
3581
3690
|
}
|
|
3582
|
-
var
|
|
3691
|
+
var import_crypto4, WRITE_TOOLS;
|
|
3583
3692
|
var init_orchestrator = __esm({
|
|
3584
3693
|
"src/auth/orchestrator.ts"() {
|
|
3585
3694
|
"use strict";
|
|
3586
|
-
|
|
3695
|
+
import_crypto4 = require("crypto");
|
|
3587
3696
|
init_native();
|
|
3588
3697
|
init_context_sniper();
|
|
3589
3698
|
init_dlp();
|
|
@@ -3593,6 +3702,7 @@ var init_orchestrator = __esm({
|
|
|
3593
3702
|
init_state();
|
|
3594
3703
|
init_daemon();
|
|
3595
3704
|
init_cloud();
|
|
3705
|
+
init_loop_detector();
|
|
3596
3706
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
3597
3707
|
"write",
|
|
3598
3708
|
"write_file",
|
|
@@ -5266,11 +5376,11 @@ function commonPathPrefix(paths) {
|
|
|
5266
5376
|
const prefix = common.join("/").replace(/\/?$/, "/");
|
|
5267
5377
|
return prefix.length > 1 ? prefix : null;
|
|
5268
5378
|
}
|
|
5269
|
-
var
|
|
5379
|
+
var import_crypto5, SuggestionTracker;
|
|
5270
5380
|
var init_suggestion_tracker = __esm({
|
|
5271
5381
|
"src/daemon/suggestion-tracker.ts"() {
|
|
5272
5382
|
"use strict";
|
|
5273
|
-
|
|
5383
|
+
import_crypto5 = require("crypto");
|
|
5274
5384
|
SuggestionTracker = class {
|
|
5275
5385
|
events = /* @__PURE__ */ new Map();
|
|
5276
5386
|
threshold;
|
|
@@ -5316,7 +5426,7 @@ var init_suggestion_tracker = __esm({
|
|
|
5316
5426
|
}
|
|
5317
5427
|
} : { type: "ignoredTool", toolName };
|
|
5318
5428
|
return {
|
|
5319
|
-
id: (0,
|
|
5429
|
+
id: (0, import_crypto5.randomUUID)(),
|
|
5320
5430
|
toolName,
|
|
5321
5431
|
allowCount: events.length,
|
|
5322
5432
|
suggestedRule,
|
|
@@ -5330,12 +5440,12 @@ var init_suggestion_tracker = __esm({
|
|
|
5330
5440
|
});
|
|
5331
5441
|
|
|
5332
5442
|
// src/daemon/taint-store.ts
|
|
5333
|
-
var
|
|
5443
|
+
var import_fs13, import_path16, DEFAULT_TTL_MS, TaintStore;
|
|
5334
5444
|
var init_taint_store = __esm({
|
|
5335
5445
|
"src/daemon/taint-store.ts"() {
|
|
5336
5446
|
"use strict";
|
|
5337
|
-
|
|
5338
|
-
|
|
5447
|
+
import_fs13 = __toESM(require("fs"));
|
|
5448
|
+
import_path16 = __toESM(require("path"));
|
|
5339
5449
|
DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
5340
5450
|
TaintStore = class {
|
|
5341
5451
|
records = /* @__PURE__ */ new Map();
|
|
@@ -5400,9 +5510,9 @@ var init_taint_store = __esm({
|
|
|
5400
5510
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
5401
5511
|
_resolve(filePath) {
|
|
5402
5512
|
try {
|
|
5403
|
-
return
|
|
5513
|
+
return import_fs13.default.realpathSync.native(import_path16.default.resolve(filePath));
|
|
5404
5514
|
} catch {
|
|
5405
|
-
return
|
|
5515
|
+
return import_path16.default.resolve(filePath);
|
|
5406
5516
|
}
|
|
5407
5517
|
}
|
|
5408
5518
|
};
|
|
@@ -5520,8 +5630,8 @@ var init_session_history = __esm({
|
|
|
5520
5630
|
// src/daemon/state.ts
|
|
5521
5631
|
function loadInsightCounts() {
|
|
5522
5632
|
try {
|
|
5523
|
-
if (!
|
|
5524
|
-
const data = JSON.parse(
|
|
5633
|
+
if (!import_fs14.default.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
5634
|
+
const data = JSON.parse(import_fs14.default.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
5525
5635
|
for (const [tool, count] of Object.entries(data)) {
|
|
5526
5636
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
5527
5637
|
}
|
|
@@ -5560,23 +5670,23 @@ function markRejectionHandlerRegistered() {
|
|
|
5560
5670
|
daemonRejectionHandlerRegistered = true;
|
|
5561
5671
|
}
|
|
5562
5672
|
function atomicWriteSync2(filePath, data, options) {
|
|
5563
|
-
const dir =
|
|
5564
|
-
if (!
|
|
5565
|
-
const tmpPath = `${filePath}.${(0,
|
|
5673
|
+
const dir = import_path17.default.dirname(filePath);
|
|
5674
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5675
|
+
const tmpPath = `${filePath}.${(0, import_crypto6.randomUUID)()}.tmp`;
|
|
5566
5676
|
try {
|
|
5567
|
-
|
|
5677
|
+
import_fs14.default.writeFileSync(tmpPath, data, options);
|
|
5568
5678
|
} catch (err2) {
|
|
5569
5679
|
try {
|
|
5570
|
-
|
|
5680
|
+
import_fs14.default.unlinkSync(tmpPath);
|
|
5571
5681
|
} catch {
|
|
5572
5682
|
}
|
|
5573
5683
|
throw err2;
|
|
5574
5684
|
}
|
|
5575
5685
|
try {
|
|
5576
|
-
|
|
5686
|
+
import_fs14.default.renameSync(tmpPath, filePath);
|
|
5577
5687
|
} catch (err2) {
|
|
5578
5688
|
try {
|
|
5579
|
-
|
|
5689
|
+
import_fs14.default.unlinkSync(tmpPath);
|
|
5580
5690
|
} catch {
|
|
5581
5691
|
}
|
|
5582
5692
|
throw err2;
|
|
@@ -5600,16 +5710,16 @@ function appendAuditLog(data) {
|
|
|
5600
5710
|
decision: data.decision,
|
|
5601
5711
|
source: "daemon"
|
|
5602
5712
|
};
|
|
5603
|
-
const dir =
|
|
5604
|
-
if (!
|
|
5605
|
-
|
|
5713
|
+
const dir = import_path17.default.dirname(AUDIT_LOG_FILE);
|
|
5714
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5715
|
+
import_fs14.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
5606
5716
|
} catch {
|
|
5607
5717
|
}
|
|
5608
5718
|
}
|
|
5609
5719
|
function getAuditHistory(limit = 20) {
|
|
5610
5720
|
try {
|
|
5611
|
-
if (!
|
|
5612
|
-
const lines =
|
|
5721
|
+
if (!import_fs14.default.existsSync(AUDIT_LOG_FILE)) return [];
|
|
5722
|
+
const lines = import_fs14.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
5613
5723
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
5614
5724
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
5615
5725
|
} catch {
|
|
@@ -5618,19 +5728,19 @@ function getAuditHistory(limit = 20) {
|
|
|
5618
5728
|
}
|
|
5619
5729
|
function getOrgName() {
|
|
5620
5730
|
try {
|
|
5621
|
-
if (
|
|
5731
|
+
if (import_fs14.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
5622
5732
|
} catch {
|
|
5623
5733
|
}
|
|
5624
5734
|
return null;
|
|
5625
5735
|
}
|
|
5626
5736
|
function hasStoredSlackKey() {
|
|
5627
|
-
return
|
|
5737
|
+
return import_fs14.default.existsSync(CREDENTIALS_FILE);
|
|
5628
5738
|
}
|
|
5629
5739
|
function writeGlobalSetting(key, value) {
|
|
5630
5740
|
let config = {};
|
|
5631
5741
|
try {
|
|
5632
|
-
if (
|
|
5633
|
-
config = JSON.parse(
|
|
5742
|
+
if (import_fs14.default.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
5743
|
+
config = JSON.parse(import_fs14.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
5634
5744
|
}
|
|
5635
5745
|
} catch {
|
|
5636
5746
|
}
|
|
@@ -5642,8 +5752,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5642
5752
|
try {
|
|
5643
5753
|
let trust = { entries: [] };
|
|
5644
5754
|
try {
|
|
5645
|
-
if (
|
|
5646
|
-
trust = JSON.parse(
|
|
5755
|
+
if (import_fs14.default.existsSync(TRUST_FILE2))
|
|
5756
|
+
trust = JSON.parse(import_fs14.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5647
5757
|
} catch {
|
|
5648
5758
|
}
|
|
5649
5759
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -5654,8 +5764,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5654
5764
|
}
|
|
5655
5765
|
function readPersistentDecisions() {
|
|
5656
5766
|
try {
|
|
5657
|
-
if (
|
|
5658
|
-
return JSON.parse(
|
|
5767
|
+
if (import_fs14.default.existsSync(DECISIONS_FILE)) {
|
|
5768
|
+
return JSON.parse(import_fs14.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
5659
5769
|
}
|
|
5660
5770
|
} catch {
|
|
5661
5771
|
}
|
|
@@ -5692,7 +5802,7 @@ function estimateToolCost(tool, args) {
|
|
|
5692
5802
|
const filePath = a.file_path ?? a.path;
|
|
5693
5803
|
if (filePath) {
|
|
5694
5804
|
try {
|
|
5695
|
-
const bytes =
|
|
5805
|
+
const bytes = import_fs14.default.statSync(filePath).size;
|
|
5696
5806
|
return bytes / BYTES_PER_TOKEN / 1e6 * INPUT_PRICE_PER_1M;
|
|
5697
5807
|
} catch {
|
|
5698
5808
|
}
|
|
@@ -5750,7 +5860,7 @@ function abandonPending() {
|
|
|
5750
5860
|
});
|
|
5751
5861
|
if (autoStarted) {
|
|
5752
5862
|
try {
|
|
5753
|
-
|
|
5863
|
+
import_fs14.default.unlinkSync(DAEMON_PID_FILE);
|
|
5754
5864
|
} catch {
|
|
5755
5865
|
}
|
|
5756
5866
|
setTimeout(() => {
|
|
@@ -5761,7 +5871,7 @@ function abandonPending() {
|
|
|
5761
5871
|
}
|
|
5762
5872
|
function startActivitySocket() {
|
|
5763
5873
|
try {
|
|
5764
|
-
|
|
5874
|
+
import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5765
5875
|
} catch {
|
|
5766
5876
|
}
|
|
5767
5877
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -5842,34 +5952,34 @@ function startActivitySocket() {
|
|
|
5842
5952
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
5843
5953
|
process.on("exit", () => {
|
|
5844
5954
|
try {
|
|
5845
|
-
|
|
5955
|
+
import_fs14.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5846
5956
|
} catch {
|
|
5847
5957
|
}
|
|
5848
5958
|
});
|
|
5849
5959
|
}
|
|
5850
|
-
var import_net2,
|
|
5960
|
+
var import_net2, import_fs14, import_path17, import_os12, import_child_process3, import_crypto6, 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, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES;
|
|
5851
5961
|
var init_state2 = __esm({
|
|
5852
5962
|
"src/daemon/state.ts"() {
|
|
5853
5963
|
"use strict";
|
|
5854
5964
|
import_net2 = __toESM(require("net"));
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5965
|
+
import_fs14 = __toESM(require("fs"));
|
|
5966
|
+
import_path17 = __toESM(require("path"));
|
|
5967
|
+
import_os12 = __toESM(require("os"));
|
|
5858
5968
|
import_child_process3 = require("child_process");
|
|
5859
|
-
|
|
5969
|
+
import_crypto6 = require("crypto");
|
|
5860
5970
|
init_daemon();
|
|
5861
5971
|
init_suggestion_tracker();
|
|
5862
5972
|
init_taint_store();
|
|
5863
5973
|
init_session_counters();
|
|
5864
5974
|
init_session_history();
|
|
5865
|
-
homeDir =
|
|
5866
|
-
DAEMON_PID_FILE =
|
|
5867
|
-
DECISIONS_FILE =
|
|
5868
|
-
AUDIT_LOG_FILE =
|
|
5869
|
-
TRUST_FILE2 =
|
|
5870
|
-
GLOBAL_CONFIG_FILE =
|
|
5871
|
-
CREDENTIALS_FILE =
|
|
5872
|
-
INSIGHT_COUNTS_FILE =
|
|
5975
|
+
homeDir = import_os12.default.homedir();
|
|
5976
|
+
DAEMON_PID_FILE = import_path17.default.join(homeDir, ".node9", "daemon.pid");
|
|
5977
|
+
DECISIONS_FILE = import_path17.default.join(homeDir, ".node9", "decisions.json");
|
|
5978
|
+
AUDIT_LOG_FILE = import_path17.default.join(homeDir, ".node9", "audit.log");
|
|
5979
|
+
TRUST_FILE2 = import_path17.default.join(homeDir, ".node9", "trust.json");
|
|
5980
|
+
GLOBAL_CONFIG_FILE = import_path17.default.join(homeDir, ".node9", "config.json");
|
|
5981
|
+
CREDENTIALS_FILE = import_path17.default.join(homeDir, ".node9", "credentials.json");
|
|
5982
|
+
INSIGHT_COUNTS_FILE = import_path17.default.join(homeDir, ".node9", "insight-counts.json");
|
|
5873
5983
|
pending = /* @__PURE__ */ new Map();
|
|
5874
5984
|
sseClients = /* @__PURE__ */ new Set();
|
|
5875
5985
|
suggestionTracker = new SuggestionTracker(3);
|
|
@@ -5887,7 +5997,7 @@ var init_state2 = __esm({
|
|
|
5887
5997
|
"2h": 2 * 60 * 6e4
|
|
5888
5998
|
};
|
|
5889
5999
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5890
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
6000
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path17.default.join(import_os12.default.tmpdir(), "node9-activity.sock");
|
|
5891
6001
|
ACTIVITY_RING_SIZE = 100;
|
|
5892
6002
|
activityRing = [];
|
|
5893
6003
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -5912,8 +6022,8 @@ var init_state2 = __esm({
|
|
|
5912
6022
|
function patchConfig(configPath, patch) {
|
|
5913
6023
|
let config = {};
|
|
5914
6024
|
try {
|
|
5915
|
-
if (
|
|
5916
|
-
config = JSON.parse(
|
|
6025
|
+
if (import_fs15.default.existsSync(configPath)) {
|
|
6026
|
+
config = JSON.parse(import_fs15.default.readFileSync(configPath, "utf8"));
|
|
5917
6027
|
}
|
|
5918
6028
|
} catch {
|
|
5919
6029
|
throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
|
|
@@ -5932,44 +6042,211 @@ function patchConfig(configPath, patch) {
|
|
|
5932
6042
|
ignored.push(patch.toolName);
|
|
5933
6043
|
}
|
|
5934
6044
|
}
|
|
5935
|
-
const dir =
|
|
5936
|
-
|
|
6045
|
+
const dir = import_path18.default.dirname(configPath);
|
|
6046
|
+
import_fs15.default.mkdirSync(dir, { recursive: true });
|
|
5937
6047
|
const tmp = configPath + ".node9-tmp";
|
|
5938
6048
|
try {
|
|
5939
|
-
|
|
6049
|
+
import_fs15.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5940
6050
|
} catch (err2) {
|
|
5941
6051
|
try {
|
|
5942
|
-
|
|
6052
|
+
import_fs15.default.unlinkSync(tmp);
|
|
5943
6053
|
} catch {
|
|
5944
6054
|
}
|
|
5945
6055
|
throw err2;
|
|
5946
6056
|
}
|
|
5947
6057
|
try {
|
|
5948
|
-
|
|
6058
|
+
import_fs15.default.renameSync(tmp, configPath);
|
|
5949
6059
|
} catch (err2) {
|
|
5950
6060
|
try {
|
|
5951
|
-
|
|
6061
|
+
import_fs15.default.unlinkSync(tmp);
|
|
5952
6062
|
} catch {
|
|
5953
6063
|
}
|
|
5954
6064
|
throw err2;
|
|
5955
6065
|
}
|
|
5956
6066
|
}
|
|
5957
|
-
var
|
|
6067
|
+
var import_fs15, import_path18, import_os13, GLOBAL_CONFIG_PATH;
|
|
5958
6068
|
var init_patch = __esm({
|
|
5959
6069
|
"src/config/patch.ts"() {
|
|
5960
6070
|
"use strict";
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
GLOBAL_CONFIG_PATH =
|
|
6071
|
+
import_fs15 = __toESM(require("fs"));
|
|
6072
|
+
import_path18 = __toESM(require("path"));
|
|
6073
|
+
import_os13 = __toESM(require("os"));
|
|
6074
|
+
GLOBAL_CONFIG_PATH = import_path18.default.join(import_os13.default.homedir(), ".node9", "config.json");
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
6077
|
+
|
|
6078
|
+
// src/costSync.ts
|
|
6079
|
+
function normalizeModel(raw) {
|
|
6080
|
+
return raw.replace(/-\d{8}$/, "");
|
|
6081
|
+
}
|
|
6082
|
+
function pricingFor(model) {
|
|
6083
|
+
const norm = normalizeModel(model);
|
|
6084
|
+
if (PRICING[norm]) return PRICING[norm];
|
|
6085
|
+
let best = null;
|
|
6086
|
+
for (const key of Object.keys(PRICING)) {
|
|
6087
|
+
if (norm.startsWith(key) && (best === null || key.length > best.length)) best = key;
|
|
6088
|
+
}
|
|
6089
|
+
return best ? PRICING[best] : null;
|
|
6090
|
+
}
|
|
6091
|
+
function parseJSONLFile(filePath) {
|
|
6092
|
+
let content;
|
|
6093
|
+
try {
|
|
6094
|
+
content = import_fs16.default.readFileSync(filePath, "utf8");
|
|
6095
|
+
} catch {
|
|
6096
|
+
return /* @__PURE__ */ new Map();
|
|
6097
|
+
}
|
|
6098
|
+
const daily = /* @__PURE__ */ new Map();
|
|
6099
|
+
for (const line of content.split("\n")) {
|
|
6100
|
+
if (!line.trim()) continue;
|
|
6101
|
+
let row;
|
|
6102
|
+
try {
|
|
6103
|
+
row = JSON.parse(line);
|
|
6104
|
+
} catch {
|
|
6105
|
+
continue;
|
|
6106
|
+
}
|
|
6107
|
+
if (row["type"] !== "assistant") continue;
|
|
6108
|
+
const msg = row["message"];
|
|
6109
|
+
if (!msg?.["usage"] || typeof msg["model"] !== "string") continue;
|
|
6110
|
+
const usage = msg["usage"];
|
|
6111
|
+
const model = msg["model"];
|
|
6112
|
+
const timestamp = row["timestamp"];
|
|
6113
|
+
if (typeof timestamp !== "string" || timestamp.length < 10) continue;
|
|
6114
|
+
const date = timestamp.slice(0, 10);
|
|
6115
|
+
const p = pricingFor(model);
|
|
6116
|
+
if (!p) continue;
|
|
6117
|
+
const inp = Number(usage["input_tokens"] ?? 0);
|
|
6118
|
+
const out = Number(usage["output_tokens"] ?? 0);
|
|
6119
|
+
const cw = Number(usage["cache_creation_input_tokens"] ?? 0);
|
|
6120
|
+
const cr = Number(usage["cache_read_input_tokens"] ?? 0);
|
|
6121
|
+
const cost = inp * p[0] + out * p[1] + cw * p[2] + cr * p[3];
|
|
6122
|
+
const norm = normalizeModel(model);
|
|
6123
|
+
const key = `${date}::${norm}`;
|
|
6124
|
+
const prev = daily.get(key);
|
|
6125
|
+
if (prev) {
|
|
6126
|
+
prev.costUSD += cost;
|
|
6127
|
+
prev.inputTokens += inp;
|
|
6128
|
+
prev.outputTokens += out;
|
|
6129
|
+
prev.cacheWriteTokens += cw;
|
|
6130
|
+
prev.cacheReadTokens += cr;
|
|
6131
|
+
} else {
|
|
6132
|
+
daily.set(key, {
|
|
6133
|
+
date,
|
|
6134
|
+
model: norm,
|
|
6135
|
+
costUSD: cost,
|
|
6136
|
+
inputTokens: inp,
|
|
6137
|
+
outputTokens: out,
|
|
6138
|
+
cacheWriteTokens: cw,
|
|
6139
|
+
cacheReadTokens: cr
|
|
6140
|
+
});
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
return daily;
|
|
6144
|
+
}
|
|
6145
|
+
function collectEntries() {
|
|
6146
|
+
const projectsDir = import_path19.default.join(import_os14.default.homedir(), ".claude", "projects");
|
|
6147
|
+
if (!import_fs16.default.existsSync(projectsDir)) return [];
|
|
6148
|
+
const combined = /* @__PURE__ */ new Map();
|
|
6149
|
+
let dirs;
|
|
6150
|
+
try {
|
|
6151
|
+
dirs = import_fs16.default.readdirSync(projectsDir);
|
|
6152
|
+
} catch {
|
|
6153
|
+
return [];
|
|
6154
|
+
}
|
|
6155
|
+
for (const dir of dirs) {
|
|
6156
|
+
const dirPath = import_path19.default.join(projectsDir, dir);
|
|
6157
|
+
try {
|
|
6158
|
+
if (!import_fs16.default.statSync(dirPath).isDirectory()) continue;
|
|
6159
|
+
} catch {
|
|
6160
|
+
continue;
|
|
6161
|
+
}
|
|
6162
|
+
let files;
|
|
6163
|
+
try {
|
|
6164
|
+
files = import_fs16.default.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
6165
|
+
} catch {
|
|
6166
|
+
continue;
|
|
6167
|
+
}
|
|
6168
|
+
for (const file of files) {
|
|
6169
|
+
const entries = parseJSONLFile(import_path19.default.join(dirPath, file));
|
|
6170
|
+
for (const [key, e] of entries) {
|
|
6171
|
+
const prev = combined.get(key);
|
|
6172
|
+
if (prev) {
|
|
6173
|
+
prev.costUSD += e.costUSD;
|
|
6174
|
+
prev.inputTokens += e.inputTokens;
|
|
6175
|
+
prev.outputTokens += e.outputTokens;
|
|
6176
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
6177
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
6178
|
+
} else {
|
|
6179
|
+
combined.set(key, { ...e });
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
return [...combined.values()];
|
|
6185
|
+
}
|
|
6186
|
+
async function syncCost() {
|
|
6187
|
+
const creds = getCredentials();
|
|
6188
|
+
if (!creds?.apiKey || !creds?.apiUrl) return;
|
|
6189
|
+
const entries = collectEntries();
|
|
6190
|
+
if (entries.length === 0) return;
|
|
6191
|
+
let username = "unknown";
|
|
6192
|
+
try {
|
|
6193
|
+
username = import_os14.default.userInfo().username;
|
|
6194
|
+
} catch {
|
|
6195
|
+
}
|
|
6196
|
+
const machineId = `${import_os14.default.hostname()}:${username}`;
|
|
6197
|
+
try {
|
|
6198
|
+
const res = await fetch(`${creds.apiUrl}/cost-sync`, {
|
|
6199
|
+
method: "POST",
|
|
6200
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
|
|
6201
|
+
body: JSON.stringify({ machineId, entries }),
|
|
6202
|
+
signal: AbortSignal.timeout(15e3)
|
|
6203
|
+
});
|
|
6204
|
+
if (!res.ok) {
|
|
6205
|
+
import_fs16.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] HTTP ${res.status}
|
|
6206
|
+
`);
|
|
6207
|
+
}
|
|
6208
|
+
} catch (err2) {
|
|
6209
|
+
import_fs16.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
|
|
6210
|
+
`);
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
function startCostSync() {
|
|
6214
|
+
syncCost().catch(() => {
|
|
6215
|
+
});
|
|
6216
|
+
const timer = setInterval(() => {
|
|
6217
|
+
syncCost().catch(() => {
|
|
6218
|
+
});
|
|
6219
|
+
}, SYNC_INTERVAL_MS);
|
|
6220
|
+
timer.unref();
|
|
6221
|
+
}
|
|
6222
|
+
var import_fs16, import_path19, import_os14, SYNC_INTERVAL_MS, PRICING;
|
|
6223
|
+
var init_costSync = __esm({
|
|
6224
|
+
"src/costSync.ts"() {
|
|
6225
|
+
"use strict";
|
|
6226
|
+
import_fs16 = __toESM(require("fs"));
|
|
6227
|
+
import_path19 = __toESM(require("path"));
|
|
6228
|
+
import_os14 = __toESM(require("os"));
|
|
6229
|
+
init_config();
|
|
6230
|
+
init_audit();
|
|
6231
|
+
SYNC_INTERVAL_MS = 10 * 60 * 1e3;
|
|
6232
|
+
PRICING = {
|
|
6233
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
6234
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6235
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
6236
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6237
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
6238
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
6239
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8]
|
|
6240
|
+
};
|
|
5965
6241
|
}
|
|
5966
6242
|
});
|
|
5967
6243
|
|
|
5968
6244
|
// src/daemon/server.ts
|
|
5969
6245
|
function startDaemon() {
|
|
6246
|
+
startCostSync();
|
|
5970
6247
|
loadInsightCounts();
|
|
5971
|
-
const csrfToken = (0,
|
|
5972
|
-
const internalToken = (0,
|
|
6248
|
+
const csrfToken = (0, import_crypto7.randomUUID)();
|
|
6249
|
+
const internalToken = (0, import_crypto7.randomUUID)();
|
|
5973
6250
|
const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
|
|
5974
6251
|
const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
|
|
5975
6252
|
const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
|
|
@@ -5982,7 +6259,7 @@ function startDaemon() {
|
|
|
5982
6259
|
idleTimer = setTimeout(() => {
|
|
5983
6260
|
if (autoStarted) {
|
|
5984
6261
|
try {
|
|
5985
|
-
|
|
6262
|
+
import_fs17.default.unlinkSync(DAEMON_PID_FILE);
|
|
5986
6263
|
} catch {
|
|
5987
6264
|
}
|
|
5988
6265
|
}
|
|
@@ -6103,7 +6380,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6103
6380
|
cwd,
|
|
6104
6381
|
localSmartRuleMatched = false
|
|
6105
6382
|
} = JSON.parse(body);
|
|
6106
|
-
const id = fromCLI && typeof activityId === "string" && activityId || (0,
|
|
6383
|
+
const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto7.randomUUID)();
|
|
6107
6384
|
const entry = {
|
|
6108
6385
|
id,
|
|
6109
6386
|
toolName,
|
|
@@ -6145,7 +6422,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6145
6422
|
status: "pending"
|
|
6146
6423
|
});
|
|
6147
6424
|
}
|
|
6148
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6425
|
+
const projectCwd = typeof cwd === "string" && import_path20.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6149
6426
|
const projectConfig = getConfig(projectCwd);
|
|
6150
6427
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6151
6428
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6535,8 +6812,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6535
6812
|
const body = await readBody(req);
|
|
6536
6813
|
const data = body ? JSON.parse(body) : {};
|
|
6537
6814
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6538
|
-
const node9Dir =
|
|
6539
|
-
if (!
|
|
6815
|
+
const node9Dir = import_path20.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6816
|
+
if (!import_path20.default.resolve(configPath).startsWith(node9Dir + import_path20.default.sep)) {
|
|
6540
6817
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6541
6818
|
return res.end(
|
|
6542
6819
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6647,14 +6924,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6647
6924
|
server.on("error", (e) => {
|
|
6648
6925
|
if (e.code === "EADDRINUSE") {
|
|
6649
6926
|
try {
|
|
6650
|
-
if (
|
|
6651
|
-
const { pid } = JSON.parse(
|
|
6927
|
+
if (import_fs17.default.existsSync(DAEMON_PID_FILE)) {
|
|
6928
|
+
const { pid } = JSON.parse(import_fs17.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6652
6929
|
process.kill(pid, 0);
|
|
6653
6930
|
return process.exit(0);
|
|
6654
6931
|
}
|
|
6655
6932
|
} catch {
|
|
6656
6933
|
try {
|
|
6657
|
-
|
|
6934
|
+
import_fs17.default.unlinkSync(DAEMON_PID_FILE);
|
|
6658
6935
|
} catch {
|
|
6659
6936
|
}
|
|
6660
6937
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6713,14 +6990,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6713
6990
|
}
|
|
6714
6991
|
startActivitySocket();
|
|
6715
6992
|
}
|
|
6716
|
-
var import_http,
|
|
6993
|
+
var import_http, import_fs17, import_path20, import_crypto7, import_child_process4, import_chalk2;
|
|
6717
6994
|
var init_server = __esm({
|
|
6718
6995
|
"src/daemon/server.ts"() {
|
|
6719
6996
|
"use strict";
|
|
6720
6997
|
import_http = __toESM(require("http"));
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6998
|
+
import_fs17 = __toESM(require("fs"));
|
|
6999
|
+
import_path20 = __toESM(require("path"));
|
|
7000
|
+
import_crypto7 = require("crypto");
|
|
6724
7001
|
import_child_process4 = require("child_process");
|
|
6725
7002
|
import_chalk2 = __toESM(require("chalk"));
|
|
6726
7003
|
init_core();
|
|
@@ -6729,29 +7006,30 @@ var init_server = __esm({
|
|
|
6729
7006
|
init_state2();
|
|
6730
7007
|
init_patch();
|
|
6731
7008
|
init_config_schema();
|
|
7009
|
+
init_costSync();
|
|
6732
7010
|
}
|
|
6733
7011
|
});
|
|
6734
7012
|
|
|
6735
7013
|
// src/daemon/index.ts
|
|
6736
7014
|
function stopDaemon() {
|
|
6737
|
-
if (!
|
|
7015
|
+
if (!import_fs18.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
6738
7016
|
try {
|
|
6739
|
-
const { pid } = JSON.parse(
|
|
7017
|
+
const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6740
7018
|
process.kill(pid, "SIGTERM");
|
|
6741
7019
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
6742
7020
|
} catch {
|
|
6743
7021
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
6744
7022
|
} finally {
|
|
6745
7023
|
try {
|
|
6746
|
-
|
|
7024
|
+
import_fs18.default.unlinkSync(DAEMON_PID_FILE);
|
|
6747
7025
|
} catch {
|
|
6748
7026
|
}
|
|
6749
7027
|
}
|
|
6750
7028
|
}
|
|
6751
7029
|
function daemonStatus() {
|
|
6752
|
-
if (
|
|
7030
|
+
if (import_fs18.default.existsSync(DAEMON_PID_FILE)) {
|
|
6753
7031
|
try {
|
|
6754
|
-
const { pid } = JSON.parse(
|
|
7032
|
+
const { pid } = JSON.parse(import_fs18.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6755
7033
|
process.kill(pid, 0);
|
|
6756
7034
|
console.log(import_chalk3.default.green("Node9 daemon: running"));
|
|
6757
7035
|
return;
|
|
@@ -6770,11 +7048,11 @@ function daemonStatus() {
|
|
|
6770
7048
|
console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
|
|
6771
7049
|
}
|
|
6772
7050
|
}
|
|
6773
|
-
var
|
|
7051
|
+
var import_fs18, import_chalk3, import_child_process5;
|
|
6774
7052
|
var init_daemon2 = __esm({
|
|
6775
7053
|
"src/daemon/index.ts"() {
|
|
6776
7054
|
"use strict";
|
|
6777
|
-
|
|
7055
|
+
import_fs18 = __toESM(require("fs"));
|
|
6778
7056
|
import_chalk3 = __toESM(require("chalk"));
|
|
6779
7057
|
import_child_process5 = require("child_process");
|
|
6780
7058
|
init_server();
|
|
@@ -6795,44 +7073,64 @@ function getIcon(tool) {
|
|
|
6795
7073
|
}
|
|
6796
7074
|
return "\u{1F6E0}\uFE0F";
|
|
6797
7075
|
}
|
|
7076
|
+
function visibleLength(s) {
|
|
7077
|
+
return s.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
7078
|
+
}
|
|
7079
|
+
function wrappedLineCount(text) {
|
|
7080
|
+
const cols = process.stdout.columns;
|
|
7081
|
+
if (!cols) return 1;
|
|
7082
|
+
const len = visibleLength(text);
|
|
7083
|
+
return Math.max(1, Math.ceil(len / cols));
|
|
7084
|
+
}
|
|
6798
7085
|
function formatBase(activity) {
|
|
6799
7086
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6800
7087
|
const icon = getIcon(activity.tool);
|
|
6801
7088
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6802
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
7089
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os25.default.homedir(), "~");
|
|
6803
7090
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6804
|
-
return `${
|
|
7091
|
+
return `${import_chalk19.default.gray(time)} ${icon} ${import_chalk19.default.white.bold(toolName)} ${import_chalk19.default.dim(argsPreview)}`;
|
|
6805
7092
|
}
|
|
6806
7093
|
function renderResult(activity, result) {
|
|
6807
7094
|
const base = formatBase(activity);
|
|
6808
7095
|
let status;
|
|
6809
7096
|
if (result.status === "allow") {
|
|
6810
|
-
status =
|
|
7097
|
+
status = import_chalk19.default.green("\u2713 ALLOW");
|
|
6811
7098
|
} else if (result.status === "dlp") {
|
|
6812
|
-
status =
|
|
7099
|
+
status = import_chalk19.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
6813
7100
|
} else {
|
|
6814
|
-
status =
|
|
7101
|
+
status = import_chalk19.default.red("\u2717 BLOCK");
|
|
6815
7102
|
}
|
|
6816
7103
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
6817
|
-
const costSuffix = cost == null ? "" :
|
|
7104
|
+
const costSuffix = cost == null ? "" : import_chalk19.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
6818
7105
|
if (process.stdout.isTTY) {
|
|
6819
|
-
|
|
6820
|
-
|
|
7106
|
+
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
7107
|
+
import_readline5.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
7108
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7109
|
+
process.stdout.write(ERASE_DOWN);
|
|
7110
|
+
} else {
|
|
7111
|
+
import_readline5.default.clearLine(process.stdout, 0);
|
|
7112
|
+
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7113
|
+
}
|
|
7114
|
+
pendingShownForId = null;
|
|
7115
|
+
pendingWrappedLines = 0;
|
|
6821
7116
|
}
|
|
6822
7117
|
console.log(`${base} ${status}${costSuffix}`);
|
|
6823
7118
|
}
|
|
6824
7119
|
function renderPending(activity) {
|
|
6825
7120
|
if (!process.stdout.isTTY) return;
|
|
6826
|
-
|
|
7121
|
+
const line = `${formatBase(activity)} ${import_chalk19.default.yellow("\u25CF \u2026")}`;
|
|
7122
|
+
pendingShownForId = activity.id;
|
|
7123
|
+
pendingWrappedLines = wrappedLineCount(line);
|
|
7124
|
+
process.stdout.write(`${line}\r`);
|
|
6827
7125
|
}
|
|
6828
7126
|
async function ensureDaemon() {
|
|
6829
7127
|
let pidPort = null;
|
|
6830
|
-
if (
|
|
7128
|
+
if (import_fs29.default.existsSync(PID_FILE)) {
|
|
6831
7129
|
try {
|
|
6832
|
-
const { port } = JSON.parse(
|
|
7130
|
+
const { port } = JSON.parse(import_fs29.default.readFileSync(PID_FILE, "utf-8"));
|
|
6833
7131
|
pidPort = port;
|
|
6834
7132
|
} catch {
|
|
6835
|
-
console.error(
|
|
7133
|
+
console.error(import_chalk19.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
6836
7134
|
}
|
|
6837
7135
|
}
|
|
6838
7136
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -6843,7 +7141,7 @@ async function ensureDaemon() {
|
|
|
6843
7141
|
if (res.ok) return checkPort;
|
|
6844
7142
|
} catch {
|
|
6845
7143
|
}
|
|
6846
|
-
console.log(
|
|
7144
|
+
console.log(import_chalk19.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6847
7145
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6848
7146
|
detached: true,
|
|
6849
7147
|
stdio: "ignore",
|
|
@@ -6860,7 +7158,7 @@ async function ensureDaemon() {
|
|
|
6860
7158
|
} catch {
|
|
6861
7159
|
}
|
|
6862
7160
|
}
|
|
6863
|
-
console.error(
|
|
7161
|
+
console.error(import_chalk19.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6864
7162
|
process.exit(1);
|
|
6865
7163
|
}
|
|
6866
7164
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -6906,6 +7204,9 @@ function buildCardLines(req, localCount = 0) {
|
|
|
6906
7204
|
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
6907
7205
|
`${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
|
|
6908
7206
|
];
|
|
7207
|
+
if (req.riskMetadata?.ruleDescription) {
|
|
7208
|
+
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u2139 ${req.riskMetadata.ruleDescription}${RESET2}`);
|
|
7209
|
+
}
|
|
6909
7210
|
if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
|
|
6910
7211
|
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
6911
7212
|
}
|
|
@@ -6949,9 +7250,9 @@ function buildRecoveryCardLines(req) {
|
|
|
6949
7250
|
];
|
|
6950
7251
|
}
|
|
6951
7252
|
function readApproversFromDisk() {
|
|
6952
|
-
const configPath =
|
|
7253
|
+
const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
|
|
6953
7254
|
try {
|
|
6954
|
-
const raw = JSON.parse(
|
|
7255
|
+
const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
|
|
6955
7256
|
const settings = raw.settings ?? {};
|
|
6956
7257
|
return settings.approvers ?? {};
|
|
6957
7258
|
} catch {
|
|
@@ -6962,20 +7263,20 @@ function approverStatusLine() {
|
|
|
6962
7263
|
const a = readApproversFromDisk();
|
|
6963
7264
|
const fmt = (label, key) => {
|
|
6964
7265
|
const on = a[key] !== false;
|
|
6965
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
7266
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk19.default.green("\u2713") : import_chalk19.default.dim("\u2717")}`;
|
|
6966
7267
|
};
|
|
6967
7268
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
6968
7269
|
}
|
|
6969
7270
|
function toggleApprover(channel) {
|
|
6970
|
-
const configPath =
|
|
7271
|
+
const configPath = import_path32.default.join(import_os25.default.homedir(), ".node9", "config.json");
|
|
6971
7272
|
try {
|
|
6972
|
-
const raw = JSON.parse(
|
|
7273
|
+
const raw = JSON.parse(import_fs29.default.readFileSync(configPath, "utf-8"));
|
|
6973
7274
|
const settings = raw.settings ?? {};
|
|
6974
7275
|
const approvers = settings.approvers ?? {};
|
|
6975
7276
|
approvers[channel] = approvers[channel] === false;
|
|
6976
7277
|
settings.approvers = approvers;
|
|
6977
7278
|
raw.settings = settings;
|
|
6978
|
-
|
|
7279
|
+
import_fs29.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6979
7280
|
} catch (err2) {
|
|
6980
7281
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
6981
7282
|
`);
|
|
@@ -7007,7 +7308,7 @@ async function startTail(options = {}) {
|
|
|
7007
7308
|
req2.end();
|
|
7008
7309
|
});
|
|
7009
7310
|
if (result.ok) {
|
|
7010
|
-
console.log(
|
|
7311
|
+
console.log(import_chalk19.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
7011
7312
|
} else if (result.code === "ECONNREFUSED") {
|
|
7012
7313
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
7013
7314
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7051,7 +7352,7 @@ async function startTail(options = {}) {
|
|
|
7051
7352
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7052
7353
|
if (channel) {
|
|
7053
7354
|
toggleApprover(channel);
|
|
7054
|
-
console.log(
|
|
7355
|
+
console.log(import_chalk19.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7055
7356
|
}
|
|
7056
7357
|
};
|
|
7057
7358
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7117,7 +7418,7 @@ async function startTail(options = {}) {
|
|
|
7117
7418
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7118
7419
|
)
|
|
7119
7420
|
);
|
|
7120
|
-
const decisionStamp = action === "always-allow" ?
|
|
7421
|
+
const decisionStamp = action === "always-allow" ? import_chalk19.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk19.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk19.default.yellow("\u21A9 REDIRECT AI") : import_chalk19.default.red("\u2717 DENIED");
|
|
7121
7422
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7122
7423
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7123
7424
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7145,8 +7446,8 @@ async function startTail(options = {}) {
|
|
|
7145
7446
|
}
|
|
7146
7447
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7147
7448
|
try {
|
|
7148
|
-
|
|
7149
|
-
|
|
7449
|
+
import_fs29.default.appendFileSync(
|
|
7450
|
+
import_path32.default.join(import_os25.default.homedir(), ".node9", "hook-debug.log"),
|
|
7150
7451
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7151
7452
|
`
|
|
7152
7453
|
);
|
|
@@ -7168,7 +7469,7 @@ async function startTail(options = {}) {
|
|
|
7168
7469
|
);
|
|
7169
7470
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7170
7471
|
if (externalDecision) {
|
|
7171
|
-
const source = externalDecision === "allow" ?
|
|
7472
|
+
const source = externalDecision === "allow" ? import_chalk19.default.green("\u2713 ALLOWED") : import_chalk19.default.red("\u2717 DENIED");
|
|
7172
7473
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7173
7474
|
}
|
|
7174
7475
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7227,16 +7528,16 @@ async function startTail(options = {}) {
|
|
|
7227
7528
|
}
|
|
7228
7529
|
} catch {
|
|
7229
7530
|
}
|
|
7230
|
-
console.log(
|
|
7231
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
7531
|
+
console.log(import_chalk19.default.cyan.bold(`
|
|
7532
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk19.default.dim(`\u2192 ${dashboardUrl}`));
|
|
7232
7533
|
if (canApprove) {
|
|
7233
|
-
console.log(
|
|
7234
|
-
console.log(
|
|
7534
|
+
console.log(import_chalk19.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
7535
|
+
console.log(import_chalk19.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
7235
7536
|
}
|
|
7236
7537
|
if (options.history) {
|
|
7237
|
-
console.log(
|
|
7538
|
+
console.log(import_chalk19.default.dim("Showing history + live events.\n"));
|
|
7238
7539
|
} else {
|
|
7239
|
-
console.log(
|
|
7540
|
+
console.log(import_chalk19.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7240
7541
|
}
|
|
7241
7542
|
process.on("SIGINT", () => {
|
|
7242
7543
|
exitIdleMode();
|
|
@@ -7246,13 +7547,13 @@ async function startTail(options = {}) {
|
|
|
7246
7547
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7247
7548
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7248
7549
|
}
|
|
7249
|
-
console.log(
|
|
7550
|
+
console.log(import_chalk19.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7250
7551
|
process.exit(0);
|
|
7251
7552
|
});
|
|
7252
7553
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7253
7554
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7254
7555
|
if (res.statusCode !== 200) {
|
|
7255
|
-
console.error(
|
|
7556
|
+
console.error(import_chalk19.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7256
7557
|
process.exit(1);
|
|
7257
7558
|
}
|
|
7258
7559
|
if (canApprove) enterIdleMode();
|
|
@@ -7283,7 +7584,7 @@ async function startTail(options = {}) {
|
|
|
7283
7584
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7284
7585
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7285
7586
|
}
|
|
7286
|
-
console.log(
|
|
7587
|
+
console.log(import_chalk19.default.red("\n\u274C Daemon disconnected."));
|
|
7287
7588
|
process.exit(1);
|
|
7288
7589
|
});
|
|
7289
7590
|
});
|
|
@@ -7375,9 +7676,9 @@ async function startTail(options = {}) {
|
|
|
7375
7676
|
const hash = data.hash ?? "";
|
|
7376
7677
|
const summary = data.argsSummary ?? data.tool;
|
|
7377
7678
|
const fileCount = data.fileCount ?? 0;
|
|
7378
|
-
const files = fileCount > 0 ?
|
|
7679
|
+
const files = fileCount > 0 ? import_chalk19.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7379
7680
|
process.stdout.write(
|
|
7380
|
-
`${
|
|
7681
|
+
`${import_chalk19.default.dim(time)} ${import_chalk19.default.cyan("\u{1F4F8} snapshot")} ${import_chalk19.default.dim(hash)} ${summary}${files}
|
|
7381
7682
|
`
|
|
7382
7683
|
);
|
|
7383
7684
|
return;
|
|
@@ -7394,26 +7695,26 @@ async function startTail(options = {}) {
|
|
|
7394
7695
|
}
|
|
7395
7696
|
req.on("error", (err2) => {
|
|
7396
7697
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7397
|
-
console.error(
|
|
7698
|
+
console.error(import_chalk19.default.red(`
|
|
7398
7699
|
\u274C ${msg}`));
|
|
7399
7700
|
process.exit(1);
|
|
7400
7701
|
});
|
|
7401
7702
|
}
|
|
7402
|
-
var import_http2,
|
|
7703
|
+
var import_http2, import_chalk19, import_fs29, import_os25, import_path32, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
7403
7704
|
var init_tail = __esm({
|
|
7404
7705
|
"src/tui/tail.ts"() {
|
|
7405
7706
|
"use strict";
|
|
7406
7707
|
import_http2 = __toESM(require("http"));
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7708
|
+
import_chalk19 = __toESM(require("chalk"));
|
|
7709
|
+
import_fs29 = __toESM(require("fs"));
|
|
7710
|
+
import_os25 = __toESM(require("os"));
|
|
7711
|
+
import_path32 = __toESM(require("path"));
|
|
7411
7712
|
import_readline5 = __toESM(require("readline"));
|
|
7412
7713
|
import_child_process14 = require("child_process");
|
|
7413
7714
|
init_daemon2();
|
|
7414
7715
|
init_daemon();
|
|
7415
7716
|
init_core();
|
|
7416
|
-
PID_FILE =
|
|
7717
|
+
PID_FILE = import_path32.default.join(import_os25.default.homedir(), ".node9", "daemon.pid");
|
|
7417
7718
|
ICONS = {
|
|
7418
7719
|
bash: "\u{1F4BB}",
|
|
7419
7720
|
shell: "\u{1F4BB}",
|
|
@@ -7441,6 +7742,8 @@ var init_tail = __esm({
|
|
|
7441
7742
|
HIDE_CURSOR = "\x1B[?25l";
|
|
7442
7743
|
SHOW_CURSOR = "\x1B[?25h";
|
|
7443
7744
|
ERASE_DOWN = "\x1B[J";
|
|
7745
|
+
pendingShownForId = null;
|
|
7746
|
+
pendingWrappedLines = 0;
|
|
7444
7747
|
DIVIDER = "\u2500".repeat(60);
|
|
7445
7748
|
}
|
|
7446
7749
|
});
|
|
@@ -7509,10 +7812,10 @@ function bold(s) {
|
|
|
7509
7812
|
function color(c, s) {
|
|
7510
7813
|
return `${c}${s}${RESET3}`;
|
|
7511
7814
|
}
|
|
7512
|
-
function progressBar(
|
|
7513
|
-
const filled = Math.round(Math.min(
|
|
7815
|
+
function progressBar(pct2, warnAt = 70, critAt = 85) {
|
|
7816
|
+
const filled = Math.round(Math.min(pct2, 100) / 100 * BAR_WIDTH);
|
|
7514
7817
|
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
7515
|
-
const c =
|
|
7818
|
+
const c = pct2 >= critAt ? RED2 : pct2 >= warnAt ? YELLOW2 : GREEN2;
|
|
7516
7819
|
return `${c}${bar}${RESET3}`;
|
|
7517
7820
|
}
|
|
7518
7821
|
function formatTimeLeft(resetsAt) {
|
|
@@ -7526,9 +7829,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7526
7829
|
return ` (${m}m left)`;
|
|
7527
7830
|
}
|
|
7528
7831
|
function safeReadJson(filePath) {
|
|
7529
|
-
if (!
|
|
7832
|
+
if (!import_fs30.default.existsSync(filePath)) return null;
|
|
7530
7833
|
try {
|
|
7531
|
-
return JSON.parse(
|
|
7834
|
+
return JSON.parse(import_fs30.default.readFileSync(filePath, "utf-8"));
|
|
7532
7835
|
} catch {
|
|
7533
7836
|
return null;
|
|
7534
7837
|
}
|
|
@@ -7549,12 +7852,12 @@ function countHooksInFile(filePath) {
|
|
|
7549
7852
|
return Object.keys(cfg.hooks).length;
|
|
7550
7853
|
}
|
|
7551
7854
|
function countRulesInDir(rulesDir) {
|
|
7552
|
-
if (!
|
|
7855
|
+
if (!import_fs30.default.existsSync(rulesDir)) return 0;
|
|
7553
7856
|
let count = 0;
|
|
7554
7857
|
try {
|
|
7555
|
-
for (const entry of
|
|
7858
|
+
for (const entry of import_fs30.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7556
7859
|
if (entry.isDirectory()) {
|
|
7557
|
-
count += countRulesInDir(
|
|
7860
|
+
count += countRulesInDir(import_path33.default.join(rulesDir, entry.name));
|
|
7558
7861
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7559
7862
|
count++;
|
|
7560
7863
|
}
|
|
@@ -7565,46 +7868,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7565
7868
|
}
|
|
7566
7869
|
function isSamePath(a, b) {
|
|
7567
7870
|
try {
|
|
7568
|
-
return
|
|
7871
|
+
return import_path33.default.resolve(a) === import_path33.default.resolve(b);
|
|
7569
7872
|
} catch {
|
|
7570
7873
|
return false;
|
|
7571
7874
|
}
|
|
7572
7875
|
}
|
|
7573
7876
|
function countConfigs(cwd) {
|
|
7574
|
-
const homeDir2 =
|
|
7575
|
-
const claudeDir =
|
|
7877
|
+
const homeDir2 = import_os26.default.homedir();
|
|
7878
|
+
const claudeDir = import_path33.default.join(homeDir2, ".claude");
|
|
7576
7879
|
let claudeMdCount = 0;
|
|
7577
7880
|
let rulesCount = 0;
|
|
7578
7881
|
let hooksCount = 0;
|
|
7579
7882
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7580
7883
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7581
|
-
if (
|
|
7582
|
-
rulesCount += countRulesInDir(
|
|
7583
|
-
const userSettings =
|
|
7884
|
+
if (import_fs30.default.existsSync(import_path33.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7885
|
+
rulesCount += countRulesInDir(import_path33.default.join(claudeDir, "rules"));
|
|
7886
|
+
const userSettings = import_path33.default.join(claudeDir, "settings.json");
|
|
7584
7887
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7585
7888
|
hooksCount += countHooksInFile(userSettings);
|
|
7586
|
-
const userClaudeJson =
|
|
7889
|
+
const userClaudeJson = import_path33.default.join(homeDir2, ".claude.json");
|
|
7587
7890
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7588
7891
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7589
7892
|
userMcpServers.delete(name);
|
|
7590
7893
|
}
|
|
7591
7894
|
if (cwd) {
|
|
7592
|
-
if (
|
|
7593
|
-
if (
|
|
7594
|
-
const projectClaudeDir =
|
|
7895
|
+
if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7896
|
+
if (import_fs30.default.existsSync(import_path33.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7897
|
+
const projectClaudeDir = import_path33.default.join(cwd, ".claude");
|
|
7595
7898
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7596
7899
|
if (!overlapsUserScope) {
|
|
7597
|
-
if (
|
|
7598
|
-
rulesCount += countRulesInDir(
|
|
7599
|
-
const projSettings =
|
|
7900
|
+
if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7901
|
+
rulesCount += countRulesInDir(import_path33.default.join(projectClaudeDir, "rules"));
|
|
7902
|
+
const projSettings = import_path33.default.join(projectClaudeDir, "settings.json");
|
|
7600
7903
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7601
7904
|
hooksCount += countHooksInFile(projSettings);
|
|
7602
7905
|
}
|
|
7603
|
-
if (
|
|
7604
|
-
const localSettings =
|
|
7906
|
+
if (import_fs30.default.existsSync(import_path33.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7907
|
+
const localSettings = import_path33.default.join(projectClaudeDir, "settings.local.json");
|
|
7605
7908
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7606
7909
|
hooksCount += countHooksInFile(localSettings);
|
|
7607
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7910
|
+
const mcpJsonServers = getMcpServerNames(import_path33.default.join(cwd, ".mcp.json"));
|
|
7608
7911
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7609
7912
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7610
7913
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7637,12 +7940,12 @@ function readActiveShieldsHud() {
|
|
|
7637
7940
|
return shieldsCache.value;
|
|
7638
7941
|
}
|
|
7639
7942
|
try {
|
|
7640
|
-
const shieldsPath =
|
|
7641
|
-
if (!
|
|
7943
|
+
const shieldsPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "shields.json");
|
|
7944
|
+
if (!import_fs30.default.existsSync(shieldsPath)) {
|
|
7642
7945
|
shieldsCache = { value: [], ts: now };
|
|
7643
7946
|
return [];
|
|
7644
7947
|
}
|
|
7645
|
-
const parsed = JSON.parse(
|
|
7948
|
+
const parsed = JSON.parse(import_fs30.default.readFileSync(shieldsPath, "utf-8"));
|
|
7646
7949
|
if (!Array.isArray(parsed.active)) {
|
|
7647
7950
|
shieldsCache = { value: [], ts: now };
|
|
7648
7951
|
return [];
|
|
@@ -7728,15 +8031,15 @@ function renderContextLine(stdin) {
|
|
|
7728
8031
|
}
|
|
7729
8032
|
const rl = stdin.rate_limits;
|
|
7730
8033
|
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
7731
|
-
const
|
|
7732
|
-
const bar = progressBar(
|
|
8034
|
+
const pct2 = Math.round(rl.five_hour.used_percentage);
|
|
8035
|
+
const bar = progressBar(pct2, 60, 80);
|
|
7733
8036
|
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
7734
|
-
parts.push(`${dim("\u2502")} 5h ${bar} ${
|
|
8037
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct2}%${left}`);
|
|
7735
8038
|
}
|
|
7736
8039
|
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
7737
|
-
const
|
|
7738
|
-
const bar = progressBar(
|
|
7739
|
-
parts.push(`${dim("\u2502")} 7d ${bar} ${
|
|
8040
|
+
const pct2 = Math.round(rl.seven_day.used_percentage);
|
|
8041
|
+
const bar = progressBar(pct2, 60, 80);
|
|
8042
|
+
parts.push(`${dim("\u2502")} 7d ${bar} ${pct2}%`);
|
|
7740
8043
|
}
|
|
7741
8044
|
if (parts.length === 0) return null;
|
|
7742
8045
|
return parts.join(" ");
|
|
@@ -7744,17 +8047,17 @@ function renderContextLine(stdin) {
|
|
|
7744
8047
|
async function main() {
|
|
7745
8048
|
try {
|
|
7746
8049
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7747
|
-
if (
|
|
8050
|
+
if (import_fs30.default.existsSync(import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug"))) {
|
|
7748
8051
|
try {
|
|
7749
|
-
const logPath =
|
|
8052
|
+
const logPath = import_path33.default.join(import_os26.default.homedir(), ".node9", "hud-debug.log");
|
|
7750
8053
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7751
8054
|
let size = 0;
|
|
7752
8055
|
try {
|
|
7753
|
-
size =
|
|
8056
|
+
size = import_fs30.default.statSync(logPath).size;
|
|
7754
8057
|
} catch {
|
|
7755
8058
|
}
|
|
7756
8059
|
if (size < MAX_LOG_SIZE) {
|
|
7757
|
-
|
|
8060
|
+
import_fs30.default.appendFileSync(
|
|
7758
8061
|
logPath,
|
|
7759
8062
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7760
8063
|
);
|
|
@@ -7775,11 +8078,11 @@ async function main() {
|
|
|
7775
8078
|
try {
|
|
7776
8079
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7777
8080
|
for (const configPath of [
|
|
7778
|
-
|
|
7779
|
-
|
|
8081
|
+
import_path33.default.join(cwd, "node9.config.json"),
|
|
8082
|
+
import_path33.default.join(import_os26.default.homedir(), ".node9", "config.json")
|
|
7780
8083
|
]) {
|
|
7781
|
-
if (!
|
|
7782
|
-
const cfg = JSON.parse(
|
|
8084
|
+
if (!import_fs30.default.existsSync(configPath)) continue;
|
|
8085
|
+
const cfg = JSON.parse(import_fs30.default.readFileSync(configPath, "utf-8"));
|
|
7783
8086
|
const hud = cfg.settings?.hud;
|
|
7784
8087
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7785
8088
|
}
|
|
@@ -7797,13 +8100,13 @@ async function main() {
|
|
|
7797
8100
|
renderOffline();
|
|
7798
8101
|
}
|
|
7799
8102
|
}
|
|
7800
|
-
var
|
|
8103
|
+
var import_fs30, import_path33, import_os26, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
7801
8104
|
var init_hud = __esm({
|
|
7802
8105
|
"src/cli/hud.ts"() {
|
|
7803
8106
|
"use strict";
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
8107
|
+
import_fs30 = __toESM(require("fs"));
|
|
8108
|
+
import_path33 = __toESM(require("path"));
|
|
8109
|
+
import_os26 = __toESM(require("os"));
|
|
7807
8110
|
import_http3 = __toESM(require("http"));
|
|
7808
8111
|
init_daemon();
|
|
7809
8112
|
RESET3 = "\x1B[0m";
|
|
@@ -7829,9 +8132,9 @@ var import_commander = require("commander");
|
|
|
7829
8132
|
init_core();
|
|
7830
8133
|
|
|
7831
8134
|
// src/setup.ts
|
|
7832
|
-
var
|
|
7833
|
-
var
|
|
7834
|
-
var
|
|
8135
|
+
var import_fs12 = __toESM(require("fs"));
|
|
8136
|
+
var import_path15 = __toESM(require("path"));
|
|
8137
|
+
var import_os11 = __toESM(require("os"));
|
|
7835
8138
|
var import_chalk = __toESM(require("chalk"));
|
|
7836
8139
|
var import_prompts = require("@inquirer/prompts");
|
|
7837
8140
|
var import_smol_toml = require("smol-toml");
|
|
@@ -7859,26 +8162,26 @@ function fullPathCommand(subcommand) {
|
|
|
7859
8162
|
}
|
|
7860
8163
|
function readJson(filePath) {
|
|
7861
8164
|
try {
|
|
7862
|
-
if (
|
|
7863
|
-
return JSON.parse(
|
|
8165
|
+
if (import_fs12.default.existsSync(filePath)) {
|
|
8166
|
+
return JSON.parse(import_fs12.default.readFileSync(filePath, "utf-8"));
|
|
7864
8167
|
}
|
|
7865
8168
|
} catch {
|
|
7866
8169
|
}
|
|
7867
8170
|
return null;
|
|
7868
8171
|
}
|
|
7869
8172
|
function writeJson(filePath, data) {
|
|
7870
|
-
const dir =
|
|
7871
|
-
if (!
|
|
7872
|
-
|
|
8173
|
+
const dir = import_path15.default.dirname(filePath);
|
|
8174
|
+
if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
|
|
8175
|
+
import_fs12.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
7873
8176
|
}
|
|
7874
8177
|
function isNode9Hook(cmd) {
|
|
7875
8178
|
if (!cmd) return false;
|
|
7876
8179
|
return /(?:^|[\s/\\])node9 (?:check|log)/.test(cmd) || /(?:^|[\s/\\])cli\.js (?:check|log)/.test(cmd);
|
|
7877
8180
|
}
|
|
7878
8181
|
function teardownClaude() {
|
|
7879
|
-
const homeDir2 =
|
|
7880
|
-
const hooksPath =
|
|
7881
|
-
const mcpPath =
|
|
8182
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8183
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8184
|
+
const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
|
|
7882
8185
|
let changed = false;
|
|
7883
8186
|
const settings = readJson(hooksPath);
|
|
7884
8187
|
if (settings?.hooks) {
|
|
@@ -7926,8 +8229,8 @@ function teardownClaude() {
|
|
|
7926
8229
|
}
|
|
7927
8230
|
}
|
|
7928
8231
|
function teardownGemini() {
|
|
7929
|
-
const homeDir2 =
|
|
7930
|
-
const settingsPath =
|
|
8232
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8233
|
+
const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
|
|
7931
8234
|
const settings = readJson(settingsPath);
|
|
7932
8235
|
if (!settings) {
|
|
7933
8236
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -7970,8 +8273,8 @@ function teardownGemini() {
|
|
|
7970
8273
|
}
|
|
7971
8274
|
}
|
|
7972
8275
|
function teardownCursor() {
|
|
7973
|
-
const homeDir2 =
|
|
7974
|
-
const mcpPath =
|
|
8276
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8277
|
+
const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7975
8278
|
const mcpConfig = readJson(mcpPath);
|
|
7976
8279
|
if (!mcpConfig?.mcpServers) {
|
|
7977
8280
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -8002,9 +8305,9 @@ function teardownCursor() {
|
|
|
8002
8305
|
}
|
|
8003
8306
|
}
|
|
8004
8307
|
async function setupClaude() {
|
|
8005
|
-
const homeDir2 =
|
|
8006
|
-
const mcpPath =
|
|
8007
|
-
const hooksPath =
|
|
8308
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8309
|
+
const mcpPath = import_path15.default.join(homeDir2, ".claude", ".mcp.json");
|
|
8310
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8008
8311
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
8009
8312
|
const settings = readJson(hooksPath) ?? {};
|
|
8010
8313
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -8101,8 +8404,8 @@ async function setupClaude() {
|
|
|
8101
8404
|
}
|
|
8102
8405
|
}
|
|
8103
8406
|
async function setupGemini() {
|
|
8104
|
-
const homeDir2 =
|
|
8105
|
-
const settingsPath =
|
|
8407
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8408
|
+
const settingsPath = import_path15.default.join(homeDir2, ".gemini", "settings.json");
|
|
8106
8409
|
const settings = readJson(settingsPath) ?? {};
|
|
8107
8410
|
const servers = settings.mcpServers ?? {};
|
|
8108
8411
|
let hooksChanged = false;
|
|
@@ -8197,10 +8500,10 @@ async function setupGemini() {
|
|
|
8197
8500
|
printDaemonTip();
|
|
8198
8501
|
}
|
|
8199
8502
|
}
|
|
8200
|
-
function detectAgents(homeDir2 =
|
|
8503
|
+
function detectAgents(homeDir2 = import_os11.default.homedir()) {
|
|
8201
8504
|
const exists = (p) => {
|
|
8202
8505
|
try {
|
|
8203
|
-
return
|
|
8506
|
+
return import_fs12.default.existsSync(p);
|
|
8204
8507
|
} catch (err2) {
|
|
8205
8508
|
const code = err2.code;
|
|
8206
8509
|
if (code !== "ENOENT") {
|
|
@@ -8211,15 +8514,15 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
|
8211
8514
|
}
|
|
8212
8515
|
};
|
|
8213
8516
|
return {
|
|
8214
|
-
claude: exists(
|
|
8215
|
-
gemini: exists(
|
|
8216
|
-
cursor: exists(
|
|
8217
|
-
codex: exists(
|
|
8517
|
+
claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
|
|
8518
|
+
gemini: exists(import_path15.default.join(homeDir2, ".gemini")),
|
|
8519
|
+
cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
|
|
8520
|
+
codex: exists(import_path15.default.join(homeDir2, ".codex"))
|
|
8218
8521
|
};
|
|
8219
8522
|
}
|
|
8220
8523
|
async function setupCursor() {
|
|
8221
|
-
const homeDir2 =
|
|
8222
|
-
const mcpPath =
|
|
8524
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8525
|
+
const mcpPath = import_path15.default.join(homeDir2, ".cursor", "mcp.json");
|
|
8223
8526
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
8224
8527
|
const servers = mcpConfig.mcpServers ?? {};
|
|
8225
8528
|
let anythingChanged = false;
|
|
@@ -8285,21 +8588,21 @@ async function setupCursor() {
|
|
|
8285
8588
|
}
|
|
8286
8589
|
function readToml(filePath) {
|
|
8287
8590
|
try {
|
|
8288
|
-
if (
|
|
8289
|
-
return (0, import_smol_toml.parse)(
|
|
8591
|
+
if (import_fs12.default.existsSync(filePath)) {
|
|
8592
|
+
return (0, import_smol_toml.parse)(import_fs12.default.readFileSync(filePath, "utf-8"));
|
|
8290
8593
|
}
|
|
8291
8594
|
} catch {
|
|
8292
8595
|
}
|
|
8293
8596
|
return null;
|
|
8294
8597
|
}
|
|
8295
8598
|
function writeToml(filePath, data) {
|
|
8296
|
-
const dir =
|
|
8297
|
-
if (!
|
|
8298
|
-
|
|
8599
|
+
const dir = import_path15.default.dirname(filePath);
|
|
8600
|
+
if (!import_fs12.default.existsSync(dir)) import_fs12.default.mkdirSync(dir, { recursive: true });
|
|
8601
|
+
import_fs12.default.writeFileSync(filePath, (0, import_smol_toml.stringify)(data));
|
|
8299
8602
|
}
|
|
8300
8603
|
async function setupCodex() {
|
|
8301
|
-
const homeDir2 =
|
|
8302
|
-
const configPath =
|
|
8604
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8605
|
+
const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
|
|
8303
8606
|
const config = readToml(configPath) ?? {};
|
|
8304
8607
|
const servers = config.mcp_servers ?? {};
|
|
8305
8608
|
let anythingChanged = false;
|
|
@@ -8364,8 +8667,8 @@ async function setupCodex() {
|
|
|
8364
8667
|
}
|
|
8365
8668
|
}
|
|
8366
8669
|
function setupHud() {
|
|
8367
|
-
const homeDir2 =
|
|
8368
|
-
const hooksPath =
|
|
8670
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8671
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8369
8672
|
const settings = readJson(hooksPath) ?? {};
|
|
8370
8673
|
const hudCommand = fullPathCommand("hud");
|
|
8371
8674
|
const statusLineObj = { type: "command", command: hudCommand };
|
|
@@ -8391,8 +8694,8 @@ function setupHud() {
|
|
|
8391
8694
|
console.log(import_chalk.default.gray(" Restart Claude Code to activate."));
|
|
8392
8695
|
}
|
|
8393
8696
|
function teardownHud() {
|
|
8394
|
-
const homeDir2 =
|
|
8395
|
-
const hooksPath =
|
|
8697
|
+
const homeDir2 = import_os11.default.homedir();
|
|
8698
|
+
const hooksPath = import_path15.default.join(homeDir2, ".claude", "settings.json");
|
|
8396
8699
|
const settings = readJson(hooksPath);
|
|
8397
8700
|
if (!settings) {
|
|
8398
8701
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
|
|
@@ -8412,10 +8715,10 @@ function teardownHud() {
|
|
|
8412
8715
|
|
|
8413
8716
|
// src/cli.ts
|
|
8414
8717
|
init_daemon2();
|
|
8415
|
-
var
|
|
8416
|
-
var
|
|
8417
|
-
var
|
|
8418
|
-
var
|
|
8718
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
8719
|
+
var import_fs31 = __toESM(require("fs"));
|
|
8720
|
+
var import_path34 = __toESM(require("path"));
|
|
8721
|
+
var import_os27 = __toESM(require("os"));
|
|
8419
8722
|
var import_prompts2 = require("@inquirer/prompts");
|
|
8420
8723
|
|
|
8421
8724
|
// src/utils/duration.ts
|
|
@@ -8640,10 +8943,10 @@ async function autoStartDaemonAndWait() {
|
|
|
8640
8943
|
|
|
8641
8944
|
// src/cli/commands/check.ts
|
|
8642
8945
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8643
|
-
var
|
|
8946
|
+
var import_fs20 = __toESM(require("fs"));
|
|
8644
8947
|
var import_child_process9 = require("child_process");
|
|
8645
|
-
var
|
|
8646
|
-
var
|
|
8948
|
+
var import_path22 = __toESM(require("path"));
|
|
8949
|
+
var import_os16 = __toESM(require("os"));
|
|
8647
8950
|
init_orchestrator();
|
|
8648
8951
|
init_daemon();
|
|
8649
8952
|
init_config();
|
|
@@ -8651,12 +8954,12 @@ init_policy();
|
|
|
8651
8954
|
|
|
8652
8955
|
// src/undo.ts
|
|
8653
8956
|
var import_child_process8 = require("child_process");
|
|
8654
|
-
var
|
|
8655
|
-
var
|
|
8957
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
8958
|
+
var import_fs19 = __toESM(require("fs"));
|
|
8656
8959
|
var import_net3 = __toESM(require("net"));
|
|
8657
|
-
var
|
|
8658
|
-
var
|
|
8659
|
-
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
8960
|
+
var import_path21 = __toESM(require("path"));
|
|
8961
|
+
var import_os15 = __toESM(require("os"));
|
|
8962
|
+
var ACTIVITY_SOCKET_PATH3 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path21.default.join(import_os15.default.tmpdir(), "node9-activity.sock");
|
|
8660
8963
|
function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
8661
8964
|
try {
|
|
8662
8965
|
const payload = JSON.stringify({
|
|
@@ -8676,22 +8979,22 @@ function notifySnapshotTaken(hash, tool, argsSummary, fileCount) {
|
|
|
8676
8979
|
} catch {
|
|
8677
8980
|
}
|
|
8678
8981
|
}
|
|
8679
|
-
var SNAPSHOT_STACK_PATH =
|
|
8680
|
-
var UNDO_LATEST_PATH =
|
|
8982
|
+
var SNAPSHOT_STACK_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots.json");
|
|
8983
|
+
var UNDO_LATEST_PATH = import_path21.default.join(import_os15.default.homedir(), ".node9", "undo_latest.txt");
|
|
8681
8984
|
var MAX_SNAPSHOTS = 10;
|
|
8682
8985
|
var GIT_TIMEOUT = 15e3;
|
|
8683
8986
|
function readStack() {
|
|
8684
8987
|
try {
|
|
8685
|
-
if (
|
|
8686
|
-
return JSON.parse(
|
|
8988
|
+
if (import_fs19.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
8989
|
+
return JSON.parse(import_fs19.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
8687
8990
|
} catch {
|
|
8688
8991
|
}
|
|
8689
8992
|
return [];
|
|
8690
8993
|
}
|
|
8691
8994
|
function writeStack(stack) {
|
|
8692
|
-
const dir =
|
|
8693
|
-
if (!
|
|
8694
|
-
|
|
8995
|
+
const dir = import_path21.default.dirname(SNAPSHOT_STACK_PATH);
|
|
8996
|
+
if (!import_fs19.default.existsSync(dir)) import_fs19.default.mkdirSync(dir, { recursive: true });
|
|
8997
|
+
import_fs19.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
8695
8998
|
}
|
|
8696
8999
|
function extractFilePath(args) {
|
|
8697
9000
|
if (!args || typeof args !== "object") return null;
|
|
@@ -8711,12 +9014,12 @@ function buildArgsSummary(tool, args) {
|
|
|
8711
9014
|
return "";
|
|
8712
9015
|
}
|
|
8713
9016
|
function findProjectRoot(filePath) {
|
|
8714
|
-
let dir =
|
|
9017
|
+
let dir = import_path21.default.dirname(filePath);
|
|
8715
9018
|
while (true) {
|
|
8716
|
-
if (
|
|
9019
|
+
if (import_fs19.default.existsSync(import_path21.default.join(dir, ".git")) || import_fs19.default.existsSync(import_path21.default.join(dir, "package.json"))) {
|
|
8717
9020
|
return dir;
|
|
8718
9021
|
}
|
|
8719
|
-
const parent =
|
|
9022
|
+
const parent = import_path21.default.dirname(dir);
|
|
8720
9023
|
if (parent === dir) return process.cwd();
|
|
8721
9024
|
dir = parent;
|
|
8722
9025
|
}
|
|
@@ -8724,7 +9027,7 @@ function findProjectRoot(filePath) {
|
|
|
8724
9027
|
function normalizeCwdForHash(cwd) {
|
|
8725
9028
|
let normalized;
|
|
8726
9029
|
try {
|
|
8727
|
-
normalized =
|
|
9030
|
+
normalized = import_fs19.default.realpathSync(cwd);
|
|
8728
9031
|
} catch {
|
|
8729
9032
|
normalized = cwd;
|
|
8730
9033
|
}
|
|
@@ -8733,17 +9036,17 @@ function normalizeCwdForHash(cwd) {
|
|
|
8733
9036
|
return normalized;
|
|
8734
9037
|
}
|
|
8735
9038
|
function getShadowRepoDir(cwd) {
|
|
8736
|
-
const hash =
|
|
8737
|
-
return
|
|
9039
|
+
const hash = import_crypto8.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
9040
|
+
return import_path21.default.join(import_os15.default.homedir(), ".node9", "snapshots", hash);
|
|
8738
9041
|
}
|
|
8739
9042
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
8740
9043
|
try {
|
|
8741
9044
|
const cutoff = Date.now() - 6e4;
|
|
8742
|
-
for (const f of
|
|
9045
|
+
for (const f of import_fs19.default.readdirSync(shadowDir)) {
|
|
8743
9046
|
if (f.startsWith("index_")) {
|
|
8744
|
-
const fp =
|
|
9047
|
+
const fp = import_path21.default.join(shadowDir, f);
|
|
8745
9048
|
try {
|
|
8746
|
-
if (
|
|
9049
|
+
if (import_fs19.default.statSync(fp).mtimeMs < cutoff) import_fs19.default.unlinkSync(fp);
|
|
8747
9050
|
} catch {
|
|
8748
9051
|
}
|
|
8749
9052
|
}
|
|
@@ -8755,7 +9058,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
8755
9058
|
const hardcoded = [".git", ".node9"];
|
|
8756
9059
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
8757
9060
|
try {
|
|
8758
|
-
|
|
9061
|
+
import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
8759
9062
|
} catch {
|
|
8760
9063
|
}
|
|
8761
9064
|
}
|
|
@@ -8768,25 +9071,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8768
9071
|
timeout: 3e3
|
|
8769
9072
|
});
|
|
8770
9073
|
if (check.status === 0) {
|
|
8771
|
-
const ptPath =
|
|
9074
|
+
const ptPath = import_path21.default.join(shadowDir, "project-path.txt");
|
|
8772
9075
|
try {
|
|
8773
|
-
const stored =
|
|
9076
|
+
const stored = import_fs19.default.readFileSync(ptPath, "utf8").trim();
|
|
8774
9077
|
if (stored === normalizedCwd) return true;
|
|
8775
9078
|
if (process.env.NODE9_DEBUG === "1")
|
|
8776
9079
|
console.error(
|
|
8777
9080
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
8778
9081
|
);
|
|
8779
|
-
|
|
9082
|
+
import_fs19.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
8780
9083
|
} catch {
|
|
8781
9084
|
try {
|
|
8782
|
-
|
|
9085
|
+
import_fs19.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
8783
9086
|
} catch {
|
|
8784
9087
|
}
|
|
8785
9088
|
return true;
|
|
8786
9089
|
}
|
|
8787
9090
|
}
|
|
8788
9091
|
try {
|
|
8789
|
-
|
|
9092
|
+
import_fs19.default.mkdirSync(shadowDir, { recursive: true });
|
|
8790
9093
|
} catch {
|
|
8791
9094
|
}
|
|
8792
9095
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -8795,7 +9098,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8795
9098
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
8796
9099
|
return false;
|
|
8797
9100
|
}
|
|
8798
|
-
const configFile =
|
|
9101
|
+
const configFile = import_path21.default.join(shadowDir, "config");
|
|
8799
9102
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
8800
9103
|
timeout: 3e3
|
|
8801
9104
|
});
|
|
@@ -8803,7 +9106,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8803
9106
|
timeout: 3e3
|
|
8804
9107
|
});
|
|
8805
9108
|
try {
|
|
8806
|
-
|
|
9109
|
+
import_fs19.default.writeFileSync(import_path21.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
8807
9110
|
} catch {
|
|
8808
9111
|
}
|
|
8809
9112
|
return true;
|
|
@@ -8823,12 +9126,12 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8823
9126
|
let indexFile = null;
|
|
8824
9127
|
try {
|
|
8825
9128
|
const rawFilePath = extractFilePath(args);
|
|
8826
|
-
const absFilePath = rawFilePath &&
|
|
9129
|
+
const absFilePath = rawFilePath && import_path21.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8827
9130
|
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
8828
9131
|
const shadowDir = getShadowRepoDir(cwd);
|
|
8829
9132
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
8830
9133
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
8831
|
-
indexFile =
|
|
9134
|
+
indexFile = import_path21.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
8832
9135
|
const shadowEnv = {
|
|
8833
9136
|
...process.env,
|
|
8834
9137
|
GIT_DIR: shadowDir,
|
|
@@ -8900,7 +9203,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8900
9203
|
writeStack(stack);
|
|
8901
9204
|
const entry = stack[stack.length - 1];
|
|
8902
9205
|
notifySnapshotTaken(commitHash.slice(0, 7), tool, entry.argsSummary, capturedFiles.length);
|
|
8903
|
-
|
|
9206
|
+
import_fs19.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8904
9207
|
if (shouldGc) {
|
|
8905
9208
|
(0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
8906
9209
|
}
|
|
@@ -8911,7 +9214,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8911
9214
|
} finally {
|
|
8912
9215
|
if (indexFile) {
|
|
8913
9216
|
try {
|
|
8914
|
-
|
|
9217
|
+
import_fs19.default.unlinkSync(indexFile);
|
|
8915
9218
|
} catch {
|
|
8916
9219
|
}
|
|
8917
9220
|
}
|
|
@@ -8987,9 +9290,9 @@ function applyUndo(hash, cwd) {
|
|
|
8987
9290
|
timeout: GIT_TIMEOUT
|
|
8988
9291
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8989
9292
|
for (const file of [...tracked, ...untracked]) {
|
|
8990
|
-
const fullPath =
|
|
8991
|
-
if (!snapshotFiles.has(file) &&
|
|
8992
|
-
|
|
9293
|
+
const fullPath = import_path21.default.join(dir, file);
|
|
9294
|
+
if (!snapshotFiles.has(file) && import_fs19.default.existsSync(fullPath)) {
|
|
9295
|
+
import_fs19.default.unlinkSync(fullPath);
|
|
8993
9296
|
}
|
|
8994
9297
|
}
|
|
8995
9298
|
return true;
|
|
@@ -9013,9 +9316,9 @@ function registerCheckCommand(program2) {
|
|
|
9013
9316
|
} catch (err2) {
|
|
9014
9317
|
const tempConfig = getConfig();
|
|
9015
9318
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
9016
|
-
const logPath =
|
|
9319
|
+
const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
|
|
9017
9320
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9018
|
-
|
|
9321
|
+
import_fs20.default.appendFileSync(
|
|
9019
9322
|
logPath,
|
|
9020
9323
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
9021
9324
|
RAW: ${raw}
|
|
@@ -9028,10 +9331,10 @@ RAW: ${raw}
|
|
|
9028
9331
|
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9029
9332
|
try {
|
|
9030
9333
|
const scriptPath = process.argv[1];
|
|
9031
|
-
if (typeof scriptPath !== "string" || !
|
|
9334
|
+
if (typeof scriptPath !== "string" || !import_path22.default.isAbsolute(scriptPath))
|
|
9032
9335
|
throw new Error("node9: argv[1] is not an absolute path");
|
|
9033
|
-
const resolvedScript =
|
|
9034
|
-
const expectedCli =
|
|
9336
|
+
const resolvedScript = import_fs20.default.realpathSync(scriptPath);
|
|
9337
|
+
const expectedCli = import_fs20.default.realpathSync(import_path22.default.resolve(__dirname, "../../cli.js"));
|
|
9035
9338
|
if (resolvedScript !== expectedCli)
|
|
9036
9339
|
throw new Error(
|
|
9037
9340
|
"node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
|
|
@@ -9057,10 +9360,10 @@ RAW: ${raw}
|
|
|
9057
9360
|
}
|
|
9058
9361
|
}
|
|
9059
9362
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9060
|
-
const logPath =
|
|
9061
|
-
if (!
|
|
9062
|
-
|
|
9063
|
-
|
|
9363
|
+
const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
|
|
9364
|
+
if (!import_fs20.default.existsSync(import_path22.default.dirname(logPath)))
|
|
9365
|
+
import_fs20.default.mkdirSync(import_path22.default.dirname(logPath), { recursive: true });
|
|
9366
|
+
import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
9064
9367
|
`);
|
|
9065
9368
|
}
|
|
9066
9369
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -9073,8 +9376,8 @@ RAW: ${raw}
|
|
|
9073
9376
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
9074
9377
|
let ttyFd = null;
|
|
9075
9378
|
try {
|
|
9076
|
-
ttyFd =
|
|
9077
|
-
const writeTty = (line) =>
|
|
9379
|
+
ttyFd = import_fs20.default.openSync("/dev/tty", "w");
|
|
9380
|
+
const writeTty = (line) => import_fs20.default.writeSync(ttyFd, line + "\n");
|
|
9078
9381
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
9079
9382
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
9080
9383
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -9093,7 +9396,7 @@ RAW: ${raw}
|
|
|
9093
9396
|
} finally {
|
|
9094
9397
|
if (ttyFd !== null)
|
|
9095
9398
|
try {
|
|
9096
|
-
|
|
9399
|
+
import_fs20.default.closeSync(ttyFd);
|
|
9097
9400
|
} catch {
|
|
9098
9401
|
}
|
|
9099
9402
|
}
|
|
@@ -9125,7 +9428,7 @@ RAW: ${raw}
|
|
|
9125
9428
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
9126
9429
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
9127
9430
|
}
|
|
9128
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
9431
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path22.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9129
9432
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
9130
9433
|
cwd: safeCwdForAuth
|
|
9131
9434
|
});
|
|
@@ -9137,12 +9440,12 @@ RAW: ${raw}
|
|
|
9137
9440
|
}
|
|
9138
9441
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
9139
9442
|
try {
|
|
9140
|
-
const tty =
|
|
9141
|
-
|
|
9443
|
+
const tty = import_fs20.default.openSync("/dev/tty", "w");
|
|
9444
|
+
import_fs20.default.writeSync(
|
|
9142
9445
|
tty,
|
|
9143
9446
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
9144
9447
|
);
|
|
9145
|
-
|
|
9448
|
+
import_fs20.default.closeSync(tty);
|
|
9146
9449
|
} catch {
|
|
9147
9450
|
}
|
|
9148
9451
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -9169,9 +9472,9 @@ RAW: ${raw}
|
|
|
9169
9472
|
});
|
|
9170
9473
|
} catch (err2) {
|
|
9171
9474
|
if (process.env.NODE9_DEBUG === "1") {
|
|
9172
|
-
const logPath =
|
|
9475
|
+
const logPath = import_path22.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
|
|
9173
9476
|
const errMsg = err2 instanceof Error ? err2.message : String(err2);
|
|
9174
|
-
|
|
9477
|
+
import_fs20.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
9175
9478
|
`);
|
|
9176
9479
|
}
|
|
9177
9480
|
process.exit(0);
|
|
@@ -9205,9 +9508,9 @@ RAW: ${raw}
|
|
|
9205
9508
|
}
|
|
9206
9509
|
|
|
9207
9510
|
// src/cli/commands/log.ts
|
|
9208
|
-
var
|
|
9209
|
-
var
|
|
9210
|
-
var
|
|
9511
|
+
var import_fs21 = __toESM(require("fs"));
|
|
9512
|
+
var import_path23 = __toESM(require("path"));
|
|
9513
|
+
var import_os17 = __toESM(require("os"));
|
|
9211
9514
|
init_audit();
|
|
9212
9515
|
init_config();
|
|
9213
9516
|
init_policy();
|
|
@@ -9251,9 +9554,9 @@ function containsShellMetachar(token) {
|
|
|
9251
9554
|
}
|
|
9252
9555
|
|
|
9253
9556
|
// src/cli/commands/log.ts
|
|
9254
|
-
var
|
|
9557
|
+
var TEST_COMMAND_RE2 = /(?:^|\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;
|
|
9255
9558
|
function detectTestResult(command, output) {
|
|
9256
|
-
if (!
|
|
9559
|
+
if (!TEST_COMMAND_RE2.test(command)) return null;
|
|
9257
9560
|
const out = output.toLowerCase();
|
|
9258
9561
|
if (/\b(tests?\s+passed|all\s+tests?\s+passed|passing|test\s+suites?.*passed|ok\b|\d+\s+passed)/i.test(
|
|
9259
9562
|
out
|
|
@@ -9283,10 +9586,10 @@ function registerLogCommand(program2) {
|
|
|
9283
9586
|
decision: "allowed",
|
|
9284
9587
|
source: "post-hook"
|
|
9285
9588
|
};
|
|
9286
|
-
const logPath =
|
|
9287
|
-
if (!
|
|
9288
|
-
|
|
9289
|
-
|
|
9589
|
+
const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
|
|
9590
|
+
if (!import_fs21.default.existsSync(import_path23.default.dirname(logPath)))
|
|
9591
|
+
import_fs21.default.mkdirSync(import_path23.default.dirname(logPath), { recursive: true });
|
|
9592
|
+
import_fs21.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
9290
9593
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
9291
9594
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
9292
9595
|
if (command) {
|
|
@@ -9302,16 +9605,24 @@ function registerLogCommand(program2) {
|
|
|
9302
9605
|
if (bashCommand && output) {
|
|
9303
9606
|
const testResult = detectTestResult(bashCommand, output);
|
|
9304
9607
|
if (testResult) {
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
ts: Date.now(),
|
|
9608
|
+
appendToLog(LOCAL_AUDIT_LOG, {
|
|
9609
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9308
9610
|
tool,
|
|
9309
|
-
|
|
9611
|
+
testResult,
|
|
9612
|
+
source: "test-result"
|
|
9310
9613
|
});
|
|
9614
|
+
if (isDaemonRunning()) {
|
|
9615
|
+
await notifyActivitySocket({
|
|
9616
|
+
id: "test-result",
|
|
9617
|
+
ts: Date.now(),
|
|
9618
|
+
tool,
|
|
9619
|
+
status: testResult === "pass" ? "test_pass" : "test_fail"
|
|
9620
|
+
});
|
|
9621
|
+
}
|
|
9311
9622
|
}
|
|
9312
9623
|
}
|
|
9313
9624
|
}
|
|
9314
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
9625
|
+
const safeCwd = typeof payload.cwd === "string" && import_path23.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
9315
9626
|
const config = getConfig(safeCwd);
|
|
9316
9627
|
if (shouldSnapshot(tool, {}, config)) {
|
|
9317
9628
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -9320,9 +9631,9 @@ function registerLogCommand(program2) {
|
|
|
9320
9631
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
9321
9632
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
9322
9633
|
`);
|
|
9323
|
-
const debugPath =
|
|
9634
|
+
const debugPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "hook-debug.log");
|
|
9324
9635
|
try {
|
|
9325
|
-
|
|
9636
|
+
import_fs21.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
9326
9637
|
`);
|
|
9327
9638
|
} catch {
|
|
9328
9639
|
}
|
|
@@ -9722,14 +10033,14 @@ function registerConfigShowCommand(program2) {
|
|
|
9722
10033
|
|
|
9723
10034
|
// src/cli/commands/doctor.ts
|
|
9724
10035
|
var import_chalk7 = __toESM(require("chalk"));
|
|
9725
|
-
var
|
|
9726
|
-
var
|
|
9727
|
-
var
|
|
10036
|
+
var import_fs22 = __toESM(require("fs"));
|
|
10037
|
+
var import_path24 = __toESM(require("path"));
|
|
10038
|
+
var import_os18 = __toESM(require("os"));
|
|
9728
10039
|
var import_child_process10 = require("child_process");
|
|
9729
10040
|
init_daemon();
|
|
9730
10041
|
function registerDoctorCommand(program2, version2) {
|
|
9731
10042
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
9732
|
-
const homeDir2 =
|
|
10043
|
+
const homeDir2 = import_os18.default.homedir();
|
|
9733
10044
|
let failures = 0;
|
|
9734
10045
|
function pass(msg) {
|
|
9735
10046
|
console.log(import_chalk7.default.green(" \u2705 ") + msg);
|
|
@@ -9778,10 +10089,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9778
10089
|
);
|
|
9779
10090
|
}
|
|
9780
10091
|
section("Configuration");
|
|
9781
|
-
const globalConfigPath =
|
|
9782
|
-
if (
|
|
10092
|
+
const globalConfigPath = import_path24.default.join(homeDir2, ".node9", "config.json");
|
|
10093
|
+
if (import_fs22.default.existsSync(globalConfigPath)) {
|
|
9783
10094
|
try {
|
|
9784
|
-
JSON.parse(
|
|
10095
|
+
JSON.parse(import_fs22.default.readFileSync(globalConfigPath, "utf-8"));
|
|
9785
10096
|
pass("~/.node9/config.json found and valid");
|
|
9786
10097
|
} catch {
|
|
9787
10098
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -9789,10 +10100,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9789
10100
|
} else {
|
|
9790
10101
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
9791
10102
|
}
|
|
9792
|
-
const projectConfigPath =
|
|
9793
|
-
if (
|
|
10103
|
+
const projectConfigPath = import_path24.default.join(process.cwd(), "node9.config.json");
|
|
10104
|
+
if (import_fs22.default.existsSync(projectConfigPath)) {
|
|
9794
10105
|
try {
|
|
9795
|
-
JSON.parse(
|
|
10106
|
+
JSON.parse(import_fs22.default.readFileSync(projectConfigPath, "utf-8"));
|
|
9796
10107
|
pass("node9.config.json found and valid (project)");
|
|
9797
10108
|
} catch {
|
|
9798
10109
|
fail(
|
|
@@ -9801,8 +10112,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9801
10112
|
);
|
|
9802
10113
|
}
|
|
9803
10114
|
}
|
|
9804
|
-
const credsPath =
|
|
9805
|
-
if (
|
|
10115
|
+
const credsPath = import_path24.default.join(homeDir2, ".node9", "credentials.json");
|
|
10116
|
+
if (import_fs22.default.existsSync(credsPath)) {
|
|
9806
10117
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
9807
10118
|
} else {
|
|
9808
10119
|
warn(
|
|
@@ -9811,10 +10122,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9811
10122
|
);
|
|
9812
10123
|
}
|
|
9813
10124
|
section("Agent Hooks");
|
|
9814
|
-
const claudeSettingsPath =
|
|
9815
|
-
if (
|
|
10125
|
+
const claudeSettingsPath = import_path24.default.join(homeDir2, ".claude", "settings.json");
|
|
10126
|
+
if (import_fs22.default.existsSync(claudeSettingsPath)) {
|
|
9816
10127
|
try {
|
|
9817
|
-
const cs = JSON.parse(
|
|
10128
|
+
const cs = JSON.parse(import_fs22.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
9818
10129
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
9819
10130
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9820
10131
|
);
|
|
@@ -9830,10 +10141,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9830
10141
|
} else {
|
|
9831
10142
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
9832
10143
|
}
|
|
9833
|
-
const geminiSettingsPath =
|
|
9834
|
-
if (
|
|
10144
|
+
const geminiSettingsPath = import_path24.default.join(homeDir2, ".gemini", "settings.json");
|
|
10145
|
+
if (import_fs22.default.existsSync(geminiSettingsPath)) {
|
|
9835
10146
|
try {
|
|
9836
|
-
const gs = JSON.parse(
|
|
10147
|
+
const gs = JSON.parse(import_fs22.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
9837
10148
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
9838
10149
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
9839
10150
|
);
|
|
@@ -9849,10 +10160,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9849
10160
|
} else {
|
|
9850
10161
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
9851
10162
|
}
|
|
9852
|
-
const cursorHooksPath =
|
|
9853
|
-
if (
|
|
10163
|
+
const cursorHooksPath = import_path24.default.join(homeDir2, ".cursor", "hooks.json");
|
|
10164
|
+
if (import_fs22.default.existsSync(cursorHooksPath)) {
|
|
9854
10165
|
try {
|
|
9855
|
-
const cur = JSON.parse(
|
|
10166
|
+
const cur = JSON.parse(import_fs22.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
9856
10167
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
9857
10168
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
9858
10169
|
);
|
|
@@ -9890,9 +10201,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9890
10201
|
|
|
9891
10202
|
// src/cli/commands/audit.ts
|
|
9892
10203
|
var import_chalk8 = __toESM(require("chalk"));
|
|
9893
|
-
var
|
|
9894
|
-
var
|
|
9895
|
-
var
|
|
10204
|
+
var import_fs23 = __toESM(require("fs"));
|
|
10205
|
+
var import_path25 = __toESM(require("path"));
|
|
10206
|
+
var import_os19 = __toESM(require("os"));
|
|
9896
10207
|
function formatRelativeTime(timestamp) {
|
|
9897
10208
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
9898
10209
|
const sec = Math.floor(diff / 1e3);
|
|
@@ -9905,14 +10216,14 @@ function formatRelativeTime(timestamp) {
|
|
|
9905
10216
|
}
|
|
9906
10217
|
function registerAuditCommand(program2) {
|
|
9907
10218
|
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) => {
|
|
9908
|
-
const logPath =
|
|
9909
|
-
if (!
|
|
10219
|
+
const logPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "audit.log");
|
|
10220
|
+
if (!import_fs23.default.existsSync(logPath)) {
|
|
9910
10221
|
console.log(
|
|
9911
10222
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
9912
10223
|
);
|
|
9913
10224
|
return;
|
|
9914
10225
|
}
|
|
9915
|
-
const raw =
|
|
10226
|
+
const raw = import_fs23.default.readFileSync(logPath, "utf-8");
|
|
9916
10227
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
9917
10228
|
let entries = lines.flatMap((line) => {
|
|
9918
10229
|
try {
|
|
@@ -9964,8 +10275,443 @@ function registerAuditCommand(program2) {
|
|
|
9964
10275
|
});
|
|
9965
10276
|
}
|
|
9966
10277
|
|
|
9967
|
-
// src/cli/commands/
|
|
10278
|
+
// src/cli/commands/report.ts
|
|
9968
10279
|
var import_chalk9 = __toESM(require("chalk"));
|
|
10280
|
+
var import_fs24 = __toESM(require("fs"));
|
|
10281
|
+
var import_path26 = __toESM(require("path"));
|
|
10282
|
+
var import_os20 = __toESM(require("os"));
|
|
10283
|
+
var TEST_COMMAND_RE3 = /(?:^|\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;
|
|
10284
|
+
function buildTestTimestamps(allEntries) {
|
|
10285
|
+
const testTs = /* @__PURE__ */ new Set();
|
|
10286
|
+
for (const e of allEntries) {
|
|
10287
|
+
if (e.source !== "post-hook") continue;
|
|
10288
|
+
if (e.tool !== "Bash" && e.tool !== "bash") continue;
|
|
10289
|
+
const cmd = e.args?.command;
|
|
10290
|
+
if (typeof cmd === "string" && TEST_COMMAND_RE3.test(cmd)) {
|
|
10291
|
+
testTs.add(new Date(e.ts).getTime());
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
return testTs;
|
|
10295
|
+
}
|
|
10296
|
+
function isTestEntry(entry, testTs) {
|
|
10297
|
+
if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
|
|
10298
|
+
if (entry.testRun === true) return true;
|
|
10299
|
+
const cmd = entry.args?.command;
|
|
10300
|
+
if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
|
|
10301
|
+
const t = new Date(entry.ts).getTime();
|
|
10302
|
+
for (const ts of testTs) {
|
|
10303
|
+
if (Math.abs(ts - t) <= 3e3) return true;
|
|
10304
|
+
}
|
|
10305
|
+
return false;
|
|
10306
|
+
}
|
|
10307
|
+
function getDateRange(period) {
|
|
10308
|
+
const now = /* @__PURE__ */ new Date();
|
|
10309
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
10310
|
+
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
10311
|
+
switch (period) {
|
|
10312
|
+
case "today":
|
|
10313
|
+
return { start: todayStart, end };
|
|
10314
|
+
case "7d": {
|
|
10315
|
+
const s = new Date(todayStart);
|
|
10316
|
+
s.setDate(s.getDate() - 6);
|
|
10317
|
+
return { start: s, end };
|
|
10318
|
+
}
|
|
10319
|
+
case "30d": {
|
|
10320
|
+
const s = new Date(todayStart);
|
|
10321
|
+
s.setDate(s.getDate() - 29);
|
|
10322
|
+
return { start: s, end };
|
|
10323
|
+
}
|
|
10324
|
+
case "month":
|
|
10325
|
+
return { start: new Date(now.getFullYear(), now.getMonth(), 1), end };
|
|
10326
|
+
}
|
|
10327
|
+
}
|
|
10328
|
+
function parseAuditLog(logPath) {
|
|
10329
|
+
if (!import_fs24.default.existsSync(logPath)) return [];
|
|
10330
|
+
const raw = import_fs24.default.readFileSync(logPath, "utf-8");
|
|
10331
|
+
return raw.split("\n").flatMap((line) => {
|
|
10332
|
+
if (!line.trim()) return [];
|
|
10333
|
+
try {
|
|
10334
|
+
return [JSON.parse(line)];
|
|
10335
|
+
} catch {
|
|
10336
|
+
return [];
|
|
10337
|
+
}
|
|
10338
|
+
});
|
|
10339
|
+
}
|
|
10340
|
+
function isAllow(decision) {
|
|
10341
|
+
return decision.startsWith("allow");
|
|
10342
|
+
}
|
|
10343
|
+
function isDlp(checkedBy) {
|
|
10344
|
+
return !!checkedBy?.includes("dlp");
|
|
10345
|
+
}
|
|
10346
|
+
function barStr(value, max, width) {
|
|
10347
|
+
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
10348
|
+
const filled = Math.max(1, Math.round(value / max * width));
|
|
10349
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
10350
|
+
}
|
|
10351
|
+
function colorBar(value, max, width) {
|
|
10352
|
+
const s = barStr(value, max, width);
|
|
10353
|
+
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
10354
|
+
return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
|
|
10355
|
+
}
|
|
10356
|
+
function pct(num2, total) {
|
|
10357
|
+
if (total === 0) return "\u2013";
|
|
10358
|
+
return Math.round(num2 / total * 100) + "%";
|
|
10359
|
+
}
|
|
10360
|
+
function fmtDate(d) {
|
|
10361
|
+
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
10362
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
10363
|
+
}
|
|
10364
|
+
function num(n) {
|
|
10365
|
+
return n.toLocaleString();
|
|
10366
|
+
}
|
|
10367
|
+
function fmtCost(usd) {
|
|
10368
|
+
if (usd < 1e-3) return "< $0.001";
|
|
10369
|
+
if (usd < 1) return "$" + usd.toFixed(4);
|
|
10370
|
+
return "$" + usd.toFixed(2);
|
|
10371
|
+
}
|
|
10372
|
+
var CLAUDE_PRICING = {
|
|
10373
|
+
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
10374
|
+
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
10375
|
+
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
10376
|
+
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10377
|
+
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10378
|
+
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10379
|
+
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10380
|
+
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
10381
|
+
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
10382
|
+
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
10383
|
+
};
|
|
10384
|
+
function claudeModelPrice(model) {
|
|
10385
|
+
const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
10386
|
+
for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
|
|
10387
|
+
if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
|
|
10388
|
+
}
|
|
10389
|
+
return null;
|
|
10390
|
+
}
|
|
10391
|
+
function loadClaudeCost(start, end) {
|
|
10392
|
+
const empty = {
|
|
10393
|
+
total: 0,
|
|
10394
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
10395
|
+
byModel: /* @__PURE__ */ new Map(),
|
|
10396
|
+
inputTokens: 0,
|
|
10397
|
+
cacheReadTokens: 0
|
|
10398
|
+
};
|
|
10399
|
+
const projectsDir = import_path26.default.join(import_os20.default.homedir(), ".claude", "projects");
|
|
10400
|
+
if (!import_fs24.default.existsSync(projectsDir)) return empty;
|
|
10401
|
+
let dirs;
|
|
10402
|
+
try {
|
|
10403
|
+
dirs = import_fs24.default.readdirSync(projectsDir);
|
|
10404
|
+
} catch {
|
|
10405
|
+
return empty;
|
|
10406
|
+
}
|
|
10407
|
+
let total = 0;
|
|
10408
|
+
let inputTokens = 0;
|
|
10409
|
+
let cacheReadTokens = 0;
|
|
10410
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
10411
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
10412
|
+
for (const proj of dirs) {
|
|
10413
|
+
const projPath = import_path26.default.join(projectsDir, proj);
|
|
10414
|
+
let files;
|
|
10415
|
+
try {
|
|
10416
|
+
const stat = import_fs24.default.statSync(projPath);
|
|
10417
|
+
if (!stat.isDirectory()) continue;
|
|
10418
|
+
files = import_fs24.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
10419
|
+
} catch {
|
|
10420
|
+
continue;
|
|
10421
|
+
}
|
|
10422
|
+
for (const file of files) {
|
|
10423
|
+
try {
|
|
10424
|
+
const raw = import_fs24.default.readFileSync(import_path26.default.join(projPath, file), "utf-8");
|
|
10425
|
+
for (const line of raw.split("\n")) {
|
|
10426
|
+
if (!line.trim()) continue;
|
|
10427
|
+
let entry;
|
|
10428
|
+
try {
|
|
10429
|
+
entry = JSON.parse(line);
|
|
10430
|
+
} catch {
|
|
10431
|
+
continue;
|
|
10432
|
+
}
|
|
10433
|
+
if (entry.type !== "assistant") continue;
|
|
10434
|
+
if (!entry.timestamp) continue;
|
|
10435
|
+
const ts = new Date(entry.timestamp);
|
|
10436
|
+
if (ts < start || ts > end) continue;
|
|
10437
|
+
const usage = entry.message?.usage;
|
|
10438
|
+
const model = entry.message?.model;
|
|
10439
|
+
if (!usage || !model) continue;
|
|
10440
|
+
const p = claudeModelPrice(model);
|
|
10441
|
+
if (!p) continue;
|
|
10442
|
+
const inp = usage.input_tokens ?? 0;
|
|
10443
|
+
const out = usage.output_tokens ?? 0;
|
|
10444
|
+
const cw = usage.cache_creation_input_tokens ?? 0;
|
|
10445
|
+
const cr = usage.cache_read_input_tokens ?? 0;
|
|
10446
|
+
const cost = inp * p.i + out * p.o + cw * p.cw + cr * p.cr;
|
|
10447
|
+
total += cost;
|
|
10448
|
+
inputTokens += inp;
|
|
10449
|
+
cacheReadTokens += cr;
|
|
10450
|
+
const dateKey = entry.timestamp.slice(0, 10);
|
|
10451
|
+
byDay.set(dateKey, (byDay.get(dateKey) ?? 0) + cost);
|
|
10452
|
+
const normModel = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
|
|
10453
|
+
byModel.set(normModel, (byModel.get(normModel) ?? 0) + cost);
|
|
10454
|
+
}
|
|
10455
|
+
} catch {
|
|
10456
|
+
continue;
|
|
10457
|
+
}
|
|
10458
|
+
}
|
|
10459
|
+
}
|
|
10460
|
+
return { total, byDay, byModel, inputTokens, cacheReadTokens };
|
|
10461
|
+
}
|
|
10462
|
+
function registerReportCommand(program2) {
|
|
10463
|
+
program2.command("report").description("Activity and security report \u2014 what Claude did, what was blocked").option("--period <period>", "today | 7d | 30d | month", "7d").option("--no-tests", "exclude test runner calls (npm test, vitest, pytest\u2026) from stats").action((options) => {
|
|
10464
|
+
const period = ["today", "7d", "30d", "month"].includes(
|
|
10465
|
+
options.period
|
|
10466
|
+
) ? options.period : "7d";
|
|
10467
|
+
const logPath = import_path26.default.join(import_os20.default.homedir(), ".node9", "audit.log");
|
|
10468
|
+
const allEntries = parseAuditLog(logPath);
|
|
10469
|
+
if (allEntries.length === 0) {
|
|
10470
|
+
console.log(
|
|
10471
|
+
import_chalk9.default.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
10472
|
+
);
|
|
10473
|
+
return;
|
|
10474
|
+
}
|
|
10475
|
+
const { start, end } = getDateRange(period);
|
|
10476
|
+
const {
|
|
10477
|
+
total: costUSD,
|
|
10478
|
+
byDay: costByDay,
|
|
10479
|
+
byModel: costByModel,
|
|
10480
|
+
inputTokens: costInputTokens,
|
|
10481
|
+
cacheReadTokens: costCacheRead
|
|
10482
|
+
} = loadClaudeCost(start, end);
|
|
10483
|
+
const periodMs = end.getTime() - start.getTime();
|
|
10484
|
+
const priorEnd = new Date(start.getTime() - 1);
|
|
10485
|
+
const priorStart = new Date(start.getTime() - periodMs);
|
|
10486
|
+
const priorEntries = allEntries.filter((e) => {
|
|
10487
|
+
if (e.source === "post-hook") return false;
|
|
10488
|
+
const ts = new Date(e.ts);
|
|
10489
|
+
return ts >= priorStart && ts <= priorEnd;
|
|
10490
|
+
});
|
|
10491
|
+
const priorBlocked = priorEntries.filter((e) => !isAllow(e.decision)).length;
|
|
10492
|
+
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
10493
|
+
const excludeTests = options.tests === false;
|
|
10494
|
+
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
10495
|
+
let filteredTestCount = 0;
|
|
10496
|
+
const entries = allEntries.filter((e) => {
|
|
10497
|
+
if (e.source === "post-hook") return false;
|
|
10498
|
+
const ts = new Date(e.ts);
|
|
10499
|
+
if (ts < start || ts > end) return false;
|
|
10500
|
+
if (excludeTests && isTestEntry(e, testTs)) {
|
|
10501
|
+
filteredTestCount++;
|
|
10502
|
+
return false;
|
|
10503
|
+
}
|
|
10504
|
+
return true;
|
|
10505
|
+
});
|
|
10506
|
+
if (entries.length === 0) {
|
|
10507
|
+
console.log(import_chalk9.default.yellow(`
|
|
10508
|
+
No activity for period "${period}".
|
|
10509
|
+
`));
|
|
10510
|
+
return;
|
|
10511
|
+
}
|
|
10512
|
+
let allowed = 0;
|
|
10513
|
+
let blocked = 0;
|
|
10514
|
+
let dlpHits = 0;
|
|
10515
|
+
let loopHits = 0;
|
|
10516
|
+
let testPasses = 0;
|
|
10517
|
+
let testFails = 0;
|
|
10518
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
10519
|
+
const blockMap = /* @__PURE__ */ new Map();
|
|
10520
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
10521
|
+
const mcpMap = /* @__PURE__ */ new Map();
|
|
10522
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
10523
|
+
const hourMap = /* @__PURE__ */ new Map();
|
|
10524
|
+
for (const e of entries) {
|
|
10525
|
+
const allow = isAllow(e.decision);
|
|
10526
|
+
const dateKey = e.ts.slice(0, 10);
|
|
10527
|
+
if (allow) allowed++;
|
|
10528
|
+
else blocked++;
|
|
10529
|
+
if (isDlp(e.checkedBy)) dlpHits++;
|
|
10530
|
+
if (e.checkedBy === "loop-detected") loopHits++;
|
|
10531
|
+
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
10532
|
+
t.calls++;
|
|
10533
|
+
if (!allow) t.blocked++;
|
|
10534
|
+
toolMap.set(e.tool, t);
|
|
10535
|
+
if (!allow && e.checkedBy) {
|
|
10536
|
+
blockMap.set(e.checkedBy, (blockMap.get(e.checkedBy) ?? 0) + 1);
|
|
10537
|
+
}
|
|
10538
|
+
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
10539
|
+
if (e.mcpServer) mcpMap.set(e.mcpServer, (mcpMap.get(e.mcpServer) ?? 0) + 1);
|
|
10540
|
+
const hour = new Date(e.ts).getHours();
|
|
10541
|
+
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
10542
|
+
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
10543
|
+
d.calls++;
|
|
10544
|
+
if (!allow) d.blocked++;
|
|
10545
|
+
dailyMap.set(dateKey, d);
|
|
10546
|
+
}
|
|
10547
|
+
for (const e of allEntries) {
|
|
10548
|
+
if (e.source !== "test-result") continue;
|
|
10549
|
+
const ts = new Date(e.ts);
|
|
10550
|
+
if (ts < start || ts > end) continue;
|
|
10551
|
+
if (e.testResult === "pass") testPasses++;
|
|
10552
|
+
else if (e.testResult === "fail") testFails++;
|
|
10553
|
+
}
|
|
10554
|
+
const total = entries.length;
|
|
10555
|
+
const topTools = [...toolMap.entries()].sort((a, b) => b[1].calls - a[1].calls).slice(0, 8);
|
|
10556
|
+
const topBlocks = [...blockMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
10557
|
+
const dailyList = [...dailyMap.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-14);
|
|
10558
|
+
const maxTool = Math.max(...topTools.map(([, v]) => v.calls), 1);
|
|
10559
|
+
const maxBlock = Math.max(...topBlocks.map(([, v]) => v), 1);
|
|
10560
|
+
const maxDaily = Math.max(...dailyList.map(([, v]) => v.calls), 1);
|
|
10561
|
+
const W = Math.min(process.stdout.columns || 80, 100);
|
|
10562
|
+
const INNER = W - 4;
|
|
10563
|
+
const COL = Math.floor(INNER / 2) - 1;
|
|
10564
|
+
const LABEL = 24;
|
|
10565
|
+
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
10566
|
+
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num(v.calls).length), 1);
|
|
10567
|
+
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num(v).length), 1);
|
|
10568
|
+
const line = import_chalk9.default.dim("\u2500".repeat(W - 2));
|
|
10569
|
+
const periodLabel = {
|
|
10570
|
+
today: "Today",
|
|
10571
|
+
"7d": "Last 7 Days",
|
|
10572
|
+
"30d": "Last 30 Days",
|
|
10573
|
+
month: "This Month"
|
|
10574
|
+
};
|
|
10575
|
+
console.log("");
|
|
10576
|
+
console.log(
|
|
10577
|
+
" " + import_chalk9.default.bold.cyan("\u{1F6E1} node9 Report") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.white(periodLabel[period]) + import_chalk9.default.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + import_chalk9.default.dim(` ${num(total)} events`) + (excludeTests ? import_chalk9.default.dim(` \u2013tests (\u2013${filteredTestCount})`) : "")
|
|
10578
|
+
);
|
|
10579
|
+
console.log(" " + line);
|
|
10580
|
+
console.log("");
|
|
10581
|
+
const blockLabel = blocked > 0 ? import_chalk9.default.red(`\u{1F6D1} ${num(blocked)} blocked`) : import_chalk9.default.dim("\u{1F6D1} 0 blocked");
|
|
10582
|
+
const dlpLabel = dlpHits > 0 ? import_chalk9.default.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : import_chalk9.default.dim("\u{1F6A8} 0 DLP hits");
|
|
10583
|
+
const loopLabel = loopHits > 0 ? import_chalk9.default.yellow(`\u{1F504} ${loopHits} loops`) : import_chalk9.default.dim("\u{1F504} 0 loops");
|
|
10584
|
+
const currentRate = total > 0 ? blocked / total : 0;
|
|
10585
|
+
const trendLabel = (() => {
|
|
10586
|
+
if (priorBlockRate === null) return import_chalk9.default.dim(`${pct(blocked, total)} block rate`);
|
|
10587
|
+
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
10588
|
+
const arrow = delta > 0 ? import_chalk9.default.red(`\u25B2${delta}%`) : delta < 0 ? import_chalk9.default.green(`\u25BC${Math.abs(delta)}%`) : import_chalk9.default.dim("\u2013");
|
|
10589
|
+
return import_chalk9.default.dim(`${pct(blocked, total)} block rate `) + arrow + import_chalk9.default.dim(" vs prior");
|
|
10590
|
+
})();
|
|
10591
|
+
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
10592
|
+
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
10593
|
+
const ratioLabel = reads > 0 ? import_chalk9.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk9.default.dim("edit/read \u2013");
|
|
10594
|
+
const testLabel = testPasses + testFails > 0 ? import_chalk9.default.dim("tests ") + import_chalk9.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk9.default.red(`${testFails}\u2717`) : "") : import_chalk9.default.dim("tests \u2013");
|
|
10595
|
+
console.log(
|
|
10596
|
+
" " + import_chalk9.default.green(`\u2705 ${num(allowed)} allowed`) + " " + blockLabel + " " + dlpLabel + " " + loopLabel + " " + trendLabel
|
|
10597
|
+
);
|
|
10598
|
+
console.log(" " + ratioLabel + " " + testLabel);
|
|
10599
|
+
console.log("");
|
|
10600
|
+
const toolHeaderRaw = "Top Tools";
|
|
10601
|
+
const blockHeaderRaw = "Top Blocks";
|
|
10602
|
+
console.log(
|
|
10603
|
+
" " + import_chalk9.default.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + import_chalk9.default.bold(blockHeaderRaw)
|
|
10604
|
+
);
|
|
10605
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(COL)) + " " + import_chalk9.default.dim("\u2500".repeat(COL)));
|
|
10606
|
+
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
10607
|
+
for (let i = 0; i < rows; i++) {
|
|
10608
|
+
let leftStyled = " ".repeat(COL);
|
|
10609
|
+
if (i < topTools.length) {
|
|
10610
|
+
const [tool, { calls }] = topTools[i];
|
|
10611
|
+
const label = tool.length > LABEL - 1 ? tool.slice(0, LABEL - 2) + "\u2026" : tool;
|
|
10612
|
+
const countStr = num(calls).padStart(TOOL_COUNT_W);
|
|
10613
|
+
const b = colorBar(calls, maxTool, BAR);
|
|
10614
|
+
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
10615
|
+
const pad = Math.max(0, COL - rawLen);
|
|
10616
|
+
leftStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(countStr) + " ".repeat(pad);
|
|
10617
|
+
}
|
|
10618
|
+
let rightStyled = "";
|
|
10619
|
+
if (i < topBlocks.length) {
|
|
10620
|
+
const [reason, count] = topBlocks[i];
|
|
10621
|
+
const label = reason.length > LABEL - 1 ? reason.slice(0, LABEL - 2) + "\u2026" : reason;
|
|
10622
|
+
const countStr = num(count).padStart(BLOCK_COUNT_W);
|
|
10623
|
+
const b = colorBar(count, maxBlock, BAR);
|
|
10624
|
+
rightStyled = import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.red(countStr);
|
|
10625
|
+
}
|
|
10626
|
+
console.log(" " + leftStyled + " " + rightStyled);
|
|
10627
|
+
}
|
|
10628
|
+
if (topBlocks.length === 0) {
|
|
10629
|
+
console.log(" " + " ".repeat(COL) + " " + import_chalk9.default.dim("nothing blocked \u2713"));
|
|
10630
|
+
}
|
|
10631
|
+
if (agentMap.size > 1) {
|
|
10632
|
+
console.log("");
|
|
10633
|
+
console.log(" " + import_chalk9.default.bold("Agents"));
|
|
10634
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10635
|
+
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
10636
|
+
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
10637
|
+
const label = agent.slice(0, LABEL - 1);
|
|
10638
|
+
const b = colorBar(count, maxAgent, BAR);
|
|
10639
|
+
console.log(" " + import_chalk9.default.white(label.padEnd(LABEL)) + b + " " + import_chalk9.default.white(num(count)));
|
|
10640
|
+
}
|
|
10641
|
+
}
|
|
10642
|
+
if (mcpMap.size > 0) {
|
|
10643
|
+
console.log("");
|
|
10644
|
+
console.log(" " + import_chalk9.default.bold("MCP Servers"));
|
|
10645
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10646
|
+
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
10647
|
+
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
10648
|
+
const label = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
10649
|
+
const b = colorBar(count, maxMcp, BAR);
|
|
10650
|
+
console.log(" " + import_chalk9.default.white(label) + b + " " + import_chalk9.default.white(num(count)));
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10653
|
+
if (hourMap.size > 0) {
|
|
10654
|
+
const BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
10655
|
+
const maxHour = Math.max(...hourMap.values(), 1);
|
|
10656
|
+
const bar = Array.from({ length: 24 }, (_, h) => {
|
|
10657
|
+
const v = hourMap.get(h) ?? 0;
|
|
10658
|
+
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
10659
|
+
}).join("");
|
|
10660
|
+
console.log("");
|
|
10661
|
+
console.log(" " + import_chalk9.default.bold("Hour of Day") + import_chalk9.default.dim(" (local, 0h \u2013 23h)"));
|
|
10662
|
+
console.log(" " + import_chalk9.default.cyan(bar));
|
|
10663
|
+
console.log(" " + import_chalk9.default.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
10664
|
+
}
|
|
10665
|
+
if (dailyList.length > 1) {
|
|
10666
|
+
console.log("");
|
|
10667
|
+
console.log(" " + import_chalk9.default.bold("Daily Activity"));
|
|
10668
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(W - 2)));
|
|
10669
|
+
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
10670
|
+
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
10671
|
+
const label = fmtDate(dateKey).padEnd(10);
|
|
10672
|
+
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
10673
|
+
const dayCost = costByDay.get(dateKey);
|
|
10674
|
+
const costNote = dayCost ? import_chalk9.default.magenta(` ${fmtCost(dayCost)}`) : "";
|
|
10675
|
+
const blockNote = db > 0 ? import_chalk9.default.red(` ${db} blocked`) : "";
|
|
10676
|
+
console.log(
|
|
10677
|
+
" " + import_chalk9.default.dim(label) + " " + b + " " + import_chalk9.default.white(num(calls)) + blockNote + costNote
|
|
10678
|
+
);
|
|
10679
|
+
}
|
|
10680
|
+
}
|
|
10681
|
+
if (costUSD > 0) {
|
|
10682
|
+
const periodDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 864e5));
|
|
10683
|
+
const avgPerDay = costUSD / periodDays;
|
|
10684
|
+
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
10685
|
+
const costHeaderRight = [
|
|
10686
|
+
import_chalk9.default.yellow(fmtCost(costUSD)),
|
|
10687
|
+
import_chalk9.default.dim(`avg ${fmtCost(avgPerDay)}/day`),
|
|
10688
|
+
cacheHitPct > 0 ? import_chalk9.default.dim(`${cacheHitPct}% cache hit`) : null
|
|
10689
|
+
].filter(Boolean).join(import_chalk9.default.dim(" \xB7 "));
|
|
10690
|
+
console.log("");
|
|
10691
|
+
console.log(" " + import_chalk9.default.bold("Cost") + " " + costHeaderRight);
|
|
10692
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
10693
|
+
const modelList = [...costByModel.entries()].sort((a, b) => b[1] - a[1]);
|
|
10694
|
+
const maxModelCost = Math.max(...modelList.map(([, v]) => v), 1e-9);
|
|
10695
|
+
const MODEL_LABEL = 22;
|
|
10696
|
+
const MODEL_BAR = Math.max(6, Math.min(20, W - MODEL_LABEL - 12));
|
|
10697
|
+
for (const [model, cost] of modelList) {
|
|
10698
|
+
const label = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
10699
|
+
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
10700
|
+
console.log(
|
|
10701
|
+
" " + import_chalk9.default.white(label.padEnd(MODEL_LABEL)) + b + " " + import_chalk9.default.yellow(fmtCost(cost))
|
|
10702
|
+
);
|
|
10703
|
+
}
|
|
10704
|
+
}
|
|
10705
|
+
console.log("");
|
|
10706
|
+
console.log(
|
|
10707
|
+
" " + import_chalk9.default.dim("node9 audit --deny") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
10708
|
+
);
|
|
10709
|
+
console.log("");
|
|
10710
|
+
});
|
|
10711
|
+
}
|
|
10712
|
+
|
|
10713
|
+
// src/cli/commands/daemon-cmd.ts
|
|
10714
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
9969
10715
|
var import_child_process11 = require("child_process");
|
|
9970
10716
|
init_daemon2();
|
|
9971
10717
|
init_daemon();
|
|
@@ -9980,7 +10726,7 @@ function registerDaemonCommand(program2) {
|
|
|
9980
10726
|
if (cmd === "status") return daemonStatus();
|
|
9981
10727
|
if (cmd !== "start" && action !== void 0) {
|
|
9982
10728
|
console.error(
|
|
9983
|
-
|
|
10729
|
+
import_chalk10.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`)
|
|
9984
10730
|
);
|
|
9985
10731
|
process.exit(1);
|
|
9986
10732
|
}
|
|
@@ -9988,7 +10734,7 @@ function registerDaemonCommand(program2) {
|
|
|
9988
10734
|
process.env.NODE9_WATCH_MODE = "1";
|
|
9989
10735
|
setTimeout(() => {
|
|
9990
10736
|
openBrowserLocal();
|
|
9991
|
-
console.log(
|
|
10737
|
+
console.log(import_chalk10.default.cyan(`\u{1F6F0}\uFE0F Flight Recorder: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
9992
10738
|
}, 600);
|
|
9993
10739
|
startDaemon();
|
|
9994
10740
|
return;
|
|
@@ -9996,7 +10742,7 @@ function registerDaemonCommand(program2) {
|
|
|
9996
10742
|
if (options.openui) {
|
|
9997
10743
|
if (isDaemonRunning()) {
|
|
9998
10744
|
openBrowserLocal();
|
|
9999
|
-
console.log(
|
|
10745
|
+
console.log(import_chalk10.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
10000
10746
|
process.exit(0);
|
|
10001
10747
|
}
|
|
10002
10748
|
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
@@ -10009,7 +10755,7 @@ function registerDaemonCommand(program2) {
|
|
|
10009
10755
|
if (isDaemonRunning()) break;
|
|
10010
10756
|
}
|
|
10011
10757
|
openBrowserLocal();
|
|
10012
|
-
console.log(
|
|
10758
|
+
console.log(import_chalk10.default.green(`
|
|
10013
10759
|
\u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
|
|
10014
10760
|
process.exit(0);
|
|
10015
10761
|
}
|
|
@@ -10019,7 +10765,7 @@ function registerDaemonCommand(program2) {
|
|
|
10019
10765
|
stdio: "ignore"
|
|
10020
10766
|
});
|
|
10021
10767
|
child.unref();
|
|
10022
|
-
console.log(
|
|
10768
|
+
console.log(import_chalk10.default.green(`
|
|
10023
10769
|
\u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
|
|
10024
10770
|
process.exit(0);
|
|
10025
10771
|
}
|
|
@@ -10029,15 +10775,15 @@ function registerDaemonCommand(program2) {
|
|
|
10029
10775
|
}
|
|
10030
10776
|
|
|
10031
10777
|
// src/cli/commands/status.ts
|
|
10032
|
-
var
|
|
10033
|
-
var
|
|
10034
|
-
var
|
|
10035
|
-
var
|
|
10778
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
10779
|
+
var import_fs25 = __toESM(require("fs"));
|
|
10780
|
+
var import_path27 = __toESM(require("path"));
|
|
10781
|
+
var import_os21 = __toESM(require("os"));
|
|
10036
10782
|
init_core();
|
|
10037
10783
|
init_daemon();
|
|
10038
10784
|
function readJson2(filePath) {
|
|
10039
10785
|
try {
|
|
10040
|
-
if (
|
|
10786
|
+
if (import_fs25.default.existsSync(filePath)) return JSON.parse(import_fs25.default.readFileSync(filePath, "utf-8"));
|
|
10041
10787
|
} catch {
|
|
10042
10788
|
}
|
|
10043
10789
|
return null;
|
|
@@ -10051,21 +10797,21 @@ function wrappedMcpServers(servers) {
|
|
|
10051
10797
|
return Object.entries(servers).filter(([, s]) => s.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
|
|
10052
10798
|
}
|
|
10053
10799
|
function printAgentSection(label, hookPairs, wrapped) {
|
|
10054
|
-
console.log(
|
|
10800
|
+
console.log(import_chalk11.default.bold(` ${label}`));
|
|
10055
10801
|
for (const { name, present } of hookPairs) {
|
|
10056
10802
|
if (present) {
|
|
10057
|
-
console.log(
|
|
10803
|
+
console.log(import_chalk11.default.green(` \u2713 ${name}`));
|
|
10058
10804
|
} else {
|
|
10059
|
-
console.log(
|
|
10805
|
+
console.log(import_chalk11.default.red(` \u2717 ${name}`) + import_chalk11.default.gray(" (not wired)"));
|
|
10060
10806
|
}
|
|
10061
10807
|
}
|
|
10062
10808
|
if (wrapped.length > 0) {
|
|
10063
|
-
console.log(
|
|
10809
|
+
console.log(import_chalk11.default.cyan(` MCP proxied:`));
|
|
10064
10810
|
for (const entry of wrapped) {
|
|
10065
|
-
console.log(
|
|
10811
|
+
console.log(import_chalk11.default.gray(` \u2022 ${entry}`));
|
|
10066
10812
|
}
|
|
10067
10813
|
} else {
|
|
10068
|
-
console.log(
|
|
10814
|
+
console.log(import_chalk11.default.gray(` MCP proxied: none`));
|
|
10069
10815
|
}
|
|
10070
10816
|
}
|
|
10071
10817
|
function registerStatusCommand(program2) {
|
|
@@ -10076,58 +10822,58 @@ function registerStatusCommand(program2) {
|
|
|
10076
10822
|
const settings = mergedConfig.settings;
|
|
10077
10823
|
console.log("");
|
|
10078
10824
|
if (creds && settings.approvers.cloud) {
|
|
10079
|
-
console.log(
|
|
10825
|
+
console.log(import_chalk11.default.green(" \u25CF Agent mode") + import_chalk11.default.gray(" \u2014 cloud team policy enforced"));
|
|
10080
10826
|
} else if (creds && !settings.approvers.cloud) {
|
|
10081
10827
|
console.log(
|
|
10082
|
-
|
|
10828
|
+
import_chalk11.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 all decisions stay on this machine")
|
|
10083
10829
|
);
|
|
10084
10830
|
} else {
|
|
10085
10831
|
console.log(
|
|
10086
|
-
|
|
10832
|
+
import_chalk11.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk11.default.gray(" \u2014 no API key (Local rules only)")
|
|
10087
10833
|
);
|
|
10088
10834
|
}
|
|
10089
10835
|
console.log("");
|
|
10090
10836
|
if (daemonRunning) {
|
|
10091
10837
|
console.log(
|
|
10092
|
-
|
|
10838
|
+
import_chalk11.default.green(" \u25CF Daemon running") + import_chalk11.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
|
|
10093
10839
|
);
|
|
10094
10840
|
} else {
|
|
10095
|
-
console.log(
|
|
10841
|
+
console.log(import_chalk11.default.gray(" \u25CB Daemon stopped"));
|
|
10096
10842
|
}
|
|
10097
10843
|
if (settings.enableUndo) {
|
|
10098
10844
|
console.log(
|
|
10099
|
-
|
|
10845
|
+
import_chalk11.default.magenta(" \u25CF Undo Engine") + import_chalk11.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
|
|
10100
10846
|
);
|
|
10101
10847
|
}
|
|
10102
10848
|
console.log("");
|
|
10103
|
-
const modeLabel = settings.mode === "audit" ?
|
|
10849
|
+
const modeLabel = settings.mode === "audit" ? import_chalk11.default.blue("audit") : settings.mode === "strict" ? import_chalk11.default.red("strict") : import_chalk11.default.white("standard");
|
|
10104
10850
|
console.log(` Mode: ${modeLabel}`);
|
|
10105
|
-
const projectConfig =
|
|
10106
|
-
const globalConfig =
|
|
10851
|
+
const projectConfig = import_path27.default.join(process.cwd(), "node9.config.json");
|
|
10852
|
+
const globalConfig = import_path27.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
10107
10853
|
console.log(
|
|
10108
|
-
` Local: ${
|
|
10854
|
+
` Local: ${import_fs25.default.existsSync(projectConfig) ? import_chalk11.default.green("Active (node9.config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10109
10855
|
);
|
|
10110
10856
|
console.log(
|
|
10111
|
-
` Global: ${
|
|
10857
|
+
` Global: ${import_fs25.default.existsSync(globalConfig) ? import_chalk11.default.green("Active (~/.node9/config.json)") : import_chalk11.default.gray("Not present")}`
|
|
10112
10858
|
);
|
|
10113
10859
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
10114
10860
|
console.log(
|
|
10115
|
-
` Sandbox: ${
|
|
10861
|
+
` Sandbox: ${import_chalk11.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
10116
10862
|
);
|
|
10117
10863
|
}
|
|
10118
|
-
const homeDir2 =
|
|
10864
|
+
const homeDir2 = import_os21.default.homedir();
|
|
10119
10865
|
const claudeSettings = readJson2(
|
|
10120
|
-
|
|
10866
|
+
import_path27.default.join(homeDir2, ".claude", "settings.json")
|
|
10121
10867
|
);
|
|
10122
|
-
const claudeConfig = readJson2(
|
|
10868
|
+
const claudeConfig = readJson2(import_path27.default.join(homeDir2, ".claude.json"));
|
|
10123
10869
|
const geminiSettings = readJson2(
|
|
10124
|
-
|
|
10870
|
+
import_path27.default.join(homeDir2, ".gemini", "settings.json")
|
|
10125
10871
|
);
|
|
10126
|
-
const cursorConfig = readJson2(
|
|
10872
|
+
const cursorConfig = readJson2(import_path27.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
10127
10873
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
10128
10874
|
if (agentFound) {
|
|
10129
10875
|
console.log("");
|
|
10130
|
-
console.log(
|
|
10876
|
+
console.log(import_chalk11.default.bold(" Agent Wiring:"));
|
|
10131
10877
|
console.log("");
|
|
10132
10878
|
if (claudeSettings || claudeConfig) {
|
|
10133
10879
|
const preHook = claudeSettings?.hooks?.PreToolUse?.some(
|
|
@@ -10173,7 +10919,7 @@ function registerStatusCommand(program2) {
|
|
|
10173
10919
|
const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
|
|
10174
10920
|
console.log("");
|
|
10175
10921
|
console.log(
|
|
10176
|
-
|
|
10922
|
+
import_chalk11.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk11.default.gray(" \u2014 all tool calls allowed")
|
|
10177
10923
|
);
|
|
10178
10924
|
}
|
|
10179
10925
|
console.log("");
|
|
@@ -10181,10 +10927,10 @@ function registerStatusCommand(program2) {
|
|
|
10181
10927
|
}
|
|
10182
10928
|
|
|
10183
10929
|
// src/cli/commands/init.ts
|
|
10184
|
-
var
|
|
10185
|
-
var
|
|
10186
|
-
var
|
|
10187
|
-
var
|
|
10930
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
10931
|
+
var import_fs26 = __toESM(require("fs"));
|
|
10932
|
+
var import_path28 = __toESM(require("path"));
|
|
10933
|
+
var import_os22 = __toESM(require("os"));
|
|
10188
10934
|
var import_https2 = __toESM(require("https"));
|
|
10189
10935
|
init_core();
|
|
10190
10936
|
init_shields();
|
|
@@ -10220,7 +10966,7 @@ function fireTelemetryPing(agents) {
|
|
|
10220
10966
|
}
|
|
10221
10967
|
function registerInitCommand(program2) {
|
|
10222
10968
|
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) => {
|
|
10223
|
-
console.log(
|
|
10969
|
+
console.log(import_chalk12.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
10224
10970
|
let chosenMode = options.mode.toLowerCase();
|
|
10225
10971
|
if (!["standard", "strict", "audit"].includes(chosenMode)) {
|
|
10226
10972
|
chosenMode = DEFAULT_CONFIG.settings.mode;
|
|
@@ -10239,37 +10985,37 @@ function registerInitCommand(program2) {
|
|
|
10239
10985
|
const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
|
|
10240
10986
|
if (hasNewShields) writeActiveShields(merged);
|
|
10241
10987
|
} catch (err2) {
|
|
10242
|
-
console.log(
|
|
10988
|
+
console.log(import_chalk12.default.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
|
|
10243
10989
|
}
|
|
10244
10990
|
}
|
|
10245
10991
|
console.log("");
|
|
10246
10992
|
}
|
|
10247
|
-
const configPath =
|
|
10248
|
-
if (
|
|
10993
|
+
const configPath = import_path28.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
10994
|
+
if (import_fs26.default.existsSync(configPath) && !options.force) {
|
|
10249
10995
|
try {
|
|
10250
|
-
const existing = JSON.parse(
|
|
10996
|
+
const existing = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
|
|
10251
10997
|
const settings = existing.settings ?? {};
|
|
10252
10998
|
if (settings.mode !== chosenMode) {
|
|
10253
10999
|
settings.mode = chosenMode;
|
|
10254
11000
|
existing.settings = settings;
|
|
10255
|
-
|
|
10256
|
-
console.log(
|
|
11001
|
+
import_fs26.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
11002
|
+
console.log(import_chalk12.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
10257
11003
|
} else {
|
|
10258
|
-
console.log(
|
|
11004
|
+
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
10259
11005
|
}
|
|
10260
11006
|
} catch {
|
|
10261
|
-
console.log(
|
|
11007
|
+
console.log(import_chalk12.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
10262
11008
|
}
|
|
10263
11009
|
} else {
|
|
10264
11010
|
const configToSave = {
|
|
10265
11011
|
...DEFAULT_CONFIG,
|
|
10266
11012
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
10267
11013
|
};
|
|
10268
|
-
const dir =
|
|
10269
|
-
if (!
|
|
10270
|
-
|
|
10271
|
-
console.log(
|
|
10272
|
-
console.log(
|
|
11014
|
+
const dir = import_path28.default.dirname(configPath);
|
|
11015
|
+
if (!import_fs26.default.existsSync(dir)) import_fs26.default.mkdirSync(dir, { recursive: true });
|
|
11016
|
+
import_fs26.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
11017
|
+
console.log(import_chalk12.default.green(`\u2705 Config created: ${configPath}`));
|
|
11018
|
+
console.log(import_chalk12.default.gray(` Mode: ${chosenMode}`));
|
|
10273
11019
|
}
|
|
10274
11020
|
if (options.skipSetup) return;
|
|
10275
11021
|
console.log("");
|
|
@@ -10279,18 +11025,18 @@ function registerInitCommand(program2) {
|
|
|
10279
11025
|
);
|
|
10280
11026
|
if (found.length === 0) {
|
|
10281
11027
|
console.log(
|
|
10282
|
-
|
|
11028
|
+
import_chalk12.default.gray("No AI agents detected. Install Claude Code, Gemini CLI, Cursor, or Codex")
|
|
10283
11029
|
);
|
|
10284
|
-
console.log(
|
|
11030
|
+
console.log(import_chalk12.default.gray("then run: node9 addto <claude|gemini|cursor|codex>"));
|
|
10285
11031
|
return;
|
|
10286
11032
|
}
|
|
10287
|
-
console.log(
|
|
11033
|
+
console.log(import_chalk12.default.bold("Detected agents:"));
|
|
10288
11034
|
for (const agent of found) {
|
|
10289
|
-
console.log(
|
|
11035
|
+
console.log(import_chalk12.default.green(` \u2713 ${agent}`));
|
|
10290
11036
|
}
|
|
10291
11037
|
console.log("");
|
|
10292
11038
|
for (const agent of found) {
|
|
10293
|
-
console.log(
|
|
11039
|
+
console.log(import_chalk12.default.bold(`Wiring ${agent}...`));
|
|
10294
11040
|
if (agent === "claude") await setupClaude();
|
|
10295
11041
|
else if (agent === "gemini") await setupGemini();
|
|
10296
11042
|
else if (agent === "cursor") await setupCursor();
|
|
@@ -10307,26 +11053,26 @@ function registerInitCommand(program2) {
|
|
|
10307
11053
|
console.log("");
|
|
10308
11054
|
}
|
|
10309
11055
|
const agentList = found.join(", ");
|
|
10310
|
-
console.log(
|
|
11056
|
+
console.log(import_chalk12.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
10311
11057
|
console.log("");
|
|
10312
|
-
console.log(
|
|
10313
|
-
console.log(
|
|
11058
|
+
console.log(import_chalk12.default.white(" Watch live: ") + import_chalk12.default.cyan("node9 tail"));
|
|
11059
|
+
console.log(import_chalk12.default.white(" Local UI: ") + import_chalk12.default.cyan("node9 daemon --openui"));
|
|
10314
11060
|
console.log("");
|
|
10315
|
-
console.log(
|
|
11061
|
+
console.log(import_chalk12.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10316
11062
|
console.log(
|
|
10317
|
-
|
|
11063
|
+
import_chalk12.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk12.default.cyan.bold("https://node9.ai")
|
|
10318
11064
|
);
|
|
10319
|
-
console.log(
|
|
11065
|
+
console.log(import_chalk12.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10320
11066
|
});
|
|
10321
11067
|
}
|
|
10322
11068
|
|
|
10323
11069
|
// src/cli/commands/undo.ts
|
|
10324
|
-
var
|
|
10325
|
-
var
|
|
11070
|
+
var import_path29 = __toESM(require("path"));
|
|
11071
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
10326
11072
|
|
|
10327
11073
|
// src/tui/undo-navigator.ts
|
|
10328
11074
|
var import_readline2 = __toESM(require("readline"));
|
|
10329
|
-
var
|
|
11075
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
10330
11076
|
var RESET = "\x1B[0m";
|
|
10331
11077
|
var BOLD = "\x1B[1m";
|
|
10332
11078
|
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
@@ -10344,15 +11090,15 @@ function renderDiff(raw) {
|
|
|
10344
11090
|
);
|
|
10345
11091
|
for (const line of lines) {
|
|
10346
11092
|
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
10347
|
-
process.stdout.write(
|
|
11093
|
+
process.stdout.write(import_chalk13.default.bold(line) + "\n");
|
|
10348
11094
|
} else if (line.startsWith("+")) {
|
|
10349
|
-
process.stdout.write(
|
|
11095
|
+
process.stdout.write(import_chalk13.default.green(line) + "\n");
|
|
10350
11096
|
} else if (line.startsWith("-")) {
|
|
10351
|
-
process.stdout.write(
|
|
11097
|
+
process.stdout.write(import_chalk13.default.red(line) + "\n");
|
|
10352
11098
|
} else if (line.startsWith("@@")) {
|
|
10353
|
-
process.stdout.write(
|
|
11099
|
+
process.stdout.write(import_chalk13.default.cyan(line) + "\n");
|
|
10354
11100
|
} else {
|
|
10355
|
-
process.stdout.write(
|
|
11101
|
+
process.stdout.write(import_chalk13.default.gray(line) + "\n");
|
|
10356
11102
|
}
|
|
10357
11103
|
}
|
|
10358
11104
|
}
|
|
@@ -10371,23 +11117,23 @@ function render(entries, idx) {
|
|
|
10371
11117
|
const step = idx + 1;
|
|
10372
11118
|
process.stdout.write(CLEAR_SCREEN);
|
|
10373
11119
|
process.stdout.write(
|
|
10374
|
-
|
|
11120
|
+
import_chalk13.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk13.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk13.default.gray(
|
|
10375
11121
|
` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
|
|
10376
11122
|
) : "") + "\n\n"
|
|
10377
11123
|
);
|
|
10378
11124
|
process.stdout.write(
|
|
10379
|
-
` ${BOLD}Tool:${RESET} ${
|
|
11125
|
+
` ${BOLD}Tool:${RESET} ${import_chalk13.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk13.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
|
|
10380
11126
|
);
|
|
10381
|
-
process.stdout.write(` ${BOLD}When:${RESET} ${
|
|
11127
|
+
process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk13.default.gray(formatAge(entry.timestamp))}
|
|
10382
11128
|
`);
|
|
10383
|
-
process.stdout.write(` ${BOLD}Dir: ${RESET} ${
|
|
11129
|
+
process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk13.default.gray(entry.cwd)}
|
|
10384
11130
|
`);
|
|
10385
11131
|
if (entry.files && entry.files.length > 0) {
|
|
10386
|
-
process.stdout.write(` ${BOLD}Files:${RESET} ${
|
|
11132
|
+
process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk13.default.gray(entry.files.join(", "))}
|
|
10387
11133
|
`);
|
|
10388
11134
|
}
|
|
10389
11135
|
if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
|
|
10390
|
-
process.stdout.write(
|
|
11136
|
+
process.stdout.write(import_chalk13.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
|
|
10391
11137
|
}
|
|
10392
11138
|
process.stdout.write("\n");
|
|
10393
11139
|
const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
|
|
@@ -10395,12 +11141,12 @@ function render(entries, idx) {
|
|
|
10395
11141
|
renderDiff(diff);
|
|
10396
11142
|
} else {
|
|
10397
11143
|
process.stdout.write(
|
|
10398
|
-
|
|
11144
|
+
import_chalk13.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
|
|
10399
11145
|
);
|
|
10400
11146
|
}
|
|
10401
11147
|
process.stdout.write("\n");
|
|
10402
11148
|
process.stdout.write(
|
|
10403
|
-
|
|
11149
|
+
import_chalk13.default.gray(" ") + (idx < total - 1 ? import_chalk13.default.white("[\u2190] older") : import_chalk13.default.gray("[\u2190] older")) + import_chalk13.default.gray(" ") + (idx > 0 ? import_chalk13.default.white("[\u2192] newer") : import_chalk13.default.gray("[\u2192] newer")) + import_chalk13.default.gray(" ") + import_chalk13.default.green("[\u21B5] restore here") + import_chalk13.default.gray(" ") + import_chalk13.default.yellow("[s] session start") + import_chalk13.default.gray(" ") + import_chalk13.default.gray("[q] quit") + "\n"
|
|
10404
11150
|
);
|
|
10405
11151
|
}
|
|
10406
11152
|
async function runUndoNavigator(entries) {
|
|
@@ -10454,19 +11200,19 @@ async function runUndoNavigator(entries) {
|
|
|
10454
11200
|
cleanup();
|
|
10455
11201
|
process.stdout.write(CLEAR_SCREEN);
|
|
10456
11202
|
const entry = display[idx];
|
|
10457
|
-
process.stdout.write(
|
|
11203
|
+
process.stdout.write(import_chalk13.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
|
|
10458
11204
|
if (applyUndo(entry.hash, entry.cwd)) {
|
|
10459
|
-
process.stdout.write(
|
|
11205
|
+
process.stdout.write(import_chalk13.default.green("\u2705 Reverted successfully.\n\n"));
|
|
10460
11206
|
resolve({ restored: true });
|
|
10461
11207
|
} else {
|
|
10462
|
-
process.stdout.write(
|
|
11208
|
+
process.stdout.write(import_chalk13.default.red("\u274C Undo failed.\n\n"));
|
|
10463
11209
|
resolve({ restored: false });
|
|
10464
11210
|
}
|
|
10465
11211
|
} else if (name === "q" || key?.ctrl && name === "c") {
|
|
10466
11212
|
done = true;
|
|
10467
11213
|
cleanup();
|
|
10468
11214
|
process.stdout.write(CLEAR_SCREEN);
|
|
10469
|
-
process.stdout.write(
|
|
11215
|
+
process.stdout.write(import_chalk13.default.gray("\nCancelled.\n\n"));
|
|
10470
11216
|
resolve({ restored: false });
|
|
10471
11217
|
}
|
|
10472
11218
|
};
|
|
@@ -10480,7 +11226,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
10480
11226
|
let dir = startDir;
|
|
10481
11227
|
while (true) {
|
|
10482
11228
|
if (cwds.has(dir)) return dir;
|
|
10483
|
-
const parent =
|
|
11229
|
+
const parent = import_path29.default.dirname(dir);
|
|
10484
11230
|
if (parent === dir) return null;
|
|
10485
11231
|
dir = parent;
|
|
10486
11232
|
}
|
|
@@ -10502,39 +11248,39 @@ function registerUndoCommand(program2) {
|
|
|
10502
11248
|
if (history.length === 0) {
|
|
10503
11249
|
if (!options.all && allHistory.length > 0) {
|
|
10504
11250
|
console.log(
|
|
10505
|
-
|
|
11251
|
+
import_chalk14.default.yellow(
|
|
10506
11252
|
`
|
|
10507
11253
|
\u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
|
|
10508
|
-
Run ${
|
|
11254
|
+
Run ${import_chalk14.default.cyan("node9 undo --all")} to see snapshots from all projects.
|
|
10509
11255
|
`
|
|
10510
11256
|
)
|
|
10511
11257
|
);
|
|
10512
11258
|
} else {
|
|
10513
|
-
console.log(
|
|
11259
|
+
console.log(import_chalk14.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
|
|
10514
11260
|
}
|
|
10515
11261
|
return;
|
|
10516
11262
|
}
|
|
10517
11263
|
if (options.list) {
|
|
10518
|
-
console.log(
|
|
11264
|
+
console.log(import_chalk14.default.magenta.bold("\n\u23EA Snapshot History\n"));
|
|
10519
11265
|
console.log(
|
|
10520
|
-
|
|
11266
|
+
import_chalk14.default.gray(
|
|
10521
11267
|
` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
|
|
10522
11268
|
)
|
|
10523
11269
|
);
|
|
10524
|
-
console.log(
|
|
11270
|
+
console.log(import_chalk14.default.gray(" " + "\u2500".repeat(80)));
|
|
10525
11271
|
const display = [...history].reverse();
|
|
10526
11272
|
let prevTs = null;
|
|
10527
11273
|
for (let i = 0; i < display.length; i++) {
|
|
10528
11274
|
const e = display[i];
|
|
10529
11275
|
const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
|
|
10530
|
-
if (isGap) console.log(
|
|
11276
|
+
if (isGap) console.log(import_chalk14.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
|
|
10531
11277
|
const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
|
|
10532
11278
|
const tool = e.tool.slice(0, 8).padEnd(8);
|
|
10533
11279
|
const when = formatAge2(e.timestamp).padEnd(10);
|
|
10534
11280
|
const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
|
|
10535
11281
|
console.log(
|
|
10536
|
-
|
|
10537
|
-
` ${String(i + 1).padEnd(3)} ${label} ${
|
|
11282
|
+
import_chalk14.default.white(
|
|
11283
|
+
` ${String(i + 1).padEnd(3)} ${label} ${import_chalk14.default.cyan(tool)} ${import_chalk14.default.gray(when)} ${import_chalk14.default.gray(dir)}`
|
|
10538
11284
|
)
|
|
10539
11285
|
);
|
|
10540
11286
|
prevTs = e.timestamp;
|
|
@@ -10547,7 +11293,7 @@ function registerUndoCommand(program2) {
|
|
|
10547
11293
|
const idx = history.length - steps;
|
|
10548
11294
|
if (idx < 0) {
|
|
10549
11295
|
console.log(
|
|
10550
|
-
|
|
11296
|
+
import_chalk14.default.yellow(
|
|
10551
11297
|
`
|
|
10552
11298
|
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
10553
11299
|
`
|
|
@@ -10558,47 +11304,47 @@ function registerUndoCommand(program2) {
|
|
|
10558
11304
|
const snapshot = history[idx];
|
|
10559
11305
|
const ageStr = formatAge2(snapshot.timestamp);
|
|
10560
11306
|
console.log(
|
|
10561
|
-
|
|
11307
|
+
import_chalk14.default.magenta.bold(`
|
|
10562
11308
|
\u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
|
|
10563
11309
|
);
|
|
10564
11310
|
console.log(
|
|
10565
|
-
|
|
10566
|
-
` Tool: ${
|
|
11311
|
+
import_chalk14.default.white(
|
|
11312
|
+
` Tool: ${import_chalk14.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk14.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
10567
11313
|
)
|
|
10568
11314
|
);
|
|
10569
|
-
console.log(
|
|
10570
|
-
console.log(
|
|
11315
|
+
console.log(import_chalk14.default.white(` When: ${import_chalk14.default.gray(ageStr)}`));
|
|
11316
|
+
console.log(import_chalk14.default.white(` Dir: ${import_chalk14.default.gray(snapshot.cwd)}`));
|
|
10571
11317
|
if (steps > 1)
|
|
10572
11318
|
console.log(
|
|
10573
|
-
|
|
11319
|
+
import_chalk14.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
|
|
10574
11320
|
);
|
|
10575
11321
|
console.log("");
|
|
10576
11322
|
const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
|
|
10577
11323
|
if (diff) {
|
|
10578
11324
|
const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
|
|
10579
11325
|
for (const line of lines) {
|
|
10580
|
-
if (line.startsWith("+++") || line.startsWith("---")) console.log(
|
|
10581
|
-
else if (line.startsWith("+")) console.log(
|
|
10582
|
-
else if (line.startsWith("-")) console.log(
|
|
10583
|
-
else if (line.startsWith("@@")) console.log(
|
|
10584
|
-
else console.log(
|
|
11326
|
+
if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk14.default.bold(line));
|
|
11327
|
+
else if (line.startsWith("+")) console.log(import_chalk14.default.green(line));
|
|
11328
|
+
else if (line.startsWith("-")) console.log(import_chalk14.default.red(line));
|
|
11329
|
+
else if (line.startsWith("@@")) console.log(import_chalk14.default.cyan(line));
|
|
11330
|
+
else console.log(import_chalk14.default.gray(line));
|
|
10585
11331
|
}
|
|
10586
11332
|
console.log("");
|
|
10587
11333
|
} else {
|
|
10588
11334
|
console.log(
|
|
10589
|
-
|
|
11335
|
+
import_chalk14.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
|
|
10590
11336
|
);
|
|
10591
11337
|
}
|
|
10592
11338
|
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
10593
11339
|
const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
|
|
10594
11340
|
if (proceed) {
|
|
10595
11341
|
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
10596
|
-
console.log(
|
|
11342
|
+
console.log(import_chalk14.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10597
11343
|
} else {
|
|
10598
|
-
console.error(
|
|
11344
|
+
console.error(import_chalk14.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
10599
11345
|
}
|
|
10600
11346
|
} else {
|
|
10601
|
-
console.log(
|
|
11347
|
+
console.log(import_chalk14.default.gray("\nCancelled.\n"));
|
|
10602
11348
|
}
|
|
10603
11349
|
return;
|
|
10604
11350
|
}
|
|
@@ -10607,7 +11353,7 @@ function registerUndoCommand(program2) {
|
|
|
10607
11353
|
}
|
|
10608
11354
|
|
|
10609
11355
|
// src/cli/commands/watch.ts
|
|
10610
|
-
var
|
|
11356
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
10611
11357
|
var import_child_process12 = require("child_process");
|
|
10612
11358
|
init_daemon();
|
|
10613
11359
|
function registerWatchCommand(program2) {
|
|
@@ -10624,7 +11370,7 @@ function registerWatchCommand(program2) {
|
|
|
10624
11370
|
throw new Error("not running");
|
|
10625
11371
|
}
|
|
10626
11372
|
} catch {
|
|
10627
|
-
console.error(
|
|
11373
|
+
console.error(import_chalk15.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
10628
11374
|
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10629
11375
|
detached: true,
|
|
10630
11376
|
stdio: "ignore",
|
|
@@ -10646,12 +11392,12 @@ function registerWatchCommand(program2) {
|
|
|
10646
11392
|
}
|
|
10647
11393
|
}
|
|
10648
11394
|
if (!ready) {
|
|
10649
|
-
console.error(
|
|
11395
|
+
console.error(import_chalk15.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
10650
11396
|
process.exit(1);
|
|
10651
11397
|
}
|
|
10652
11398
|
}
|
|
10653
11399
|
console.error(
|
|
10654
|
-
|
|
11400
|
+
import_chalk15.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk15.default.dim(` \u2192 localhost:${port}`) + import_chalk15.default.dim(
|
|
10655
11401
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
10656
11402
|
)
|
|
10657
11403
|
);
|
|
@@ -10660,7 +11406,7 @@ function registerWatchCommand(program2) {
|
|
|
10660
11406
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
10661
11407
|
});
|
|
10662
11408
|
if (result.error) {
|
|
10663
|
-
console.error(
|
|
11409
|
+
console.error(import_chalk15.default.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
10664
11410
|
process.exit(1);
|
|
10665
11411
|
}
|
|
10666
11412
|
process.exit(result.status ?? 0);
|
|
@@ -10669,19 +11415,19 @@ function registerWatchCommand(program2) {
|
|
|
10669
11415
|
|
|
10670
11416
|
// src/mcp-gateway/index.ts
|
|
10671
11417
|
var import_readline3 = __toESM(require("readline"));
|
|
10672
|
-
var
|
|
11418
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
10673
11419
|
var import_child_process13 = require("child_process");
|
|
10674
11420
|
var import_execa3 = require("execa");
|
|
10675
11421
|
init_orchestrator();
|
|
10676
11422
|
init_provenance();
|
|
10677
11423
|
|
|
10678
11424
|
// src/mcp-pin.ts
|
|
10679
|
-
var
|
|
10680
|
-
var
|
|
10681
|
-
var
|
|
10682
|
-
var
|
|
11425
|
+
var import_fs27 = __toESM(require("fs"));
|
|
11426
|
+
var import_path30 = __toESM(require("path"));
|
|
11427
|
+
var import_os23 = __toESM(require("os"));
|
|
11428
|
+
var import_crypto9 = __toESM(require("crypto"));
|
|
10683
11429
|
function getPinsFilePath() {
|
|
10684
|
-
return
|
|
11430
|
+
return import_path30.default.join(import_os23.default.homedir(), ".node9", "mcp-pins.json");
|
|
10685
11431
|
}
|
|
10686
11432
|
function hashToolDefinitions(tools) {
|
|
10687
11433
|
const sorted = [...tools].sort((a, b) => {
|
|
@@ -10690,15 +11436,15 @@ function hashToolDefinitions(tools) {
|
|
|
10690
11436
|
return nameA.localeCompare(nameB);
|
|
10691
11437
|
});
|
|
10692
11438
|
const canonical = JSON.stringify(sorted);
|
|
10693
|
-
return
|
|
11439
|
+
return import_crypto9.default.createHash("sha256").update(canonical).digest("hex");
|
|
10694
11440
|
}
|
|
10695
11441
|
function getServerKey(upstreamCommand) {
|
|
10696
|
-
return
|
|
11442
|
+
return import_crypto9.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
10697
11443
|
}
|
|
10698
11444
|
function readMcpPinsSafe() {
|
|
10699
11445
|
const filePath = getPinsFilePath();
|
|
10700
11446
|
try {
|
|
10701
|
-
const raw =
|
|
11447
|
+
const raw = import_fs27.default.readFileSync(filePath, "utf-8");
|
|
10702
11448
|
if (!raw.trim()) {
|
|
10703
11449
|
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10704
11450
|
}
|
|
@@ -10722,10 +11468,10 @@ function readMcpPins() {
|
|
|
10722
11468
|
}
|
|
10723
11469
|
function writeMcpPins(data) {
|
|
10724
11470
|
const filePath = getPinsFilePath();
|
|
10725
|
-
|
|
10726
|
-
const tmp = `${filePath}.${
|
|
10727
|
-
|
|
10728
|
-
|
|
11471
|
+
import_fs27.default.mkdirSync(import_path30.default.dirname(filePath), { recursive: true });
|
|
11472
|
+
const tmp = `${filePath}.${import_crypto9.default.randomBytes(6).toString("hex")}.tmp`;
|
|
11473
|
+
import_fs27.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
11474
|
+
import_fs27.default.renameSync(tmp, filePath);
|
|
10729
11475
|
}
|
|
10730
11476
|
function checkPin(serverKey, currentHash) {
|
|
10731
11477
|
const result = readMcpPinsSafe();
|
|
@@ -10817,13 +11563,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10817
11563
|
const prov = checkProvenance(executable);
|
|
10818
11564
|
if (prov.trustLevel === "suspect") {
|
|
10819
11565
|
console.error(
|
|
10820
|
-
|
|
11566
|
+
import_chalk16.default.red(
|
|
10821
11567
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
10822
11568
|
)
|
|
10823
11569
|
);
|
|
10824
|
-
console.error(
|
|
11570
|
+
console.error(import_chalk16.default.red(" Verify this binary is trusted before proceeding."));
|
|
10825
11571
|
}
|
|
10826
|
-
console.error(
|
|
11572
|
+
console.error(import_chalk16.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
10827
11573
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
10828
11574
|
"NODE_OPTIONS",
|
|
10829
11575
|
"NODE_PATH",
|
|
@@ -10926,10 +11672,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10926
11672
|
mcpServer
|
|
10927
11673
|
});
|
|
10928
11674
|
if (!result.approved) {
|
|
10929
|
-
console.error(
|
|
11675
|
+
console.error(import_chalk16.default.red(`
|
|
10930
11676
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
10931
|
-
console.error(
|
|
10932
|
-
console.error(
|
|
11677
|
+
console.error(import_chalk16.default.gray(` Tool: ${toolName}`));
|
|
11678
|
+
console.error(import_chalk16.default.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
10933
11679
|
`));
|
|
10934
11680
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
10935
11681
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -11008,7 +11754,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11008
11754
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
11009
11755
|
pinState = "validated";
|
|
11010
11756
|
console.error(
|
|
11011
|
-
|
|
11757
|
+
import_chalk16.default.green(
|
|
11012
11758
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
11013
11759
|
)
|
|
11014
11760
|
);
|
|
@@ -11021,11 +11767,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11021
11767
|
} else if (pinStatus === "corrupt") {
|
|
11022
11768
|
pinState = "quarantined";
|
|
11023
11769
|
console.error(
|
|
11024
|
-
|
|
11770
|
+
import_chalk16.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
11025
11771
|
);
|
|
11026
|
-
console.error(
|
|
11772
|
+
console.error(import_chalk16.default.red(" Tool calls are blocked until the pin file is repaired."));
|
|
11027
11773
|
console.error(
|
|
11028
|
-
|
|
11774
|
+
import_chalk16.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
11029
11775
|
`)
|
|
11030
11776
|
);
|
|
11031
11777
|
const errorResponse = {
|
|
@@ -11042,13 +11788,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
11042
11788
|
} else {
|
|
11043
11789
|
pinState = "quarantined";
|
|
11044
11790
|
console.error(
|
|
11045
|
-
|
|
11791
|
+
import_chalk16.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
11046
11792
|
);
|
|
11047
11793
|
console.error(
|
|
11048
|
-
|
|
11794
|
+
import_chalk16.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
11049
11795
|
);
|
|
11050
|
-
console.error(
|
|
11051
|
-
console.error(
|
|
11796
|
+
console.error(import_chalk16.default.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
11797
|
+
console.error(import_chalk16.default.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
11052
11798
|
`));
|
|
11053
11799
|
const errorResponse = {
|
|
11054
11800
|
jsonrpc: "2.0",
|
|
@@ -11097,9 +11843,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
11097
11843
|
|
|
11098
11844
|
// src/mcp-server/index.ts
|
|
11099
11845
|
var import_readline4 = __toESM(require("readline"));
|
|
11100
|
-
var
|
|
11101
|
-
var
|
|
11102
|
-
var
|
|
11846
|
+
var import_fs28 = __toESM(require("fs"));
|
|
11847
|
+
var import_os24 = __toESM(require("os"));
|
|
11848
|
+
var import_path31 = __toESM(require("path"));
|
|
11103
11849
|
init_core();
|
|
11104
11850
|
init_daemon();
|
|
11105
11851
|
init_shields();
|
|
@@ -11274,13 +12020,13 @@ function handleStatus() {
|
|
|
11274
12020
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
11275
12021
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
11276
12022
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
11277
|
-
const projectConfig =
|
|
11278
|
-
const globalConfig =
|
|
12023
|
+
const projectConfig = import_path31.default.join(process.cwd(), "node9.config.json");
|
|
12024
|
+
const globalConfig = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
11279
12025
|
lines.push(
|
|
11280
|
-
`Project config (node9.config.json): ${
|
|
12026
|
+
`Project config (node9.config.json): ${import_fs28.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
11281
12027
|
);
|
|
11282
12028
|
lines.push(
|
|
11283
|
-
`Global config (~/.node9/config.json): ${
|
|
12029
|
+
`Global config (~/.node9/config.json): ${import_fs28.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
11284
12030
|
);
|
|
11285
12031
|
return lines.join("\n");
|
|
11286
12032
|
}
|
|
@@ -11354,21 +12100,21 @@ function handleShieldDisable(args) {
|
|
|
11354
12100
|
writeActiveShields(active.filter((s) => s !== name));
|
|
11355
12101
|
return `Shield "${name}" disabled.`;
|
|
11356
12102
|
}
|
|
11357
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
12103
|
+
var GLOBAL_CONFIG_PATH2 = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
11358
12104
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
11359
12105
|
function readGlobalConfigRaw() {
|
|
11360
12106
|
try {
|
|
11361
|
-
if (
|
|
11362
|
-
return JSON.parse(
|
|
12107
|
+
if (import_fs28.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
12108
|
+
return JSON.parse(import_fs28.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
11363
12109
|
}
|
|
11364
12110
|
} catch {
|
|
11365
12111
|
}
|
|
11366
12112
|
return {};
|
|
11367
12113
|
}
|
|
11368
12114
|
function writeGlobalConfigRaw(data) {
|
|
11369
|
-
const dir =
|
|
11370
|
-
if (!
|
|
11371
|
-
|
|
12115
|
+
const dir = import_path31.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
12116
|
+
if (!import_fs28.default.existsSync(dir)) import_fs28.default.mkdirSync(dir, { recursive: true });
|
|
12117
|
+
import_fs28.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
11372
12118
|
}
|
|
11373
12119
|
function handleApproverList() {
|
|
11374
12120
|
const config = getConfig();
|
|
@@ -11411,9 +12157,9 @@ function handleApproverSet(args) {
|
|
|
11411
12157
|
}
|
|
11412
12158
|
function handleAuditGet(args) {
|
|
11413
12159
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
11414
|
-
const auditPath =
|
|
11415
|
-
if (!
|
|
11416
|
-
const lines =
|
|
12160
|
+
const auditPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "audit.log");
|
|
12161
|
+
if (!import_fs28.default.existsSync(auditPath)) return "No audit log found.";
|
|
12162
|
+
const lines = import_fs28.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
11417
12163
|
const recent = lines.slice(-limit);
|
|
11418
12164
|
const entries = recent.map((line) => {
|
|
11419
12165
|
try {
|
|
@@ -11600,7 +12346,7 @@ function registerMcpServerCommand(program2) {
|
|
|
11600
12346
|
}
|
|
11601
12347
|
|
|
11602
12348
|
// src/cli/commands/trust.ts
|
|
11603
|
-
var
|
|
12349
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
11604
12350
|
init_trusted_hosts();
|
|
11605
12351
|
function isValidHost(host) {
|
|
11606
12352
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
@@ -11611,51 +12357,51 @@ function registerTrustCommand(program2) {
|
|
|
11611
12357
|
const normalized = normalizeHost(host.trim());
|
|
11612
12358
|
if (!isValidHost(normalized)) {
|
|
11613
12359
|
console.error(
|
|
11614
|
-
|
|
12360
|
+
import_chalk17.default.red(`
|
|
11615
12361
|
\u274C Invalid host: "${host}"
|
|
11616
|
-
`) +
|
|
12362
|
+
`) + import_chalk17.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
11617
12363
|
);
|
|
11618
12364
|
process.exit(1);
|
|
11619
12365
|
}
|
|
11620
12366
|
addTrustedHost(normalized);
|
|
11621
|
-
console.log(
|
|
12367
|
+
console.log(import_chalk17.default.green(`
|
|
11622
12368
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
11623
12369
|
console.log(
|
|
11624
|
-
|
|
12370
|
+
import_chalk17.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
11625
12371
|
);
|
|
11626
12372
|
});
|
|
11627
12373
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
11628
12374
|
const normalized = normalizeHost(host.trim());
|
|
11629
12375
|
const removed = removeTrustedHost(normalized);
|
|
11630
12376
|
if (!removed) {
|
|
11631
|
-
console.error(
|
|
12377
|
+
console.error(import_chalk17.default.yellow(`
|
|
11632
12378
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
11633
12379
|
`));
|
|
11634
12380
|
process.exit(1);
|
|
11635
12381
|
}
|
|
11636
|
-
console.log(
|
|
12382
|
+
console.log(import_chalk17.default.green(`
|
|
11637
12383
|
\u2705 ${normalized} removed from trusted hosts.
|
|
11638
12384
|
`));
|
|
11639
12385
|
});
|
|
11640
12386
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
11641
12387
|
const hosts = readTrustedHosts();
|
|
11642
12388
|
if (hosts.length === 0) {
|
|
11643
|
-
console.log(
|
|
11644
|
-
console.log(` Add one: ${
|
|
12389
|
+
console.log(import_chalk17.default.gray("\n No trusted hosts configured.\n"));
|
|
12390
|
+
console.log(` Add one: ${import_chalk17.default.cyan("node9 trust add api.mycompany.com")}
|
|
11645
12391
|
`);
|
|
11646
12392
|
return;
|
|
11647
12393
|
}
|
|
11648
|
-
console.log(
|
|
12394
|
+
console.log(import_chalk17.default.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
11649
12395
|
for (const entry of hosts) {
|
|
11650
12396
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
11651
|
-
console.log(` ${
|
|
12397
|
+
console.log(` ${import_chalk17.default.cyan(entry.host.padEnd(40))} ${import_chalk17.default.gray(`added ${date}`)}`);
|
|
11652
12398
|
}
|
|
11653
12399
|
console.log("");
|
|
11654
12400
|
});
|
|
11655
12401
|
}
|
|
11656
12402
|
|
|
11657
12403
|
// src/cli/commands/mcp-pin.ts
|
|
11658
|
-
var
|
|
12404
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
11659
12405
|
function registerMcpPinCommand(program2) {
|
|
11660
12406
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
11661
12407
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -11663,31 +12409,31 @@ function registerMcpPinCommand(program2) {
|
|
|
11663
12409
|
const result = readMcpPinsSafe();
|
|
11664
12410
|
if (!result.ok) {
|
|
11665
12411
|
if (result.reason === "missing") {
|
|
11666
|
-
console.log(
|
|
12412
|
+
console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
|
|
11667
12413
|
console.log(
|
|
11668
|
-
|
|
12414
|
+
import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11669
12415
|
);
|
|
11670
12416
|
return;
|
|
11671
12417
|
}
|
|
11672
|
-
console.error(
|
|
12418
|
+
console.error(import_chalk18.default.red(`
|
|
11673
12419
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
11674
|
-
console.error(
|
|
12420
|
+
console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11675
12421
|
process.exit(1);
|
|
11676
12422
|
}
|
|
11677
12423
|
const entries = Object.entries(result.pins.servers);
|
|
11678
12424
|
if (entries.length === 0) {
|
|
11679
|
-
console.log(
|
|
12425
|
+
console.log(import_chalk18.default.gray("\nNo MCP servers are pinned yet."));
|
|
11680
12426
|
console.log(
|
|
11681
|
-
|
|
12427
|
+
import_chalk18.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11682
12428
|
);
|
|
11683
12429
|
return;
|
|
11684
12430
|
}
|
|
11685
|
-
console.log(
|
|
12431
|
+
console.log(import_chalk18.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
11686
12432
|
for (const [key, entry] of entries) {
|
|
11687
|
-
console.log(` ${
|
|
11688
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
11689
|
-
console.log(` Hash: ${
|
|
11690
|
-
console.log(` Pinned: ${
|
|
12433
|
+
console.log(` ${import_chalk18.default.cyan(key)} ${import_chalk18.default.gray(entry.label)}`);
|
|
12434
|
+
console.log(` Tools (${entry.toolCount}): ${import_chalk18.default.white(entry.toolNames.join(", "))}`);
|
|
12435
|
+
console.log(` Hash: ${import_chalk18.default.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
12436
|
+
console.log(` Pinned: ${import_chalk18.default.gray(entry.pinnedAt)}`);
|
|
11691
12437
|
console.log("");
|
|
11692
12438
|
}
|
|
11693
12439
|
});
|
|
@@ -11698,55 +12444,55 @@ function registerMcpPinCommand(program2) {
|
|
|
11698
12444
|
try {
|
|
11699
12445
|
pins = readMcpPins();
|
|
11700
12446
|
} catch {
|
|
11701
|
-
console.error(
|
|
11702
|
-
console.error(
|
|
12447
|
+
console.error(import_chalk18.default.red("\n\u274C Pin file is corrupt."));
|
|
12448
|
+
console.error(import_chalk18.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11703
12449
|
process.exit(1);
|
|
11704
12450
|
}
|
|
11705
12451
|
if (!pins.servers[serverKey]) {
|
|
11706
|
-
console.error(
|
|
12452
|
+
console.error(import_chalk18.default.red(`
|
|
11707
12453
|
\u274C No pin found for server key "${serverKey}"
|
|
11708
12454
|
`));
|
|
11709
|
-
console.error(`Run ${
|
|
12455
|
+
console.error(`Run ${import_chalk18.default.cyan("node9 mcp pin list")} to see pinned servers.
|
|
11710
12456
|
`);
|
|
11711
12457
|
process.exit(1);
|
|
11712
12458
|
}
|
|
11713
12459
|
const label = pins.servers[serverKey].label;
|
|
11714
12460
|
removePin(serverKey);
|
|
11715
|
-
console.log(
|
|
11716
|
-
\u{1F513} Pin removed for ${
|
|
11717
|
-
console.log(
|
|
11718
|
-
console.log(
|
|
12461
|
+
console.log(import_chalk18.default.green(`
|
|
12462
|
+
\u{1F513} Pin removed for ${import_chalk18.default.cyan(serverKey)}`));
|
|
12463
|
+
console.log(import_chalk18.default.gray(` Server: ${label}`));
|
|
12464
|
+
console.log(import_chalk18.default.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
11719
12465
|
});
|
|
11720
12466
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
11721
12467
|
const result = readMcpPinsSafe();
|
|
11722
12468
|
if (!result.ok && result.reason === "missing") {
|
|
11723
|
-
console.log(
|
|
12469
|
+
console.log(import_chalk18.default.gray("\nNo pins to clear.\n"));
|
|
11724
12470
|
return;
|
|
11725
12471
|
}
|
|
11726
12472
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
11727
12473
|
clearAllPins();
|
|
11728
|
-
console.log(
|
|
12474
|
+
console.log(import_chalk18.default.green(`
|
|
11729
12475
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
11730
|
-
console.log(
|
|
12476
|
+
console.log(import_chalk18.default.gray(" Next connection to each server will re-pin.\n"));
|
|
11731
12477
|
});
|
|
11732
12478
|
}
|
|
11733
12479
|
|
|
11734
12480
|
// src/cli.ts
|
|
11735
12481
|
var { version } = JSON.parse(
|
|
11736
|
-
|
|
12482
|
+
import_fs31.default.readFileSync(import_path34.default.join(__dirname, "../package.json"), "utf-8")
|
|
11737
12483
|
);
|
|
11738
12484
|
var program = new import_commander.Command();
|
|
11739
12485
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
11740
12486
|
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) => {
|
|
11741
12487
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
11742
|
-
const credPath =
|
|
11743
|
-
if (!
|
|
11744
|
-
|
|
12488
|
+
const credPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "credentials.json");
|
|
12489
|
+
if (!import_fs31.default.existsSync(import_path34.default.dirname(credPath)))
|
|
12490
|
+
import_fs31.default.mkdirSync(import_path34.default.dirname(credPath), { recursive: true });
|
|
11745
12491
|
const profileName = options.profile || "default";
|
|
11746
12492
|
let existingCreds = {};
|
|
11747
12493
|
try {
|
|
11748
|
-
if (
|
|
11749
|
-
const raw = JSON.parse(
|
|
12494
|
+
if (import_fs31.default.existsSync(credPath)) {
|
|
12495
|
+
const raw = JSON.parse(import_fs31.default.readFileSync(credPath, "utf-8"));
|
|
11750
12496
|
if (raw.apiKey) {
|
|
11751
12497
|
existingCreds = {
|
|
11752
12498
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -11758,13 +12504,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11758
12504
|
} catch {
|
|
11759
12505
|
}
|
|
11760
12506
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
11761
|
-
|
|
12507
|
+
import_fs31.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
11762
12508
|
if (profileName === "default") {
|
|
11763
|
-
const configPath =
|
|
12509
|
+
const configPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "config.json");
|
|
11764
12510
|
let config = {};
|
|
11765
12511
|
try {
|
|
11766
|
-
if (
|
|
11767
|
-
config = JSON.parse(
|
|
12512
|
+
if (import_fs31.default.existsSync(configPath))
|
|
12513
|
+
config = JSON.parse(import_fs31.default.readFileSync(configPath, "utf-8"));
|
|
11768
12514
|
} catch {
|
|
11769
12515
|
}
|
|
11770
12516
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -11779,19 +12525,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11779
12525
|
approvers.cloud = false;
|
|
11780
12526
|
}
|
|
11781
12527
|
s.approvers = approvers;
|
|
11782
|
-
if (!
|
|
11783
|
-
|
|
11784
|
-
|
|
12528
|
+
if (!import_fs31.default.existsSync(import_path34.default.dirname(configPath)))
|
|
12529
|
+
import_fs31.default.mkdirSync(import_path34.default.dirname(configPath), { recursive: true });
|
|
12530
|
+
import_fs31.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
11785
12531
|
}
|
|
11786
12532
|
if (options.profile && profileName !== "default") {
|
|
11787
|
-
console.log(
|
|
11788
|
-
console.log(
|
|
12533
|
+
console.log(import_chalk20.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
12534
|
+
console.log(import_chalk20.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
11789
12535
|
} else if (options.local) {
|
|
11790
|
-
console.log(
|
|
11791
|
-
console.log(
|
|
12536
|
+
console.log(import_chalk20.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
12537
|
+
console.log(import_chalk20.default.gray(` All decisions stay on this machine.`));
|
|
11792
12538
|
} else {
|
|
11793
|
-
console.log(
|
|
11794
|
-
console.log(
|
|
12539
|
+
console.log(import_chalk20.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
12540
|
+
console.log(import_chalk20.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
11795
12541
|
}
|
|
11796
12542
|
});
|
|
11797
12543
|
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) => {
|
|
@@ -11799,19 +12545,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
11799
12545
|
if (target === "claude") return await setupClaude();
|
|
11800
12546
|
if (target === "cursor") return await setupCursor();
|
|
11801
12547
|
if (target === "hud") return setupHud();
|
|
11802
|
-
console.error(
|
|
12548
|
+
console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11803
12549
|
process.exit(1);
|
|
11804
12550
|
});
|
|
11805
12551
|
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) => {
|
|
11806
12552
|
if (!target) {
|
|
11807
|
-
console.log(
|
|
11808
|
-
console.log(" Usage: " +
|
|
12553
|
+
console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
12554
|
+
console.log(" Usage: " + import_chalk20.default.white("node9 setup <target>") + "\n");
|
|
11809
12555
|
console.log(" Targets:");
|
|
11810
|
-
console.log(" " +
|
|
11811
|
-
console.log(" " +
|
|
11812
|
-
console.log(" " +
|
|
12556
|
+
console.log(" " + import_chalk20.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
12557
|
+
console.log(" " + import_chalk20.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
12558
|
+
console.log(" " + import_chalk20.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
11813
12559
|
process.stdout.write(
|
|
11814
|
-
" " +
|
|
12560
|
+
" " + import_chalk20.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
11815
12561
|
);
|
|
11816
12562
|
console.log("");
|
|
11817
12563
|
return;
|
|
@@ -11821,7 +12567,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
11821
12567
|
if (t === "claude") return await setupClaude();
|
|
11822
12568
|
if (t === "cursor") return await setupCursor();
|
|
11823
12569
|
if (t === "hud") return setupHud();
|
|
11824
|
-
console.error(
|
|
12570
|
+
console.error(import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11825
12571
|
process.exit(1);
|
|
11826
12572
|
});
|
|
11827
12573
|
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) => {
|
|
@@ -11832,31 +12578,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
11832
12578
|
else if (target === "hud") fn = teardownHud;
|
|
11833
12579
|
else {
|
|
11834
12580
|
console.error(
|
|
11835
|
-
|
|
12581
|
+
import_chalk20.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
11836
12582
|
);
|
|
11837
12583
|
process.exit(1);
|
|
11838
12584
|
}
|
|
11839
|
-
console.log(
|
|
12585
|
+
console.log(import_chalk20.default.cyan(`
|
|
11840
12586
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
11841
12587
|
`));
|
|
11842
12588
|
try {
|
|
11843
12589
|
fn();
|
|
11844
12590
|
} catch (err2) {
|
|
11845
|
-
console.error(
|
|
12591
|
+
console.error(import_chalk20.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11846
12592
|
process.exit(1);
|
|
11847
12593
|
}
|
|
11848
|
-
console.log(
|
|
12594
|
+
console.log(import_chalk20.default.gray("\n Restart the agent for changes to take effect."));
|
|
11849
12595
|
});
|
|
11850
12596
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
11851
|
-
console.log(
|
|
11852
|
-
console.log(
|
|
12597
|
+
console.log(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
12598
|
+
console.log(import_chalk20.default.bold("Stopping daemon..."));
|
|
11853
12599
|
try {
|
|
11854
12600
|
stopDaemon();
|
|
11855
|
-
console.log(
|
|
12601
|
+
console.log(import_chalk20.default.green(" \u2705 Daemon stopped"));
|
|
11856
12602
|
} catch {
|
|
11857
|
-
console.log(
|
|
12603
|
+
console.log(import_chalk20.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
11858
12604
|
}
|
|
11859
|
-
console.log(
|
|
12605
|
+
console.log(import_chalk20.default.bold("\nRemoving hooks..."));
|
|
11860
12606
|
let teardownFailed = false;
|
|
11861
12607
|
for (const [label, fn] of [
|
|
11862
12608
|
["Claude", teardownClaude],
|
|
@@ -11868,45 +12614,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
11868
12614
|
} catch (err2) {
|
|
11869
12615
|
teardownFailed = true;
|
|
11870
12616
|
console.error(
|
|
11871
|
-
|
|
12617
|
+
import_chalk20.default.red(
|
|
11872
12618
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11873
12619
|
)
|
|
11874
12620
|
);
|
|
11875
12621
|
}
|
|
11876
12622
|
}
|
|
11877
12623
|
if (options.purge) {
|
|
11878
|
-
const node9Dir =
|
|
11879
|
-
if (
|
|
12624
|
+
const node9Dir = import_path34.default.join(import_os27.default.homedir(), ".node9");
|
|
12625
|
+
if (import_fs31.default.existsSync(node9Dir)) {
|
|
11880
12626
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
11881
12627
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
11882
12628
|
default: false
|
|
11883
12629
|
});
|
|
11884
12630
|
if (confirmed) {
|
|
11885
|
-
|
|
11886
|
-
if (
|
|
12631
|
+
import_fs31.default.rmSync(node9Dir, { recursive: true });
|
|
12632
|
+
if (import_fs31.default.existsSync(node9Dir)) {
|
|
11887
12633
|
console.error(
|
|
11888
|
-
|
|
12634
|
+
import_chalk20.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
11889
12635
|
);
|
|
11890
12636
|
} else {
|
|
11891
|
-
console.log(
|
|
12637
|
+
console.log(import_chalk20.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
11892
12638
|
}
|
|
11893
12639
|
} else {
|
|
11894
|
-
console.log(
|
|
12640
|
+
console.log(import_chalk20.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
11895
12641
|
}
|
|
11896
12642
|
} else {
|
|
11897
|
-
console.log(
|
|
12643
|
+
console.log(import_chalk20.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
11898
12644
|
}
|
|
11899
12645
|
} else {
|
|
11900
12646
|
console.log(
|
|
11901
|
-
|
|
12647
|
+
import_chalk20.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
11902
12648
|
);
|
|
11903
12649
|
}
|
|
11904
12650
|
if (teardownFailed) {
|
|
11905
|
-
console.error(
|
|
12651
|
+
console.error(import_chalk20.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
11906
12652
|
process.exit(1);
|
|
11907
12653
|
}
|
|
11908
|
-
console.log(
|
|
11909
|
-
console.log(
|
|
12654
|
+
console.log(import_chalk20.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
12655
|
+
console.log(import_chalk20.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
11910
12656
|
});
|
|
11911
12657
|
registerDoctorCommand(program, version);
|
|
11912
12658
|
program.command("explain").description(
|
|
@@ -11919,7 +12665,7 @@ program.command("explain").description(
|
|
|
11919
12665
|
try {
|
|
11920
12666
|
args = JSON.parse(trimmed);
|
|
11921
12667
|
} catch {
|
|
11922
|
-
console.error(
|
|
12668
|
+
console.error(import_chalk20.default.red(`
|
|
11923
12669
|
\u274C Invalid JSON: ${trimmed}
|
|
11924
12670
|
`));
|
|
11925
12671
|
process.exit(1);
|
|
@@ -11930,60 +12676,61 @@ program.command("explain").description(
|
|
|
11930
12676
|
}
|
|
11931
12677
|
const result = await explainPolicy(tool, args);
|
|
11932
12678
|
console.log("");
|
|
11933
|
-
console.log(
|
|
12679
|
+
console.log(import_chalk20.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
11934
12680
|
console.log("");
|
|
11935
|
-
console.log(` ${
|
|
12681
|
+
console.log(` ${import_chalk20.default.bold("Tool:")} ${import_chalk20.default.white(result.tool)}`);
|
|
11936
12682
|
if (argsRaw) {
|
|
11937
12683
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
11938
|
-
console.log(` ${
|
|
12684
|
+
console.log(` ${import_chalk20.default.bold("Input:")} ${import_chalk20.default.gray(preview)}`);
|
|
11939
12685
|
}
|
|
11940
12686
|
console.log("");
|
|
11941
|
-
console.log(
|
|
12687
|
+
console.log(import_chalk20.default.bold("Config Sources (Waterfall):"));
|
|
11942
12688
|
for (const tier of result.waterfall) {
|
|
11943
|
-
const
|
|
12689
|
+
const num2 = import_chalk20.default.gray(` ${tier.tier}.`);
|
|
11944
12690
|
const label = tier.label.padEnd(16);
|
|
11945
12691
|
let statusStr;
|
|
11946
12692
|
if (tier.tier === 1) {
|
|
11947
|
-
statusStr =
|
|
12693
|
+
statusStr = import_chalk20.default.gray(tier.note ?? "");
|
|
11948
12694
|
} else if (tier.status === "active") {
|
|
11949
|
-
const loc = tier.path ?
|
|
11950
|
-
const note = tier.note ?
|
|
11951
|
-
statusStr =
|
|
12695
|
+
const loc = tier.path ? import_chalk20.default.gray(tier.path) : "";
|
|
12696
|
+
const note = tier.note ? import_chalk20.default.gray(`(${tier.note})`) : "";
|
|
12697
|
+
statusStr = import_chalk20.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
11952
12698
|
} else {
|
|
11953
|
-
statusStr =
|
|
12699
|
+
statusStr = import_chalk20.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
11954
12700
|
}
|
|
11955
|
-
console.log(`${
|
|
12701
|
+
console.log(`${num2} ${import_chalk20.default.white(label)} ${statusStr}`);
|
|
11956
12702
|
}
|
|
11957
12703
|
console.log("");
|
|
11958
|
-
console.log(
|
|
12704
|
+
console.log(import_chalk20.default.bold("Policy Evaluation:"));
|
|
11959
12705
|
for (const step of result.steps) {
|
|
11960
12706
|
const isFinal = step.isFinal;
|
|
11961
12707
|
let icon;
|
|
11962
|
-
if (step.outcome === "allow") icon =
|
|
11963
|
-
else if (step.outcome === "review") icon =
|
|
11964
|
-
else if (step.outcome === "skip") icon =
|
|
11965
|
-
else icon =
|
|
12708
|
+
if (step.outcome === "allow") icon = import_chalk20.default.green(" \u2705");
|
|
12709
|
+
else if (step.outcome === "review") icon = import_chalk20.default.red(" \u{1F534}");
|
|
12710
|
+
else if (step.outcome === "skip") icon = import_chalk20.default.gray(" \u2500 ");
|
|
12711
|
+
else icon = import_chalk20.default.gray(" \u25CB ");
|
|
11966
12712
|
const name = step.name.padEnd(18);
|
|
11967
|
-
const nameStr = isFinal ?
|
|
11968
|
-
const detail = isFinal ?
|
|
11969
|
-
const arrow = isFinal ?
|
|
12713
|
+
const nameStr = isFinal ? import_chalk20.default.white.bold(name) : import_chalk20.default.white(name);
|
|
12714
|
+
const detail = isFinal ? import_chalk20.default.white(step.detail) : import_chalk20.default.gray(step.detail);
|
|
12715
|
+
const arrow = isFinal ? import_chalk20.default.yellow(" \u2190 STOP") : "";
|
|
11970
12716
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
11971
12717
|
}
|
|
11972
12718
|
console.log("");
|
|
11973
12719
|
if (result.decision === "allow") {
|
|
11974
|
-
console.log(
|
|
12720
|
+
console.log(import_chalk20.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk20.default.gray(" \u2014 no approval needed"));
|
|
11975
12721
|
} else {
|
|
11976
12722
|
console.log(
|
|
11977
|
-
|
|
12723
|
+
import_chalk20.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk20.default.gray(" \u2014 human approval required")
|
|
11978
12724
|
);
|
|
11979
12725
|
if (result.blockedByLabel) {
|
|
11980
|
-
console.log(
|
|
12726
|
+
console.log(import_chalk20.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
11981
12727
|
}
|
|
11982
12728
|
}
|
|
11983
12729
|
console.log("");
|
|
11984
12730
|
});
|
|
11985
12731
|
registerInitCommand(program);
|
|
11986
12732
|
registerAuditCommand(program);
|
|
12733
|
+
registerReportCommand(program);
|
|
11987
12734
|
registerStatusCommand(program);
|
|
11988
12735
|
registerDaemonCommand(program);
|
|
11989
12736
|
program.command("tail").description("Stream live agent activity to the terminal").option("--history", "Replay recent history then continue live", false).option("--clear", "Clear the history buffer and exit (does not stream)", false).action(async (options) => {
|
|
@@ -11991,7 +12738,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
11991
12738
|
try {
|
|
11992
12739
|
await startTail2(options);
|
|
11993
12740
|
} catch (err2) {
|
|
11994
|
-
console.error(
|
|
12741
|
+
console.error(import_chalk20.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11995
12742
|
process.exit(1);
|
|
11996
12743
|
}
|
|
11997
12744
|
});
|
|
@@ -12023,14 +12770,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
12023
12770
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
12024
12771
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
12025
12772
|
if (subcommand === "debug") {
|
|
12026
|
-
const flagFile =
|
|
12773
|
+
const flagFile = import_path34.default.join(import_os27.default.homedir(), ".node9", "hud-debug");
|
|
12027
12774
|
if (state === "on") {
|
|
12028
|
-
|
|
12029
|
-
|
|
12775
|
+
import_fs31.default.mkdirSync(import_path34.default.dirname(flagFile), { recursive: true });
|
|
12776
|
+
import_fs31.default.writeFileSync(flagFile, "");
|
|
12030
12777
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
12031
12778
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
12032
12779
|
} else if (state === "off") {
|
|
12033
|
-
if (
|
|
12780
|
+
if (import_fs31.default.existsSync(flagFile)) import_fs31.default.unlinkSync(flagFile);
|
|
12034
12781
|
console.log("HUD debug logging disabled.");
|
|
12035
12782
|
} else {
|
|
12036
12783
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -12045,7 +12792,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12045
12792
|
const ms = parseDuration(options.duration);
|
|
12046
12793
|
if (ms === null) {
|
|
12047
12794
|
console.error(
|
|
12048
|
-
|
|
12795
|
+
import_chalk20.default.red(`
|
|
12049
12796
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
12050
12797
|
`)
|
|
12051
12798
|
);
|
|
@@ -12053,20 +12800,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
12053
12800
|
}
|
|
12054
12801
|
pauseNode9(ms, options.duration);
|
|
12055
12802
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
12056
|
-
console.log(
|
|
12803
|
+
console.log(import_chalk20.default.yellow(`
|
|
12057
12804
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
12058
|
-
console.log(
|
|
12059
|
-
console.log(
|
|
12805
|
+
console.log(import_chalk20.default.gray(` All tool calls will be allowed without review.`));
|
|
12806
|
+
console.log(import_chalk20.default.gray(` Run "node9 resume" to re-enable early.
|
|
12060
12807
|
`));
|
|
12061
12808
|
});
|
|
12062
12809
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
12063
12810
|
const { paused } = checkPause();
|
|
12064
12811
|
if (!paused) {
|
|
12065
|
-
console.log(
|
|
12812
|
+
console.log(import_chalk20.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
12066
12813
|
return;
|
|
12067
12814
|
}
|
|
12068
12815
|
resumeNode9();
|
|
12069
|
-
console.log(
|
|
12816
|
+
console.log(import_chalk20.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
12070
12817
|
});
|
|
12071
12818
|
var HOOK_BASED_AGENTS = {
|
|
12072
12819
|
claude: "claude",
|
|
@@ -12079,15 +12826,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12079
12826
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
12080
12827
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
12081
12828
|
console.error(
|
|
12082
|
-
|
|
12829
|
+
import_chalk20.default.yellow(`
|
|
12083
12830
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
12084
12831
|
);
|
|
12085
|
-
console.error(
|
|
12832
|
+
console.error(import_chalk20.default.white(`
|
|
12086
12833
|
"${target}" uses its own hook system. Use:`));
|
|
12087
12834
|
console.error(
|
|
12088
|
-
|
|
12835
|
+
import_chalk20.default.green(` node9 addto ${target} `) + import_chalk20.default.gray("# one-time setup")
|
|
12089
12836
|
);
|
|
12090
|
-
console.error(
|
|
12837
|
+
console.error(import_chalk20.default.green(` ${target} `) + import_chalk20.default.gray("# run normally"));
|
|
12091
12838
|
process.exit(1);
|
|
12092
12839
|
}
|
|
12093
12840
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -12104,7 +12851,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12104
12851
|
}
|
|
12105
12852
|
);
|
|
12106
12853
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
12107
|
-
console.error(
|
|
12854
|
+
console.error(import_chalk20.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
12108
12855
|
const daemonReady = await autoStartDaemonAndWait();
|
|
12109
12856
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
12110
12857
|
}
|
|
@@ -12117,12 +12864,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
12117
12864
|
}
|
|
12118
12865
|
if (!result.approved) {
|
|
12119
12866
|
console.error(
|
|
12120
|
-
|
|
12867
|
+
import_chalk20.default.red(`
|
|
12121
12868
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
12122
12869
|
);
|
|
12123
12870
|
process.exit(1);
|
|
12124
12871
|
}
|
|
12125
|
-
console.error(
|
|
12872
|
+
console.error(import_chalk20.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
12126
12873
|
await runProxy(fullCommand);
|
|
12127
12874
|
} else {
|
|
12128
12875
|
program.help();
|
|
@@ -12137,9 +12884,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
12137
12884
|
const isCheckHook = process.argv[2] === "check";
|
|
12138
12885
|
if (isCheckHook) {
|
|
12139
12886
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
12140
|
-
const logPath =
|
|
12887
|
+
const logPath = import_path34.default.join(import_os27.default.homedir(), ".node9", "hook-debug.log");
|
|
12141
12888
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
12142
|
-
|
|
12889
|
+
import_fs31.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
12143
12890
|
`);
|
|
12144
12891
|
}
|
|
12145
12892
|
process.exit(0);
|