@node9/proxy 1.5.3 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -14
- package/dist/cli.js +665 -357
- package/dist/cli.mjs +660 -352
- package/dist/index.js +53 -10
- package/dist/index.mjs +53 -10
- package/package.json +6 -5
package/dist/cli.js
CHANGED
|
@@ -160,8 +160,8 @@ function sanitizeConfig(raw) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
const lines = result.error.issues.map((issue) => {
|
|
163
|
-
const
|
|
164
|
-
return ` \u2022 ${
|
|
163
|
+
const path30 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
164
|
+
return ` \u2022 ${path30}: ${issue.message}`;
|
|
165
165
|
});
|
|
166
166
|
return {
|
|
167
167
|
sanitized,
|
|
@@ -863,7 +863,9 @@ var init_config = __esm({
|
|
|
863
863
|
{
|
|
864
864
|
field: "command",
|
|
865
865
|
op: "matches",
|
|
866
|
-
|
|
866
|
+
// Require the recursive flag to be preceded by whitespace so that
|
|
867
|
+
// filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
|
|
868
|
+
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
867
869
|
},
|
|
868
870
|
{
|
|
869
871
|
field: "command",
|
|
@@ -1772,9 +1774,9 @@ function matchesPattern(text, patterns) {
|
|
|
1772
1774
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1773
1775
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1774
1776
|
}
|
|
1775
|
-
function getNestedValue(obj,
|
|
1777
|
+
function getNestedValue(obj, path30) {
|
|
1776
1778
|
if (!obj || typeof obj !== "object") return null;
|
|
1777
|
-
return
|
|
1779
|
+
return path30.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1778
1780
|
}
|
|
1779
1781
|
function shouldSnapshot(toolName, args, config) {
|
|
1780
1782
|
if (!config.settings.enableUndo) return false;
|
|
@@ -3019,6 +3021,33 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
3019
3021
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
3020
3022
|
const controller = new AbortController();
|
|
3021
3023
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
3024
|
+
if (!creds.apiKey) throw new Error("Node9 API Key is missing");
|
|
3025
|
+
let ciContext;
|
|
3026
|
+
if (process.env.CI) {
|
|
3027
|
+
try {
|
|
3028
|
+
const ciContextPath = import_path13.default.join(import_os9.default.homedir(), ".node9", "ci-context.json");
|
|
3029
|
+
const stats = import_fs10.default.statSync(ciContextPath);
|
|
3030
|
+
if (stats.size > 1e4) throw new Error("ci-context.json exceeds 10 KB");
|
|
3031
|
+
const raw = import_fs10.default.readFileSync(ciContextPath, "utf8");
|
|
3032
|
+
const parsed = JSON.parse(raw);
|
|
3033
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3034
|
+
throw new Error("ci-context.json is not a plain object");
|
|
3035
|
+
}
|
|
3036
|
+
const p = parsed;
|
|
3037
|
+
ciContext = {
|
|
3038
|
+
tests_after: p["tests_after"],
|
|
3039
|
+
files_changed: p["files_changed"],
|
|
3040
|
+
issues_found: p["issues_found"],
|
|
3041
|
+
issues_fixed: p["issues_fixed"],
|
|
3042
|
+
github_repository: p["github_repository"],
|
|
3043
|
+
github_head_ref: p["github_head_ref"],
|
|
3044
|
+
iteration: p["iteration"],
|
|
3045
|
+
draft_pr_number: p["draft_pr_number"],
|
|
3046
|
+
draft_pr_url: p["draft_pr_url"]
|
|
3047
|
+
};
|
|
3048
|
+
} catch {
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3022
3051
|
try {
|
|
3023
3052
|
const response = await fetch(creds.apiUrl, {
|
|
3024
3053
|
method: "POST",
|
|
@@ -3033,7 +3062,8 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
3033
3062
|
cwd: process.cwd(),
|
|
3034
3063
|
platform: import_os9.default.platform()
|
|
3035
3064
|
},
|
|
3036
|
-
...riskMetadata && { riskMetadata }
|
|
3065
|
+
...riskMetadata && { riskMetadata },
|
|
3066
|
+
...ciContext && { ciContext }
|
|
3037
3067
|
}),
|
|
3038
3068
|
signal: controller.signal
|
|
3039
3069
|
});
|
|
@@ -3059,12 +3089,17 @@ async function pollNode9SaaS(requestId, creds, signal) {
|
|
|
3059
3089
|
});
|
|
3060
3090
|
clearTimeout(pollTimer);
|
|
3061
3091
|
if (!statusRes.ok) continue;
|
|
3062
|
-
const
|
|
3092
|
+
const statusBody = await statusRes.json();
|
|
3093
|
+
const { status } = statusBody;
|
|
3063
3094
|
if (status === "APPROVED") {
|
|
3064
|
-
return { approved: true, reason };
|
|
3095
|
+
return { approved: true, reason: statusBody.reason };
|
|
3065
3096
|
}
|
|
3066
3097
|
if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
|
|
3067
|
-
return { approved: false, reason };
|
|
3098
|
+
return { approved: false, reason: statusBody.reason };
|
|
3099
|
+
}
|
|
3100
|
+
if (status === "FIX") {
|
|
3101
|
+
const feedbackText = statusBody.feedbackText ?? statusBody.reason ?? "Run again with feedback.";
|
|
3102
|
+
return { approved: false, reason: feedbackText };
|
|
3068
3103
|
}
|
|
3069
3104
|
} catch {
|
|
3070
3105
|
}
|
|
@@ -3101,12 +3136,13 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
3101
3136
|
);
|
|
3102
3137
|
}
|
|
3103
3138
|
}
|
|
3104
|
-
var import_fs10, import_os9;
|
|
3139
|
+
var import_fs10, import_os9, import_path13;
|
|
3105
3140
|
var init_cloud = __esm({
|
|
3106
3141
|
"src/auth/cloud.ts"() {
|
|
3107
3142
|
"use strict";
|
|
3108
3143
|
import_fs10 = __toESM(require("fs"));
|
|
3109
3144
|
import_os9 = __toESM(require("os"));
|
|
3145
|
+
import_path13 = __toESM(require("path"));
|
|
3110
3146
|
init_audit();
|
|
3111
3147
|
}
|
|
3112
3148
|
});
|
|
@@ -3298,7 +3334,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3298
3334
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
3299
3335
|
if (policyResult.decision === "allow") {
|
|
3300
3336
|
if (approvers.cloud && creds?.apiKey)
|
|
3301
|
-
auditLocalAllow(toolName, args, "local-policy", creds, meta);
|
|
3337
|
+
await auditLocalAllow(toolName, args, "local-policy", creds, meta);
|
|
3302
3338
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
|
|
3303
3339
|
return { approved: true, checkedBy: "local-policy" };
|
|
3304
3340
|
}
|
|
@@ -3319,9 +3355,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3319
3355
|
if (predicatesMet && policyResult.recoveryCommand) {
|
|
3320
3356
|
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
3321
3357
|
}
|
|
3358
|
+
} else if (isDaemonRunning() && !isTestEnv2) {
|
|
3359
|
+
if (!isManual)
|
|
3360
|
+
appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
|
|
3361
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3362
|
+
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
|
|
3322
3363
|
} else {
|
|
3323
3364
|
if (!isManual)
|
|
3324
3365
|
appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
|
|
3366
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3367
|
+
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
|
|
3325
3368
|
return {
|
|
3326
3369
|
approved: false,
|
|
3327
3370
|
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
@@ -5344,12 +5387,12 @@ var init_suggestion_tracker = __esm({
|
|
|
5344
5387
|
});
|
|
5345
5388
|
|
|
5346
5389
|
// src/daemon/taint-store.ts
|
|
5347
|
-
var import_fs12,
|
|
5390
|
+
var import_fs12, import_path15, DEFAULT_TTL_MS, TaintStore;
|
|
5348
5391
|
var init_taint_store = __esm({
|
|
5349
5392
|
"src/daemon/taint-store.ts"() {
|
|
5350
5393
|
"use strict";
|
|
5351
5394
|
import_fs12 = __toESM(require("fs"));
|
|
5352
|
-
|
|
5395
|
+
import_path15 = __toESM(require("path"));
|
|
5353
5396
|
DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
5354
5397
|
TaintStore = class {
|
|
5355
5398
|
records = /* @__PURE__ */ new Map();
|
|
@@ -5414,9 +5457,9 @@ var init_taint_store = __esm({
|
|
|
5414
5457
|
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
5415
5458
|
_resolve(filePath) {
|
|
5416
5459
|
try {
|
|
5417
|
-
return import_fs12.default.realpathSync.native(
|
|
5460
|
+
return import_fs12.default.realpathSync.native(import_path15.default.resolve(filePath));
|
|
5418
5461
|
} catch {
|
|
5419
|
-
return
|
|
5462
|
+
return import_path15.default.resolve(filePath);
|
|
5420
5463
|
}
|
|
5421
5464
|
}
|
|
5422
5465
|
};
|
|
@@ -5567,7 +5610,7 @@ function markRejectionHandlerRegistered() {
|
|
|
5567
5610
|
daemonRejectionHandlerRegistered = true;
|
|
5568
5611
|
}
|
|
5569
5612
|
function atomicWriteSync2(filePath, data, options) {
|
|
5570
|
-
const dir =
|
|
5613
|
+
const dir = import_path16.default.dirname(filePath);
|
|
5571
5614
|
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
5572
5615
|
const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
|
|
5573
5616
|
try {
|
|
@@ -5607,7 +5650,7 @@ function appendAuditLog(data) {
|
|
|
5607
5650
|
decision: data.decision,
|
|
5608
5651
|
source: "daemon"
|
|
5609
5652
|
};
|
|
5610
|
-
const dir =
|
|
5653
|
+
const dir = import_path16.default.dirname(AUDIT_LOG_FILE);
|
|
5611
5654
|
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
5612
5655
|
import_fs13.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
5613
5656
|
} catch {
|
|
@@ -5685,6 +5728,7 @@ function readBody(req) {
|
|
|
5685
5728
|
});
|
|
5686
5729
|
}
|
|
5687
5730
|
function openBrowser(url) {
|
|
5731
|
+
if (process.env.NODE9_TESTING === "1") return;
|
|
5688
5732
|
try {
|
|
5689
5733
|
const args = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
|
|
5690
5734
|
(0, import_child_process3.spawn)(args[0], args.slice(1), { detached: true, stdio: "ignore" }).unref();
|
|
@@ -5811,13 +5855,13 @@ function startActivitySocket() {
|
|
|
5811
5855
|
}
|
|
5812
5856
|
});
|
|
5813
5857
|
}
|
|
5814
|
-
var import_net2, import_fs13,
|
|
5858
|
+
var import_net2, import_fs13, import_path16, import_os11, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE, WRITE_TOOL_NAMES;
|
|
5815
5859
|
var init_state2 = __esm({
|
|
5816
5860
|
"src/daemon/state.ts"() {
|
|
5817
5861
|
"use strict";
|
|
5818
5862
|
import_net2 = __toESM(require("net"));
|
|
5819
5863
|
import_fs13 = __toESM(require("fs"));
|
|
5820
|
-
|
|
5864
|
+
import_path16 = __toESM(require("path"));
|
|
5821
5865
|
import_os11 = __toESM(require("os"));
|
|
5822
5866
|
import_child_process3 = require("child_process");
|
|
5823
5867
|
import_crypto5 = require("crypto");
|
|
@@ -5827,13 +5871,13 @@ var init_state2 = __esm({
|
|
|
5827
5871
|
init_session_counters();
|
|
5828
5872
|
init_session_history();
|
|
5829
5873
|
homeDir = import_os11.default.homedir();
|
|
5830
|
-
DAEMON_PID_FILE =
|
|
5831
|
-
DECISIONS_FILE =
|
|
5832
|
-
AUDIT_LOG_FILE =
|
|
5833
|
-
TRUST_FILE2 =
|
|
5834
|
-
GLOBAL_CONFIG_FILE =
|
|
5835
|
-
CREDENTIALS_FILE =
|
|
5836
|
-
INSIGHT_COUNTS_FILE =
|
|
5874
|
+
DAEMON_PID_FILE = import_path16.default.join(homeDir, ".node9", "daemon.pid");
|
|
5875
|
+
DECISIONS_FILE = import_path16.default.join(homeDir, ".node9", "decisions.json");
|
|
5876
|
+
AUDIT_LOG_FILE = import_path16.default.join(homeDir, ".node9", "audit.log");
|
|
5877
|
+
TRUST_FILE2 = import_path16.default.join(homeDir, ".node9", "trust.json");
|
|
5878
|
+
GLOBAL_CONFIG_FILE = import_path16.default.join(homeDir, ".node9", "config.json");
|
|
5879
|
+
CREDENTIALS_FILE = import_path16.default.join(homeDir, ".node9", "credentials.json");
|
|
5880
|
+
INSIGHT_COUNTS_FILE = import_path16.default.join(homeDir, ".node9", "insight-counts.json");
|
|
5837
5881
|
pending = /* @__PURE__ */ new Map();
|
|
5838
5882
|
sseClients = /* @__PURE__ */ new Set();
|
|
5839
5883
|
suggestionTracker = new SuggestionTracker(3);
|
|
@@ -5851,7 +5895,7 @@ var init_state2 = __esm({
|
|
|
5851
5895
|
"2h": 2 * 60 * 6e4
|
|
5852
5896
|
};
|
|
5853
5897
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5854
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
5898
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path16.default.join(import_os11.default.tmpdir(), "node9-activity.sock");
|
|
5855
5899
|
ACTIVITY_RING_SIZE = 100;
|
|
5856
5900
|
activityRing = [];
|
|
5857
5901
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -5893,7 +5937,7 @@ function patchConfig(configPath, patch) {
|
|
|
5893
5937
|
ignored.push(patch.toolName);
|
|
5894
5938
|
}
|
|
5895
5939
|
}
|
|
5896
|
-
const dir =
|
|
5940
|
+
const dir = import_path17.default.dirname(configPath);
|
|
5897
5941
|
import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5898
5942
|
const tmp = configPath + ".node9-tmp";
|
|
5899
5943
|
try {
|
|
@@ -5915,14 +5959,14 @@ function patchConfig(configPath, patch) {
|
|
|
5915
5959
|
throw err;
|
|
5916
5960
|
}
|
|
5917
5961
|
}
|
|
5918
|
-
var import_fs14,
|
|
5962
|
+
var import_fs14, import_path17, import_os12, GLOBAL_CONFIG_PATH;
|
|
5919
5963
|
var init_patch = __esm({
|
|
5920
5964
|
"src/config/patch.ts"() {
|
|
5921
5965
|
"use strict";
|
|
5922
5966
|
import_fs14 = __toESM(require("fs"));
|
|
5923
|
-
|
|
5967
|
+
import_path17 = __toESM(require("path"));
|
|
5924
5968
|
import_os12 = __toESM(require("os"));
|
|
5925
|
-
GLOBAL_CONFIG_PATH =
|
|
5969
|
+
GLOBAL_CONFIG_PATH = import_path17.default.join(import_os12.default.homedir(), ".node9", "config.json");
|
|
5926
5970
|
}
|
|
5927
5971
|
});
|
|
5928
5972
|
|
|
@@ -6105,7 +6149,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6105
6149
|
status: "pending"
|
|
6106
6150
|
});
|
|
6107
6151
|
}
|
|
6108
|
-
const projectCwd = typeof cwd === "string" &&
|
|
6152
|
+
const projectCwd = typeof cwd === "string" && import_path18.default.isAbsolute(cwd) ? cwd : void 0;
|
|
6109
6153
|
const projectConfig = getConfig(projectCwd);
|
|
6110
6154
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
6111
6155
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -6493,8 +6537,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6493
6537
|
const body = await readBody(req);
|
|
6494
6538
|
const data = body ? JSON.parse(body) : {};
|
|
6495
6539
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
6496
|
-
const node9Dir =
|
|
6497
|
-
if (!
|
|
6540
|
+
const node9Dir = import_path18.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6541
|
+
if (!import_path18.default.resolve(configPath).startsWith(node9Dir + import_path18.default.sep)) {
|
|
6498
6542
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6499
6543
|
return res.end(
|
|
6500
6544
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -6671,13 +6715,13 @@ data: ${JSON.stringify(item.data)}
|
|
|
6671
6715
|
}
|
|
6672
6716
|
startActivitySocket();
|
|
6673
6717
|
}
|
|
6674
|
-
var import_http, import_fs15,
|
|
6718
|
+
var import_http, import_fs15, import_path18, import_crypto6, import_child_process4, import_chalk2;
|
|
6675
6719
|
var init_server = __esm({
|
|
6676
6720
|
"src/daemon/server.ts"() {
|
|
6677
6721
|
"use strict";
|
|
6678
6722
|
import_http = __toESM(require("http"));
|
|
6679
6723
|
import_fs15 = __toESM(require("fs"));
|
|
6680
|
-
|
|
6724
|
+
import_path18 = __toESM(require("path"));
|
|
6681
6725
|
import_crypto6 = require("crypto");
|
|
6682
6726
|
import_child_process4 = require("child_process");
|
|
6683
6727
|
import_chalk2 = __toESM(require("chalk"));
|
|
@@ -6759,27 +6803,27 @@ function formatBase(activity) {
|
|
|
6759
6803
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6760
6804
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ");
|
|
6761
6805
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6762
|
-
return `${
|
|
6806
|
+
return `${import_chalk17.default.gray(time)} ${icon} ${import_chalk17.default.white.bold(toolName)} ${import_chalk17.default.dim(argsPreview)}`;
|
|
6763
6807
|
}
|
|
6764
6808
|
function renderResult(activity, result) {
|
|
6765
6809
|
const base = formatBase(activity);
|
|
6766
6810
|
let status;
|
|
6767
6811
|
if (result.status === "allow") {
|
|
6768
|
-
status =
|
|
6812
|
+
status = import_chalk17.default.green("\u2713 ALLOW");
|
|
6769
6813
|
} else if (result.status === "dlp") {
|
|
6770
|
-
status =
|
|
6814
|
+
status = import_chalk17.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
6771
6815
|
} else {
|
|
6772
|
-
status =
|
|
6816
|
+
status = import_chalk17.default.red("\u2717 BLOCK");
|
|
6773
6817
|
}
|
|
6774
6818
|
if (process.stdout.isTTY) {
|
|
6775
|
-
|
|
6776
|
-
|
|
6819
|
+
import_readline4.default.clearLine(process.stdout, 0);
|
|
6820
|
+
import_readline4.default.cursorTo(process.stdout, 0);
|
|
6777
6821
|
}
|
|
6778
6822
|
console.log(`${base} ${status}`);
|
|
6779
6823
|
}
|
|
6780
6824
|
function renderPending(activity) {
|
|
6781
6825
|
if (!process.stdout.isTTY) return;
|
|
6782
|
-
process.stdout.write(`${formatBase(activity)} ${
|
|
6826
|
+
process.stdout.write(`${formatBase(activity)} ${import_chalk17.default.yellow("\u25CF \u2026")}\r`);
|
|
6783
6827
|
}
|
|
6784
6828
|
async function ensureDaemon() {
|
|
6785
6829
|
let pidPort = null;
|
|
@@ -6788,7 +6832,7 @@ async function ensureDaemon() {
|
|
|
6788
6832
|
const { port } = JSON.parse(import_fs24.default.readFileSync(PID_FILE, "utf-8"));
|
|
6789
6833
|
pidPort = port;
|
|
6790
6834
|
} catch {
|
|
6791
|
-
console.error(
|
|
6835
|
+
console.error(import_chalk17.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
6792
6836
|
}
|
|
6793
6837
|
}
|
|
6794
6838
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -6799,7 +6843,7 @@ async function ensureDaemon() {
|
|
|
6799
6843
|
if (res.ok) return checkPort;
|
|
6800
6844
|
} catch {
|
|
6801
6845
|
}
|
|
6802
|
-
console.log(
|
|
6846
|
+
console.log(import_chalk17.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6803
6847
|
const child = (0, import_child_process13.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6804
6848
|
detached: true,
|
|
6805
6849
|
stdio: "ignore",
|
|
@@ -6816,7 +6860,7 @@ async function ensureDaemon() {
|
|
|
6816
6860
|
} catch {
|
|
6817
6861
|
}
|
|
6818
6862
|
}
|
|
6819
|
-
console.error(
|
|
6863
|
+
console.error(import_chalk17.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6820
6864
|
process.exit(1);
|
|
6821
6865
|
}
|
|
6822
6866
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -6858,23 +6902,23 @@ function buildCardLines(req, localCount = 0) {
|
|
|
6858
6902
|
const blockedBy = req.riskMetadata?.blockedByLabel ?? "Policy rule";
|
|
6859
6903
|
const lines = [
|
|
6860
6904
|
``,
|
|
6861
|
-
`${
|
|
6862
|
-
`${CYAN}\u2551${
|
|
6863
|
-
`${CYAN}\u2551${
|
|
6905
|
+
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
6906
|
+
`${CYAN}\u2551${RESET2} Tool: ${BOLD2}${req.toolName}${RESET2}`,
|
|
6907
|
+
`${CYAN}\u2551${RESET2} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET2}`
|
|
6864
6908
|
];
|
|
6865
6909
|
if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
|
|
6866
|
-
lines.push(`${CYAN}\u2551${
|
|
6910
|
+
lines.push(`${CYAN}\u2551${RESET2} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET2}`);
|
|
6867
6911
|
}
|
|
6868
|
-
lines.push(`${CYAN}\u2551${
|
|
6912
|
+
lines.push(`${CYAN}\u2551${RESET2} Args: ${GRAY}${argsPreview}${RESET2}`);
|
|
6869
6913
|
if (localCount >= 2) {
|
|
6870
6914
|
lines.push(
|
|
6871
|
-
`${CYAN}\u2551${
|
|
6915
|
+
`${CYAN}\u2551${RESET2} ${YELLOW}\u{1F4A1}${RESET2} Approved ${localCount}\xD7 before \u2014 ${BOLD2}[a]${RESET2}${YELLOW} creates a permanent rule${RESET2}`
|
|
6872
6916
|
);
|
|
6873
6917
|
}
|
|
6874
6918
|
lines.push(
|
|
6875
|
-
`${CYAN}\u255A${
|
|
6919
|
+
`${CYAN}\u255A${RESET2}`,
|
|
6876
6920
|
``,
|
|
6877
|
-
` ${
|
|
6921
|
+
` ${BOLD2}${GREEN}[\u21B5/y]${RESET2} Allow ${BOLD2}${RED}[n]${RESET2} Deny ${BOLD2}${YELLOW}[a]${RESET2} Always Allow ${BOLD2}${CYAN}[t]${RESET2} Trust 30m`,
|
|
6878
6922
|
``
|
|
6879
6923
|
);
|
|
6880
6924
|
return lines;
|
|
@@ -6884,23 +6928,23 @@ function buildRecoveryCardLines(req) {
|
|
|
6884
6928
|
const command = typeof argsObj?.command === "string" ? argsObj.command : JSON.stringify(req.args ?? {}).replace(/\s+/g, " ").slice(0, 60);
|
|
6885
6929
|
const ruleName = req.riskMetadata?.ruleName?.replace(/^Smart Rule:\s*/i, "") ?? "policy rule";
|
|
6886
6930
|
const recoveryCommand = req.recoveryCommand;
|
|
6887
|
-
const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${
|
|
6888
|
-
` ${
|
|
6889
|
-
` ${
|
|
6890
|
-
` ${
|
|
6931
|
+
const interactiveLines = req.viewOnly ? [` ${GRAY}\u2192 Awaiting decision from interactive terminal...${RESET2}`] : [
|
|
6932
|
+
` ${BOLD2}${GREEN}[1]${RESET2} Allow anyway ${GRAY}(override policy)${RESET2}`,
|
|
6933
|
+
` ${BOLD2}${YELLOW}[2]${RESET2} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
|
|
6934
|
+
` ${BOLD2}${RED}[3]${RESET2} Deny & stop ${GRAY}(hard block)${RESET2}`,
|
|
6891
6935
|
``,
|
|
6892
|
-
` ${GRAY}[Timeout: auto-deny]${
|
|
6936
|
+
` ${GRAY}[Timeout: auto-deny]${RESET2}`,
|
|
6893
6937
|
` Select [1-3]: `
|
|
6894
6938
|
];
|
|
6895
6939
|
return [
|
|
6896
6940
|
``,
|
|
6897
|
-
`${
|
|
6898
|
-
`\u{1F6E1}\uFE0F ${
|
|
6899
|
-
`${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${
|
|
6900
|
-
`${CYAN}${DIVIDER}${
|
|
6901
|
-
...!req.viewOnly ? [`${
|
|
6941
|
+
`${BOLD2}${CYAN}${DIVIDER}${RESET2}`,
|
|
6942
|
+
`\u{1F6E1}\uFE0F ${BOLD2}NODE9 STATE GUARD:${RESET2} '${BOLD2}${command}${RESET2}'`,
|
|
6943
|
+
`${YELLOW}\u26A0\uFE0F Rule: ${ruleName}${RESET2}`,
|
|
6944
|
+
`${CYAN}${DIVIDER}${RESET2}`,
|
|
6945
|
+
...!req.viewOnly ? [`${BOLD2}What would you like to do?${RESET2}`, ``] : [],
|
|
6902
6946
|
...interactiveLines,
|
|
6903
|
-
`${CYAN}${DIVIDER}${
|
|
6947
|
+
`${CYAN}${DIVIDER}${RESET2}`,
|
|
6904
6948
|
``
|
|
6905
6949
|
];
|
|
6906
6950
|
}
|
|
@@ -6930,7 +6974,7 @@ async function startTail(options = {}) {
|
|
|
6930
6974
|
req2.end();
|
|
6931
6975
|
});
|
|
6932
6976
|
if (result.ok) {
|
|
6933
|
-
console.log(
|
|
6977
|
+
console.log(import_chalk17.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
6934
6978
|
} else if (result.code === "ECONNREFUSED") {
|
|
6935
6979
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
6936
6980
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -6949,10 +6993,10 @@ async function startTail(options = {}) {
|
|
|
6949
6993
|
let cancelActiveCard = null;
|
|
6950
6994
|
const localAllowCounts = /* @__PURE__ */ new Map();
|
|
6951
6995
|
const canApprove = process.stdout.isTTY && process.stdin.isTTY;
|
|
6952
|
-
if (canApprove)
|
|
6996
|
+
if (canApprove) import_readline4.default.emitKeypressEvents(process.stdin);
|
|
6953
6997
|
function clearCard() {
|
|
6954
6998
|
if (cardLineCount > 0) {
|
|
6955
|
-
|
|
6999
|
+
import_readline4.default.moveCursor(process.stdout, 0, -cardLineCount);
|
|
6956
7000
|
process.stdout.write(ERASE_DOWN);
|
|
6957
7001
|
cardLineCount = 0;
|
|
6958
7002
|
}
|
|
@@ -7002,8 +7046,8 @@ async function startTail(options = {}) {
|
|
|
7002
7046
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7003
7047
|
)
|
|
7004
7048
|
);
|
|
7005
|
-
const decisionStamp = action === "always-allow" ?
|
|
7006
|
-
stampedLines.push(` ${
|
|
7049
|
+
const decisionStamp = action === "always-allow" ? import_chalk17.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk17.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk17.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk17.default.yellow("\u21A9 REDIRECT AI") : import_chalk17.default.red("\u2717 DENIED");
|
|
7050
|
+
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7007
7051
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7008
7052
|
process.stdout.write(SHOW_CURSOR);
|
|
7009
7053
|
cardLineCount = 0;
|
|
@@ -7031,7 +7075,7 @@ async function startTail(options = {}) {
|
|
|
7031
7075
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err) => {
|
|
7032
7076
|
try {
|
|
7033
7077
|
import_fs24.default.appendFileSync(
|
|
7034
|
-
|
|
7078
|
+
import_path27.default.join(import_os20.default.homedir(), ".node9", "hook-debug.log"),
|
|
7035
7079
|
`[tail] POST /decision failed: ${String(err)}
|
|
7036
7080
|
`
|
|
7037
7081
|
);
|
|
@@ -7053,8 +7097,8 @@ async function startTail(options = {}) {
|
|
|
7053
7097
|
);
|
|
7054
7098
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7055
7099
|
if (externalDecision) {
|
|
7056
|
-
const source = externalDecision === "allow" ?
|
|
7057
|
-
stampedLines.push(` ${
|
|
7100
|
+
const source = externalDecision === "allow" ? import_chalk17.default.green("\u2713 ALLOWED") : import_chalk17.default.red("\u2717 DENIED");
|
|
7101
|
+
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7058
7102
|
}
|
|
7059
7103
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7060
7104
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7112,41 +7156,41 @@ async function startTail(options = {}) {
|
|
|
7112
7156
|
}
|
|
7113
7157
|
} catch {
|
|
7114
7158
|
}
|
|
7115
|
-
console.log(
|
|
7116
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
7159
|
+
console.log(import_chalk17.default.cyan.bold(`
|
|
7160
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk17.default.dim(`\u2192 ${dashboardUrl}`));
|
|
7117
7161
|
if (canApprove) {
|
|
7118
7162
|
console.log(
|
|
7119
|
-
|
|
7163
|
+
import_chalk17.default.dim("Interactive approvals: [\u21B5/y] Allow [n] Deny [a] Always Allow [t] Trust 30m")
|
|
7120
7164
|
);
|
|
7121
7165
|
}
|
|
7122
7166
|
if (options.history) {
|
|
7123
|
-
console.log(
|
|
7167
|
+
console.log(import_chalk17.default.dim("Showing history + live events. Press Ctrl+C to exit.\n"));
|
|
7124
7168
|
} else {
|
|
7125
7169
|
console.log(
|
|
7126
|
-
|
|
7170
|
+
import_chalk17.default.dim("Showing live events only. Use --history to include past. Press Ctrl+C to exit.\n")
|
|
7127
7171
|
);
|
|
7128
7172
|
}
|
|
7129
7173
|
process.on("SIGINT", () => {
|
|
7130
7174
|
clearCard();
|
|
7131
7175
|
process.stdout.write(SHOW_CURSOR);
|
|
7132
7176
|
if (process.stdout.isTTY) {
|
|
7133
|
-
|
|
7134
|
-
|
|
7177
|
+
import_readline4.default.clearLine(process.stdout, 0);
|
|
7178
|
+
import_readline4.default.cursorTo(process.stdout, 0);
|
|
7135
7179
|
}
|
|
7136
|
-
console.log(
|
|
7180
|
+
console.log(import_chalk17.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7137
7181
|
process.exit(0);
|
|
7138
7182
|
});
|
|
7139
7183
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7140
7184
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7141
7185
|
if (res.statusCode !== 200) {
|
|
7142
|
-
console.error(
|
|
7186
|
+
console.error(import_chalk17.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7143
7187
|
process.exit(1);
|
|
7144
7188
|
}
|
|
7145
7189
|
let currentEvent = "";
|
|
7146
7190
|
let currentData = "";
|
|
7147
7191
|
res.on("error", () => {
|
|
7148
7192
|
});
|
|
7149
|
-
const rl =
|
|
7193
|
+
const rl = import_readline4.default.createInterface({ input: res, crlfDelay: Infinity });
|
|
7150
7194
|
rl.on("error", () => {
|
|
7151
7195
|
});
|
|
7152
7196
|
rl.on("line", (line) => {
|
|
@@ -7166,10 +7210,10 @@ async function startTail(options = {}) {
|
|
|
7166
7210
|
clearCard();
|
|
7167
7211
|
process.stdout.write(SHOW_CURSOR);
|
|
7168
7212
|
if (process.stdout.isTTY) {
|
|
7169
|
-
|
|
7170
|
-
|
|
7213
|
+
import_readline4.default.clearLine(process.stdout, 0);
|
|
7214
|
+
import_readline4.default.cursorTo(process.stdout, 0);
|
|
7171
7215
|
}
|
|
7172
|
-
console.log(
|
|
7216
|
+
console.log(import_chalk17.default.red("\n\u274C Daemon disconnected."));
|
|
7173
7217
|
process.exit(1);
|
|
7174
7218
|
});
|
|
7175
7219
|
});
|
|
@@ -7255,26 +7299,26 @@ async function startTail(options = {}) {
|
|
|
7255
7299
|
}
|
|
7256
7300
|
req.on("error", (err) => {
|
|
7257
7301
|
const msg = err.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err.message;
|
|
7258
|
-
console.error(
|
|
7302
|
+
console.error(import_chalk17.default.red(`
|
|
7259
7303
|
\u274C ${msg}`));
|
|
7260
7304
|
process.exit(1);
|
|
7261
7305
|
});
|
|
7262
7306
|
}
|
|
7263
|
-
var import_http2,
|
|
7307
|
+
var import_http2, import_chalk17, import_fs24, import_os20, import_path27, import_readline4, import_child_process13, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
|
|
7264
7308
|
var init_tail = __esm({
|
|
7265
7309
|
"src/tui/tail.ts"() {
|
|
7266
7310
|
"use strict";
|
|
7267
7311
|
import_http2 = __toESM(require("http"));
|
|
7268
|
-
|
|
7312
|
+
import_chalk17 = __toESM(require("chalk"));
|
|
7269
7313
|
import_fs24 = __toESM(require("fs"));
|
|
7270
7314
|
import_os20 = __toESM(require("os"));
|
|
7271
|
-
|
|
7272
|
-
|
|
7315
|
+
import_path27 = __toESM(require("path"));
|
|
7316
|
+
import_readline4 = __toESM(require("readline"));
|
|
7273
7317
|
import_child_process13 = require("child_process");
|
|
7274
7318
|
init_daemon2();
|
|
7275
7319
|
init_daemon();
|
|
7276
7320
|
init_core();
|
|
7277
|
-
PID_FILE =
|
|
7321
|
+
PID_FILE = import_path27.default.join(import_os20.default.homedir(), ".node9", "daemon.pid");
|
|
7278
7322
|
ICONS = {
|
|
7279
7323
|
bash: "\u{1F4BB}",
|
|
7280
7324
|
shell: "\u{1F4BB}",
|
|
@@ -7292,8 +7336,8 @@ var init_tail = __esm({
|
|
|
7292
7336
|
delete: "\u{1F5D1}\uFE0F",
|
|
7293
7337
|
web: "\u{1F310}"
|
|
7294
7338
|
};
|
|
7295
|
-
|
|
7296
|
-
|
|
7339
|
+
RESET2 = "\x1B[0m";
|
|
7340
|
+
BOLD2 = "\x1B[1m";
|
|
7297
7341
|
RED = "\x1B[31m";
|
|
7298
7342
|
YELLOW = "\x1B[33m";
|
|
7299
7343
|
CYAN = "\x1B[36m";
|
|
@@ -7362,19 +7406,19 @@ function queryDaemon() {
|
|
|
7362
7406
|
});
|
|
7363
7407
|
}
|
|
7364
7408
|
function dim(s) {
|
|
7365
|
-
return `${DIM}${s}${
|
|
7409
|
+
return `${DIM}${s}${RESET3}`;
|
|
7366
7410
|
}
|
|
7367
7411
|
function bold(s) {
|
|
7368
|
-
return `${
|
|
7412
|
+
return `${BOLD3}${s}${RESET3}`;
|
|
7369
7413
|
}
|
|
7370
7414
|
function color(c, s) {
|
|
7371
|
-
return `${c}${s}${
|
|
7415
|
+
return `${c}${s}${RESET3}`;
|
|
7372
7416
|
}
|
|
7373
7417
|
function progressBar(pct, warnAt = 70, critAt = 85) {
|
|
7374
7418
|
const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
|
|
7375
7419
|
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
7376
7420
|
const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
|
|
7377
|
-
return `${c}${bar}${
|
|
7421
|
+
return `${c}${bar}${RESET3}`;
|
|
7378
7422
|
}
|
|
7379
7423
|
function formatTimeLeft(resetsAt) {
|
|
7380
7424
|
if (!resetsAt) return "";
|
|
@@ -7415,7 +7459,7 @@ function countRulesInDir(rulesDir) {
|
|
|
7415
7459
|
try {
|
|
7416
7460
|
for (const entry of import_fs25.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7417
7461
|
if (entry.isDirectory()) {
|
|
7418
|
-
count += countRulesInDir(
|
|
7462
|
+
count += countRulesInDir(import_path28.default.join(rulesDir, entry.name));
|
|
7419
7463
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7420
7464
|
count++;
|
|
7421
7465
|
}
|
|
@@ -7426,46 +7470,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7426
7470
|
}
|
|
7427
7471
|
function isSamePath(a, b) {
|
|
7428
7472
|
try {
|
|
7429
|
-
return
|
|
7473
|
+
return import_path28.default.resolve(a) === import_path28.default.resolve(b);
|
|
7430
7474
|
} catch {
|
|
7431
7475
|
return false;
|
|
7432
7476
|
}
|
|
7433
7477
|
}
|
|
7434
7478
|
function countConfigs(cwd) {
|
|
7435
7479
|
const homeDir2 = import_os21.default.homedir();
|
|
7436
|
-
const claudeDir =
|
|
7480
|
+
const claudeDir = import_path28.default.join(homeDir2, ".claude");
|
|
7437
7481
|
let claudeMdCount = 0;
|
|
7438
7482
|
let rulesCount = 0;
|
|
7439
7483
|
let hooksCount = 0;
|
|
7440
7484
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7441
7485
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7442
|
-
if (import_fs25.default.existsSync(
|
|
7443
|
-
rulesCount += countRulesInDir(
|
|
7444
|
-
const userSettings =
|
|
7486
|
+
if (import_fs25.default.existsSync(import_path28.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7487
|
+
rulesCount += countRulesInDir(import_path28.default.join(claudeDir, "rules"));
|
|
7488
|
+
const userSettings = import_path28.default.join(claudeDir, "settings.json");
|
|
7445
7489
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7446
7490
|
hooksCount += countHooksInFile(userSettings);
|
|
7447
|
-
const userClaudeJson =
|
|
7491
|
+
const userClaudeJson = import_path28.default.join(homeDir2, ".claude.json");
|
|
7448
7492
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7449
7493
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7450
7494
|
userMcpServers.delete(name);
|
|
7451
7495
|
}
|
|
7452
7496
|
if (cwd) {
|
|
7453
|
-
if (import_fs25.default.existsSync(
|
|
7454
|
-
if (import_fs25.default.existsSync(
|
|
7455
|
-
const projectClaudeDir =
|
|
7497
|
+
if (import_fs25.default.existsSync(import_path28.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7498
|
+
if (import_fs25.default.existsSync(import_path28.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7499
|
+
const projectClaudeDir = import_path28.default.join(cwd, ".claude");
|
|
7456
7500
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7457
7501
|
if (!overlapsUserScope) {
|
|
7458
|
-
if (import_fs25.default.existsSync(
|
|
7459
|
-
rulesCount += countRulesInDir(
|
|
7460
|
-
const projSettings =
|
|
7502
|
+
if (import_fs25.default.existsSync(import_path28.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7503
|
+
rulesCount += countRulesInDir(import_path28.default.join(projectClaudeDir, "rules"));
|
|
7504
|
+
const projSettings = import_path28.default.join(projectClaudeDir, "settings.json");
|
|
7461
7505
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7462
7506
|
hooksCount += countHooksInFile(projSettings);
|
|
7463
7507
|
}
|
|
7464
|
-
if (import_fs25.default.existsSync(
|
|
7465
|
-
const localSettings =
|
|
7508
|
+
if (import_fs25.default.existsSync(import_path28.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7509
|
+
const localSettings = import_path28.default.join(projectClaudeDir, "settings.local.json");
|
|
7466
7510
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7467
7511
|
hooksCount += countHooksInFile(localSettings);
|
|
7468
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7512
|
+
const mcpJsonServers = getMcpServerNames(import_path28.default.join(cwd, ".mcp.json"));
|
|
7469
7513
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7470
7514
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7471
7515
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7578,8 +7622,8 @@ async function main() {
|
|
|
7578
7622
|
try {
|
|
7579
7623
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7580
7624
|
for (const configPath of [
|
|
7581
|
-
|
|
7582
|
-
|
|
7625
|
+
import_path28.default.join(cwd, "node9.config.json"),
|
|
7626
|
+
import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json")
|
|
7583
7627
|
]) {
|
|
7584
7628
|
if (!import_fs25.default.existsSync(configPath)) continue;
|
|
7585
7629
|
const cfg = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
@@ -7600,17 +7644,17 @@ async function main() {
|
|
|
7600
7644
|
renderOffline();
|
|
7601
7645
|
}
|
|
7602
7646
|
}
|
|
7603
|
-
var import_fs25,
|
|
7647
|
+
var import_fs25, import_path28, import_os21, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH;
|
|
7604
7648
|
var init_hud = __esm({
|
|
7605
7649
|
"src/cli/hud.ts"() {
|
|
7606
7650
|
"use strict";
|
|
7607
7651
|
import_fs25 = __toESM(require("fs"));
|
|
7608
|
-
|
|
7652
|
+
import_path28 = __toESM(require("path"));
|
|
7609
7653
|
import_os21 = __toESM(require("os"));
|
|
7610
7654
|
import_http3 = __toESM(require("http"));
|
|
7611
7655
|
init_daemon();
|
|
7612
|
-
|
|
7613
|
-
|
|
7656
|
+
RESET3 = "\x1B[0m";
|
|
7657
|
+
BOLD3 = "\x1B[1m";
|
|
7614
7658
|
DIM = "\x1B[2m";
|
|
7615
7659
|
RED2 = "\x1B[31m";
|
|
7616
7660
|
GREEN2 = "\x1B[32m";
|
|
@@ -7631,7 +7675,7 @@ init_core();
|
|
|
7631
7675
|
|
|
7632
7676
|
// src/setup.ts
|
|
7633
7677
|
var import_fs11 = __toESM(require("fs"));
|
|
7634
|
-
var
|
|
7678
|
+
var import_path14 = __toESM(require("path"));
|
|
7635
7679
|
var import_os10 = __toESM(require("os"));
|
|
7636
7680
|
var import_chalk = __toESM(require("chalk"));
|
|
7637
7681
|
var import_prompts = require("@inquirer/prompts");
|
|
@@ -7657,7 +7701,7 @@ function readJson(filePath) {
|
|
|
7657
7701
|
return null;
|
|
7658
7702
|
}
|
|
7659
7703
|
function writeJson(filePath, data) {
|
|
7660
|
-
const dir =
|
|
7704
|
+
const dir = import_path14.default.dirname(filePath);
|
|
7661
7705
|
if (!import_fs11.default.existsSync(dir)) import_fs11.default.mkdirSync(dir, { recursive: true });
|
|
7662
7706
|
import_fs11.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
7663
7707
|
}
|
|
@@ -7667,8 +7711,8 @@ function isNode9Hook(cmd) {
|
|
|
7667
7711
|
}
|
|
7668
7712
|
function teardownClaude() {
|
|
7669
7713
|
const homeDir2 = import_os10.default.homedir();
|
|
7670
|
-
const hooksPath =
|
|
7671
|
-
const mcpPath =
|
|
7714
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
7715
|
+
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
7672
7716
|
let changed = false;
|
|
7673
7717
|
const settings = readJson(hooksPath);
|
|
7674
7718
|
if (settings?.hooks) {
|
|
@@ -7717,7 +7761,7 @@ function teardownClaude() {
|
|
|
7717
7761
|
}
|
|
7718
7762
|
function teardownGemini() {
|
|
7719
7763
|
const homeDir2 = import_os10.default.homedir();
|
|
7720
|
-
const settingsPath =
|
|
7764
|
+
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
7721
7765
|
const settings = readJson(settingsPath);
|
|
7722
7766
|
if (!settings) {
|
|
7723
7767
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -7756,7 +7800,7 @@ function teardownGemini() {
|
|
|
7756
7800
|
}
|
|
7757
7801
|
function teardownCursor() {
|
|
7758
7802
|
const homeDir2 = import_os10.default.homedir();
|
|
7759
|
-
const mcpPath =
|
|
7803
|
+
const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7760
7804
|
const mcpConfig = readJson(mcpPath);
|
|
7761
7805
|
if (!mcpConfig?.mcpServers) {
|
|
7762
7806
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -7783,8 +7827,8 @@ function teardownCursor() {
|
|
|
7783
7827
|
}
|
|
7784
7828
|
async function setupClaude() {
|
|
7785
7829
|
const homeDir2 = import_os10.default.homedir();
|
|
7786
|
-
const mcpPath =
|
|
7787
|
-
const hooksPath =
|
|
7830
|
+
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
7831
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
7788
7832
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
7789
7833
|
const settings = readJson(hooksPath) ?? {};
|
|
7790
7834
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -7859,7 +7903,7 @@ async function setupClaude() {
|
|
|
7859
7903
|
}
|
|
7860
7904
|
async function setupGemini() {
|
|
7861
7905
|
const homeDir2 = import_os10.default.homedir();
|
|
7862
|
-
const settingsPath =
|
|
7906
|
+
const settingsPath = import_path14.default.join(homeDir2, ".gemini", "settings.json");
|
|
7863
7907
|
const settings = readJson(settingsPath) ?? {};
|
|
7864
7908
|
const servers = settings.mcpServers ?? {};
|
|
7865
7909
|
let anythingChanged = false;
|
|
@@ -7954,14 +7998,14 @@ function detectAgents(homeDir2 = import_os10.default.homedir()) {
|
|
|
7954
7998
|
}
|
|
7955
7999
|
};
|
|
7956
8000
|
return {
|
|
7957
|
-
claude: exists(
|
|
7958
|
-
gemini: exists(
|
|
7959
|
-
cursor: exists(
|
|
8001
|
+
claude: exists(import_path14.default.join(homeDir2, ".claude")) || exists(import_path14.default.join(homeDir2, ".claude.json")),
|
|
8002
|
+
gemini: exists(import_path14.default.join(homeDir2, ".gemini")),
|
|
8003
|
+
cursor: exists(import_path14.default.join(homeDir2, ".cursor"))
|
|
7960
8004
|
};
|
|
7961
8005
|
}
|
|
7962
8006
|
async function setupCursor() {
|
|
7963
8007
|
const homeDir2 = import_os10.default.homedir();
|
|
7964
|
-
const mcpPath =
|
|
8008
|
+
const mcpPath = import_path14.default.join(homeDir2, ".cursor", "mcp.json");
|
|
7965
8009
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
7966
8010
|
const servers = mcpConfig.mcpServers ?? {};
|
|
7967
8011
|
let anythingChanged = false;
|
|
@@ -8016,7 +8060,7 @@ async function setupCursor() {
|
|
|
8016
8060
|
}
|
|
8017
8061
|
function setupHud() {
|
|
8018
8062
|
const homeDir2 = import_os10.default.homedir();
|
|
8019
|
-
const hooksPath =
|
|
8063
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
8020
8064
|
const settings = readJson(hooksPath) ?? {};
|
|
8021
8065
|
const hudCommand = fullPathCommand("hud");
|
|
8022
8066
|
const statusLineObj = { type: "command", command: hudCommand };
|
|
@@ -8043,7 +8087,7 @@ function setupHud() {
|
|
|
8043
8087
|
}
|
|
8044
8088
|
function teardownHud() {
|
|
8045
8089
|
const homeDir2 = import_os10.default.homedir();
|
|
8046
|
-
const hooksPath =
|
|
8090
|
+
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
8047
8091
|
const settings = readJson(hooksPath);
|
|
8048
8092
|
if (!settings) {
|
|
8049
8093
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.claude/settings.json not found \u2014 nothing to remove"));
|
|
@@ -8063,11 +8107,11 @@ function teardownHud() {
|
|
|
8063
8107
|
|
|
8064
8108
|
// src/cli.ts
|
|
8065
8109
|
init_daemon2();
|
|
8066
|
-
var
|
|
8110
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
8067
8111
|
var import_fs26 = __toESM(require("fs"));
|
|
8068
|
-
var
|
|
8112
|
+
var import_path29 = __toESM(require("path"));
|
|
8069
8113
|
var import_os22 = __toESM(require("os"));
|
|
8070
|
-
var
|
|
8114
|
+
var import_prompts2 = require("@inquirer/prompts");
|
|
8071
8115
|
|
|
8072
8116
|
// src/utils/duration.ts
|
|
8073
8117
|
function parseDuration(str) {
|
|
@@ -8258,6 +8302,7 @@ function openBrowserLocal() {
|
|
|
8258
8302
|
}
|
|
8259
8303
|
}
|
|
8260
8304
|
async function autoStartDaemonAndWait() {
|
|
8305
|
+
if (process.env.NODE9_TESTING === "1") return false;
|
|
8261
8306
|
try {
|
|
8262
8307
|
const child = (0, import_child_process7.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
8263
8308
|
detached: true,
|
|
@@ -8289,7 +8334,7 @@ async function autoStartDaemonAndWait() {
|
|
|
8289
8334
|
// src/cli/commands/check.ts
|
|
8290
8335
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8291
8336
|
var import_fs18 = __toESM(require("fs"));
|
|
8292
|
-
var
|
|
8337
|
+
var import_path20 = __toESM(require("path"));
|
|
8293
8338
|
var import_os14 = __toESM(require("os"));
|
|
8294
8339
|
init_orchestrator();
|
|
8295
8340
|
init_daemon();
|
|
@@ -8300,10 +8345,10 @@ init_policy();
|
|
|
8300
8345
|
var import_child_process8 = require("child_process");
|
|
8301
8346
|
var import_crypto7 = __toESM(require("crypto"));
|
|
8302
8347
|
var import_fs17 = __toESM(require("fs"));
|
|
8303
|
-
var
|
|
8348
|
+
var import_path19 = __toESM(require("path"));
|
|
8304
8349
|
var import_os13 = __toESM(require("os"));
|
|
8305
|
-
var SNAPSHOT_STACK_PATH =
|
|
8306
|
-
var UNDO_LATEST_PATH =
|
|
8350
|
+
var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots.json");
|
|
8351
|
+
var UNDO_LATEST_PATH = import_path19.default.join(import_os13.default.homedir(), ".node9", "undo_latest.txt");
|
|
8307
8352
|
var MAX_SNAPSHOTS = 10;
|
|
8308
8353
|
var GIT_TIMEOUT = 15e3;
|
|
8309
8354
|
function readStack() {
|
|
@@ -8315,20 +8360,37 @@ function readStack() {
|
|
|
8315
8360
|
return [];
|
|
8316
8361
|
}
|
|
8317
8362
|
function writeStack(stack) {
|
|
8318
|
-
const dir =
|
|
8363
|
+
const dir = import_path19.default.dirname(SNAPSHOT_STACK_PATH);
|
|
8319
8364
|
if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
|
|
8320
8365
|
import_fs17.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
8321
8366
|
}
|
|
8367
|
+
function extractFilePath(args) {
|
|
8368
|
+
if (!args || typeof args !== "object") return null;
|
|
8369
|
+
const a = args;
|
|
8370
|
+
const fp = a.file_path ?? a.path ?? a.filename;
|
|
8371
|
+
return typeof fp === "string" ? fp : null;
|
|
8372
|
+
}
|
|
8322
8373
|
function buildArgsSummary(tool, args) {
|
|
8374
|
+
const filePath = extractFilePath(args);
|
|
8375
|
+
if (filePath) return filePath;
|
|
8323
8376
|
if (!args || typeof args !== "object") return "";
|
|
8324
8377
|
const a = args;
|
|
8325
|
-
const filePath = a.file_path ?? a.path ?? a.filename;
|
|
8326
|
-
if (typeof filePath === "string") return filePath;
|
|
8327
8378
|
const cmd = a.command ?? a.cmd;
|
|
8328
8379
|
if (typeof cmd === "string") return cmd.slice(0, 80);
|
|
8329
8380
|
const sql = a.sql ?? a.query;
|
|
8330
8381
|
if (typeof sql === "string") return sql.slice(0, 80);
|
|
8331
|
-
return
|
|
8382
|
+
return "";
|
|
8383
|
+
}
|
|
8384
|
+
function findProjectRoot(filePath) {
|
|
8385
|
+
let dir = import_path19.default.dirname(filePath);
|
|
8386
|
+
while (true) {
|
|
8387
|
+
if (import_fs17.default.existsSync(import_path19.default.join(dir, ".git")) || import_fs17.default.existsSync(import_path19.default.join(dir, "package.json"))) {
|
|
8388
|
+
return dir;
|
|
8389
|
+
}
|
|
8390
|
+
const parent = import_path19.default.dirname(dir);
|
|
8391
|
+
if (parent === dir) return process.cwd();
|
|
8392
|
+
dir = parent;
|
|
8393
|
+
}
|
|
8332
8394
|
}
|
|
8333
8395
|
function normalizeCwdForHash(cwd) {
|
|
8334
8396
|
let normalized;
|
|
@@ -8343,14 +8405,14 @@ function normalizeCwdForHash(cwd) {
|
|
|
8343
8405
|
}
|
|
8344
8406
|
function getShadowRepoDir(cwd) {
|
|
8345
8407
|
const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
8346
|
-
return
|
|
8408
|
+
return import_path19.default.join(import_os13.default.homedir(), ".node9", "snapshots", hash);
|
|
8347
8409
|
}
|
|
8348
8410
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
8349
8411
|
try {
|
|
8350
8412
|
const cutoff = Date.now() - 6e4;
|
|
8351
8413
|
for (const f of import_fs17.default.readdirSync(shadowDir)) {
|
|
8352
8414
|
if (f.startsWith("index_")) {
|
|
8353
|
-
const fp =
|
|
8415
|
+
const fp = import_path19.default.join(shadowDir, f);
|
|
8354
8416
|
try {
|
|
8355
8417
|
if (import_fs17.default.statSync(fp).mtimeMs < cutoff) import_fs17.default.unlinkSync(fp);
|
|
8356
8418
|
} catch {
|
|
@@ -8364,7 +8426,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
8364
8426
|
const hardcoded = [".git", ".node9"];
|
|
8365
8427
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
8366
8428
|
try {
|
|
8367
|
-
import_fs17.default.writeFileSync(
|
|
8429
|
+
import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
8368
8430
|
} catch {
|
|
8369
8431
|
}
|
|
8370
8432
|
}
|
|
@@ -8377,7 +8439,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8377
8439
|
timeout: 3e3
|
|
8378
8440
|
});
|
|
8379
8441
|
if (check.status === 0) {
|
|
8380
|
-
const ptPath =
|
|
8442
|
+
const ptPath = import_path19.default.join(shadowDir, "project-path.txt");
|
|
8381
8443
|
try {
|
|
8382
8444
|
const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
|
|
8383
8445
|
if (stored === normalizedCwd) return true;
|
|
@@ -8399,12 +8461,12 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8399
8461
|
} catch {
|
|
8400
8462
|
}
|
|
8401
8463
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
8402
|
-
if (init.status !== 0) {
|
|
8403
|
-
|
|
8404
|
-
|
|
8464
|
+
if (init.status !== 0 || init.error) {
|
|
8465
|
+
const reason = init.error ? init.error.message : init.stderr?.toString();
|
|
8466
|
+
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
8405
8467
|
return false;
|
|
8406
8468
|
}
|
|
8407
|
-
const configFile =
|
|
8469
|
+
const configFile = import_path19.default.join(shadowDir, "config");
|
|
8408
8470
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
8409
8471
|
timeout: 3e3
|
|
8410
8472
|
});
|
|
@@ -8412,7 +8474,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
8412
8474
|
timeout: 3e3
|
|
8413
8475
|
});
|
|
8414
8476
|
try {
|
|
8415
|
-
import_fs17.default.writeFileSync(
|
|
8477
|
+
import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
8416
8478
|
} catch {
|
|
8417
8479
|
}
|
|
8418
8480
|
return true;
|
|
@@ -8431,11 +8493,13 @@ function buildGitEnv(cwd) {
|
|
|
8431
8493
|
async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
|
|
8432
8494
|
let indexFile = null;
|
|
8433
8495
|
try {
|
|
8434
|
-
const
|
|
8496
|
+
const rawFilePath = extractFilePath(args);
|
|
8497
|
+
const absFilePath = rawFilePath && import_path19.default.isAbsolute(rawFilePath) ? rawFilePath : null;
|
|
8498
|
+
const cwd = absFilePath ? findProjectRoot(absFilePath) : process.cwd();
|
|
8435
8499
|
const shadowDir = getShadowRepoDir(cwd);
|
|
8436
8500
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
8437
8501
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
8438
|
-
indexFile =
|
|
8502
|
+
indexFile = import_path19.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
8439
8503
|
const shadowEnv = {
|
|
8440
8504
|
...process.env,
|
|
8441
8505
|
GIT_DIR: shadowDir,
|
|
@@ -8454,15 +8518,53 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
8454
8518
|
const commitHash = commitRes.stdout?.toString().trim();
|
|
8455
8519
|
if (!commitHash || commitRes.status !== 0) return null;
|
|
8456
8520
|
const stack = readStack();
|
|
8521
|
+
const prevEntry = [...stack].reverse().find((e) => e.cwd === cwd);
|
|
8522
|
+
let capturedFiles = [];
|
|
8523
|
+
let capturedDiff = null;
|
|
8524
|
+
if (prevEntry) {
|
|
8525
|
+
const filesRes = (0, import_child_process8.spawnSync)("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
|
|
8526
|
+
env: shadowEnv,
|
|
8527
|
+
timeout: GIT_TIMEOUT
|
|
8528
|
+
});
|
|
8529
|
+
if (filesRes.status === 0) {
|
|
8530
|
+
capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8531
|
+
}
|
|
8532
|
+
const diffRes = (0, import_child_process8.spawnSync)("git", ["diff", prevEntry.hash, commitHash], {
|
|
8533
|
+
env: shadowEnv,
|
|
8534
|
+
timeout: GIT_TIMEOUT
|
|
8535
|
+
});
|
|
8536
|
+
if (diffRes.status === 0) {
|
|
8537
|
+
capturedDiff = diffRes.stdout?.toString() || null;
|
|
8538
|
+
}
|
|
8539
|
+
} else {
|
|
8540
|
+
const filesRes = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", commitHash], {
|
|
8541
|
+
env: shadowEnv,
|
|
8542
|
+
timeout: GIT_TIMEOUT
|
|
8543
|
+
});
|
|
8544
|
+
if (filesRes.status === 0) {
|
|
8545
|
+
capturedFiles = filesRes.stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8546
|
+
}
|
|
8547
|
+
capturedDiff = null;
|
|
8548
|
+
}
|
|
8457
8549
|
stack.push({
|
|
8458
8550
|
hash: commitHash,
|
|
8459
8551
|
tool,
|
|
8460
8552
|
argsSummary: buildArgsSummary(tool, args),
|
|
8553
|
+
files: capturedFiles,
|
|
8554
|
+
diff: capturedDiff,
|
|
8461
8555
|
cwd,
|
|
8462
8556
|
timestamp: Date.now()
|
|
8463
8557
|
});
|
|
8464
8558
|
const shouldGc = stack.length % 5 === 0;
|
|
8465
|
-
|
|
8559
|
+
let cwdCount = 0;
|
|
8560
|
+
let oldestCwdIdx = -1;
|
|
8561
|
+
for (let i = 0; i < stack.length; i++) {
|
|
8562
|
+
if (stack[i].cwd === cwd) {
|
|
8563
|
+
if (oldestCwdIdx === -1) oldestCwdIdx = i;
|
|
8564
|
+
cwdCount++;
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8567
|
+
if (cwdCount > MAX_SNAPSHOTS) stack.splice(oldestCwdIdx, 1);
|
|
8466
8568
|
writeStack(stack);
|
|
8467
8569
|
import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
8468
8570
|
if (shouldGc) {
|
|
@@ -8518,14 +8620,21 @@ function applyUndo(hash, cwd) {
|
|
|
8518
8620
|
env,
|
|
8519
8621
|
timeout: GIT_TIMEOUT
|
|
8520
8622
|
});
|
|
8521
|
-
if (restore.status !== 0)
|
|
8623
|
+
if (restore.status !== 0 || restore.error) {
|
|
8624
|
+
if (process.env.NODE9_DEBUG === "1") {
|
|
8625
|
+
const msg = restore.error ? restore.error.message : restore.stderr?.toString();
|
|
8626
|
+
console.error("[Node9] git restore failed:", msg);
|
|
8627
|
+
}
|
|
8628
|
+
return false;
|
|
8629
|
+
}
|
|
8522
8630
|
const lsTree = (0, import_child_process8.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
8523
8631
|
cwd: dir,
|
|
8524
8632
|
env,
|
|
8525
8633
|
timeout: GIT_TIMEOUT
|
|
8526
8634
|
});
|
|
8527
8635
|
if (lsTree.status !== 0) {
|
|
8528
|
-
|
|
8636
|
+
const errorMsg = lsTree.stderr?.toString() || "Unknown git error";
|
|
8637
|
+
process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}: ${errorMsg}
|
|
8529
8638
|
`);
|
|
8530
8639
|
return false;
|
|
8531
8640
|
}
|
|
@@ -8544,7 +8653,7 @@ function applyUndo(hash, cwd) {
|
|
|
8544
8653
|
timeout: GIT_TIMEOUT
|
|
8545
8654
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
8546
8655
|
for (const file of [...tracked, ...untracked]) {
|
|
8547
|
-
const fullPath =
|
|
8656
|
+
const fullPath = import_path19.default.join(dir, file);
|
|
8548
8657
|
if (!snapshotFiles.has(file) && import_fs17.default.existsSync(fullPath)) {
|
|
8549
8658
|
import_fs17.default.unlinkSync(fullPath);
|
|
8550
8659
|
}
|
|
@@ -8570,7 +8679,7 @@ function registerCheckCommand(program2) {
|
|
|
8570
8679
|
} catch (err) {
|
|
8571
8680
|
const tempConfig = getConfig();
|
|
8572
8681
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
8573
|
-
const logPath =
|
|
8682
|
+
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
8574
8683
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
8575
8684
|
import_fs18.default.appendFileSync(
|
|
8576
8685
|
logPath,
|
|
@@ -8583,9 +8692,9 @@ RAW: ${raw}
|
|
|
8583
8692
|
}
|
|
8584
8693
|
const config = getConfig(payload.cwd || void 0);
|
|
8585
8694
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
8586
|
-
const logPath =
|
|
8587
|
-
if (!import_fs18.default.existsSync(
|
|
8588
|
-
import_fs18.default.mkdirSync(
|
|
8695
|
+
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
8696
|
+
if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
|
|
8697
|
+
import_fs18.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
|
|
8589
8698
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
8590
8699
|
`);
|
|
8591
8700
|
}
|
|
@@ -8650,7 +8759,7 @@ RAW: ${raw}
|
|
|
8650
8759
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
8651
8760
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
8652
8761
|
}
|
|
8653
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
8762
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
8654
8763
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
8655
8764
|
cwd: safeCwdForAuth
|
|
8656
8765
|
});
|
|
@@ -8694,7 +8803,7 @@ RAW: ${raw}
|
|
|
8694
8803
|
});
|
|
8695
8804
|
} catch (err) {
|
|
8696
8805
|
if (process.env.NODE9_DEBUG === "1") {
|
|
8697
|
-
const logPath =
|
|
8806
|
+
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
8698
8807
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
8699
8808
|
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
8700
8809
|
`);
|
|
@@ -8731,7 +8840,7 @@ RAW: ${raw}
|
|
|
8731
8840
|
|
|
8732
8841
|
// src/cli/commands/log.ts
|
|
8733
8842
|
var import_fs19 = __toESM(require("fs"));
|
|
8734
|
-
var
|
|
8843
|
+
var import_path21 = __toESM(require("path"));
|
|
8735
8844
|
var import_os15 = __toESM(require("os"));
|
|
8736
8845
|
init_audit();
|
|
8737
8846
|
init_config();
|
|
@@ -8808,9 +8917,9 @@ function registerLogCommand(program2) {
|
|
|
8808
8917
|
decision: "allowed",
|
|
8809
8918
|
source: "post-hook"
|
|
8810
8919
|
};
|
|
8811
|
-
const logPath =
|
|
8812
|
-
if (!import_fs19.default.existsSync(
|
|
8813
|
-
import_fs19.default.mkdirSync(
|
|
8920
|
+
const logPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "audit.log");
|
|
8921
|
+
if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
|
|
8922
|
+
import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
|
|
8814
8923
|
import_fs19.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
8815
8924
|
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
8816
8925
|
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
@@ -8836,7 +8945,7 @@ function registerLogCommand(program2) {
|
|
|
8836
8945
|
}
|
|
8837
8946
|
}
|
|
8838
8947
|
}
|
|
8839
|
-
const safeCwd = typeof payload.cwd === "string" &&
|
|
8948
|
+
const safeCwd = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
8840
8949
|
const config = getConfig(safeCwd);
|
|
8841
8950
|
if (shouldSnapshot(tool, {}, config)) {
|
|
8842
8951
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -8845,7 +8954,7 @@ function registerLogCommand(program2) {
|
|
|
8845
8954
|
const msg = err instanceof Error ? err.message : String(err);
|
|
8846
8955
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
8847
8956
|
`);
|
|
8848
|
-
const debugPath =
|
|
8957
|
+
const debugPath = import_path21.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
8849
8958
|
try {
|
|
8850
8959
|
import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
8851
8960
|
`);
|
|
@@ -9152,7 +9261,7 @@ function registerConfigShowCommand(program2) {
|
|
|
9152
9261
|
// src/cli/commands/doctor.ts
|
|
9153
9262
|
var import_chalk7 = __toESM(require("chalk"));
|
|
9154
9263
|
var import_fs20 = __toESM(require("fs"));
|
|
9155
|
-
var
|
|
9264
|
+
var import_path22 = __toESM(require("path"));
|
|
9156
9265
|
var import_os16 = __toESM(require("os"));
|
|
9157
9266
|
var import_child_process9 = require("child_process");
|
|
9158
9267
|
init_daemon();
|
|
@@ -9207,7 +9316,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9207
9316
|
);
|
|
9208
9317
|
}
|
|
9209
9318
|
section("Configuration");
|
|
9210
|
-
const globalConfigPath =
|
|
9319
|
+
const globalConfigPath = import_path22.default.join(homeDir2, ".node9", "config.json");
|
|
9211
9320
|
if (import_fs20.default.existsSync(globalConfigPath)) {
|
|
9212
9321
|
try {
|
|
9213
9322
|
JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
|
|
@@ -9218,7 +9327,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9218
9327
|
} else {
|
|
9219
9328
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
9220
9329
|
}
|
|
9221
|
-
const projectConfigPath =
|
|
9330
|
+
const projectConfigPath = import_path22.default.join(process.cwd(), "node9.config.json");
|
|
9222
9331
|
if (import_fs20.default.existsSync(projectConfigPath)) {
|
|
9223
9332
|
try {
|
|
9224
9333
|
JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
|
|
@@ -9230,7 +9339,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9230
9339
|
);
|
|
9231
9340
|
}
|
|
9232
9341
|
}
|
|
9233
|
-
const credsPath =
|
|
9342
|
+
const credsPath = import_path22.default.join(homeDir2, ".node9", "credentials.json");
|
|
9234
9343
|
if (import_fs20.default.existsSync(credsPath)) {
|
|
9235
9344
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
9236
9345
|
} else {
|
|
@@ -9240,7 +9349,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9240
9349
|
);
|
|
9241
9350
|
}
|
|
9242
9351
|
section("Agent Hooks");
|
|
9243
|
-
const claudeSettingsPath =
|
|
9352
|
+
const claudeSettingsPath = import_path22.default.join(homeDir2, ".claude", "settings.json");
|
|
9244
9353
|
if (import_fs20.default.existsSync(claudeSettingsPath)) {
|
|
9245
9354
|
try {
|
|
9246
9355
|
const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
@@ -9259,7 +9368,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9259
9368
|
} else {
|
|
9260
9369
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
9261
9370
|
}
|
|
9262
|
-
const geminiSettingsPath =
|
|
9371
|
+
const geminiSettingsPath = import_path22.default.join(homeDir2, ".gemini", "settings.json");
|
|
9263
9372
|
if (import_fs20.default.existsSync(geminiSettingsPath)) {
|
|
9264
9373
|
try {
|
|
9265
9374
|
const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
@@ -9278,7 +9387,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9278
9387
|
} else {
|
|
9279
9388
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
9280
9389
|
}
|
|
9281
|
-
const cursorHooksPath =
|
|
9390
|
+
const cursorHooksPath = import_path22.default.join(homeDir2, ".cursor", "hooks.json");
|
|
9282
9391
|
if (import_fs20.default.existsSync(cursorHooksPath)) {
|
|
9283
9392
|
try {
|
|
9284
9393
|
const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
@@ -9320,7 +9429,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9320
9429
|
// src/cli/commands/audit.ts
|
|
9321
9430
|
var import_chalk8 = __toESM(require("chalk"));
|
|
9322
9431
|
var import_fs21 = __toESM(require("fs"));
|
|
9323
|
-
var
|
|
9432
|
+
var import_path23 = __toESM(require("path"));
|
|
9324
9433
|
var import_os17 = __toESM(require("os"));
|
|
9325
9434
|
function formatRelativeTime(timestamp) {
|
|
9326
9435
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
@@ -9334,7 +9443,7 @@ function formatRelativeTime(timestamp) {
|
|
|
9334
9443
|
}
|
|
9335
9444
|
function registerAuditCommand(program2) {
|
|
9336
9445
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
9337
|
-
const logPath =
|
|
9446
|
+
const logPath = import_path23.default.join(import_os17.default.homedir(), ".node9", "audit.log");
|
|
9338
9447
|
if (!import_fs21.default.existsSync(logPath)) {
|
|
9339
9448
|
console.log(
|
|
9340
9449
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
@@ -9460,7 +9569,7 @@ function registerDaemonCommand(program2) {
|
|
|
9460
9569
|
// src/cli/commands/status.ts
|
|
9461
9570
|
var import_chalk10 = __toESM(require("chalk"));
|
|
9462
9571
|
var import_fs22 = __toESM(require("fs"));
|
|
9463
|
-
var
|
|
9572
|
+
var import_path24 = __toESM(require("path"));
|
|
9464
9573
|
var import_os18 = __toESM(require("os"));
|
|
9465
9574
|
init_core();
|
|
9466
9575
|
init_daemon();
|
|
@@ -9531,8 +9640,8 @@ function registerStatusCommand(program2) {
|
|
|
9531
9640
|
console.log("");
|
|
9532
9641
|
const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
|
|
9533
9642
|
console.log(` Mode: ${modeLabel}`);
|
|
9534
|
-
const projectConfig =
|
|
9535
|
-
const globalConfig =
|
|
9643
|
+
const projectConfig = import_path24.default.join(process.cwd(), "node9.config.json");
|
|
9644
|
+
const globalConfig = import_path24.default.join(import_os18.default.homedir(), ".node9", "config.json");
|
|
9536
9645
|
console.log(
|
|
9537
9646
|
` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
|
|
9538
9647
|
);
|
|
@@ -9546,13 +9655,13 @@ function registerStatusCommand(program2) {
|
|
|
9546
9655
|
}
|
|
9547
9656
|
const homeDir2 = import_os18.default.homedir();
|
|
9548
9657
|
const claudeSettings = readJson2(
|
|
9549
|
-
|
|
9658
|
+
import_path24.default.join(homeDir2, ".claude", "settings.json")
|
|
9550
9659
|
);
|
|
9551
|
-
const claudeConfig = readJson2(
|
|
9660
|
+
const claudeConfig = readJson2(import_path24.default.join(homeDir2, ".claude.json"));
|
|
9552
9661
|
const geminiSettings = readJson2(
|
|
9553
|
-
|
|
9662
|
+
import_path24.default.join(homeDir2, ".gemini", "settings.json")
|
|
9554
9663
|
);
|
|
9555
|
-
const cursorConfig = readJson2(
|
|
9664
|
+
const cursorConfig = readJson2(import_path24.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
9556
9665
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
9557
9666
|
if (agentFound) {
|
|
9558
9667
|
console.log("");
|
|
@@ -9612,13 +9721,13 @@ function registerStatusCommand(program2) {
|
|
|
9612
9721
|
// src/cli/commands/init.ts
|
|
9613
9722
|
var import_chalk11 = __toESM(require("chalk"));
|
|
9614
9723
|
var import_fs23 = __toESM(require("fs"));
|
|
9615
|
-
var
|
|
9724
|
+
var import_path25 = __toESM(require("path"));
|
|
9616
9725
|
var import_os19 = __toESM(require("os"));
|
|
9617
9726
|
init_core();
|
|
9618
9727
|
function registerInitCommand(program2) {
|
|
9619
9728
|
program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
|
|
9620
9729
|
console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
9621
|
-
const configPath =
|
|
9730
|
+
const configPath = import_path25.default.join(import_os19.default.homedir(), ".node9", "config.json");
|
|
9622
9731
|
if (import_fs23.default.existsSync(configPath) && !options.force) {
|
|
9623
9732
|
console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
9624
9733
|
} else {
|
|
@@ -9628,7 +9737,7 @@ function registerInitCommand(program2) {
|
|
|
9628
9737
|
...DEFAULT_CONFIG,
|
|
9629
9738
|
settings: { ...DEFAULT_CONFIG.settings, mode: safeMode }
|
|
9630
9739
|
};
|
|
9631
|
-
const dir =
|
|
9740
|
+
const dir = import_path25.default.dirname(configPath);
|
|
9632
9741
|
if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
|
|
9633
9742
|
import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
9634
9743
|
console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
|
|
@@ -9659,106 +9768,305 @@ function registerInitCommand(program2) {
|
|
|
9659
9768
|
else if (agent === "cursor") await setupCursor();
|
|
9660
9769
|
console.log("");
|
|
9661
9770
|
}
|
|
9771
|
+
if (detected.claude) {
|
|
9772
|
+
setupHud();
|
|
9773
|
+
console.log(import_chalk11.default.green("\u2705 node9 HUD added to Claude Code statusline"));
|
|
9774
|
+
console.log(import_chalk11.default.gray(" Restart Claude Code to activate the security statusline."));
|
|
9775
|
+
console.log("");
|
|
9776
|
+
}
|
|
9662
9777
|
console.log(import_chalk11.default.green.bold("\u{1F6E1}\uFE0F Node9 is ready!"));
|
|
9663
9778
|
console.log(import_chalk11.default.gray(" Run: node9 daemon start"));
|
|
9664
9779
|
});
|
|
9665
9780
|
}
|
|
9666
9781
|
|
|
9667
9782
|
// src/cli/commands/undo.ts
|
|
9783
|
+
var import_path26 = __toESM(require("path"));
|
|
9784
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
9785
|
+
|
|
9786
|
+
// src/tui/undo-navigator.ts
|
|
9787
|
+
var import_readline2 = __toESM(require("readline"));
|
|
9668
9788
|
var import_chalk12 = __toESM(require("chalk"));
|
|
9669
|
-
var
|
|
9789
|
+
var RESET = "\x1B[0m";
|
|
9790
|
+
var BOLD = "\x1B[1m";
|
|
9791
|
+
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
9792
|
+
var SESSION_GAP_MS = 6e4;
|
|
9793
|
+
function formatAge(timestamp) {
|
|
9794
|
+
const age = Math.round((Date.now() - timestamp) / 1e3);
|
|
9795
|
+
if (age < 60) return `${age}s ago`;
|
|
9796
|
+
if (age < 3600) return `${Math.round(age / 60)}m ago`;
|
|
9797
|
+
if (age < 86400) return `${Math.round(age / 3600)}h ago`;
|
|
9798
|
+
return `${Math.round(age / 86400)}d ago`;
|
|
9799
|
+
}
|
|
9800
|
+
function renderDiff(raw) {
|
|
9801
|
+
const lines = raw.split("\n").filter(
|
|
9802
|
+
(l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
|
|
9803
|
+
);
|
|
9804
|
+
for (const line of lines) {
|
|
9805
|
+
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
9806
|
+
process.stdout.write(import_chalk12.default.bold(line) + "\n");
|
|
9807
|
+
} else if (line.startsWith("+")) {
|
|
9808
|
+
process.stdout.write(import_chalk12.default.green(line) + "\n");
|
|
9809
|
+
} else if (line.startsWith("-")) {
|
|
9810
|
+
process.stdout.write(import_chalk12.default.red(line) + "\n");
|
|
9811
|
+
} else if (line.startsWith("@@")) {
|
|
9812
|
+
process.stdout.write(import_chalk12.default.cyan(line) + "\n");
|
|
9813
|
+
} else {
|
|
9814
|
+
process.stdout.write(import_chalk12.default.gray(line) + "\n");
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
function isSessionBoundary(entries, idx) {
|
|
9819
|
+
if (idx <= 0) return false;
|
|
9820
|
+
return entries[idx - 1].timestamp - entries[idx].timestamp > SESSION_GAP_MS;
|
|
9821
|
+
}
|
|
9822
|
+
function sessionStart(entries, idx) {
|
|
9823
|
+
let i = idx;
|
|
9824
|
+
while (i > 0 && !isSessionBoundary(entries, i)) i--;
|
|
9825
|
+
return i;
|
|
9826
|
+
}
|
|
9827
|
+
function render(entries, idx) {
|
|
9828
|
+
const entry = entries[idx];
|
|
9829
|
+
const total = entries.length;
|
|
9830
|
+
const step = idx + 1;
|
|
9831
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
9832
|
+
process.stdout.write(
|
|
9833
|
+
import_chalk12.default.magenta.bold(`\u23EA Node9 Undo`) + import_chalk12.default.gray(` \u2500\u2500 step ${step} of ${total}`) + (entry.files?.length ? import_chalk12.default.gray(
|
|
9834
|
+
` \u2500\u2500 ${entry.files.slice(0, 2).join(", ")}${entry.files.length > 2 ? ` +${entry.files.length - 2} more` : ""}`
|
|
9835
|
+
) : "") + "\n\n"
|
|
9836
|
+
);
|
|
9837
|
+
process.stdout.write(
|
|
9838
|
+
` ${BOLD}Tool:${RESET} ${import_chalk12.default.cyan(entry.tool)}` + (entry.argsSummary ? import_chalk12.default.gray(" \u2192 " + entry.argsSummary) : "") + "\n"
|
|
9839
|
+
);
|
|
9840
|
+
process.stdout.write(` ${BOLD}When:${RESET} ${import_chalk12.default.gray(formatAge(entry.timestamp))}
|
|
9841
|
+
`);
|
|
9842
|
+
process.stdout.write(` ${BOLD}Dir: ${RESET} ${import_chalk12.default.gray(entry.cwd)}
|
|
9843
|
+
`);
|
|
9844
|
+
if (entry.files && entry.files.length > 0) {
|
|
9845
|
+
process.stdout.write(` ${BOLD}Files:${RESET} ${import_chalk12.default.gray(entry.files.join(", "))}
|
|
9846
|
+
`);
|
|
9847
|
+
}
|
|
9848
|
+
if (idx < total - 1 && isSessionBoundary(entries, idx + 1)) {
|
|
9849
|
+
process.stdout.write(import_chalk12.default.gray("\n \u2500\u2500 session boundary above \u2500\u2500\n"));
|
|
9850
|
+
}
|
|
9851
|
+
process.stdout.write("\n");
|
|
9852
|
+
const diff = entry.diff ?? computeUndoDiff(entry.hash, entry.cwd);
|
|
9853
|
+
if (diff) {
|
|
9854
|
+
renderDiff(diff);
|
|
9855
|
+
} else {
|
|
9856
|
+
process.stdout.write(
|
|
9857
|
+
import_chalk12.default.gray(" (no diff \u2014 working tree may already match this snapshot)\n")
|
|
9858
|
+
);
|
|
9859
|
+
}
|
|
9860
|
+
process.stdout.write("\n");
|
|
9861
|
+
process.stdout.write(
|
|
9862
|
+
import_chalk12.default.gray(" ") + (idx < total - 1 ? import_chalk12.default.white("[\u2190] older") : import_chalk12.default.gray("[\u2190] older")) + import_chalk12.default.gray(" ") + (idx > 0 ? import_chalk12.default.white("[\u2192] newer") : import_chalk12.default.gray("[\u2192] newer")) + import_chalk12.default.gray(" ") + import_chalk12.default.green("[\u21B5] restore here") + import_chalk12.default.gray(" ") + import_chalk12.default.yellow("[s] session start") + import_chalk12.default.gray(" ") + import_chalk12.default.gray("[q] quit") + "\n"
|
|
9863
|
+
);
|
|
9864
|
+
}
|
|
9865
|
+
async function runUndoNavigator(entries) {
|
|
9866
|
+
if (entries.length === 0) return { restored: false };
|
|
9867
|
+
const display = [...entries].reverse();
|
|
9868
|
+
let idx = 0;
|
|
9869
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
9870
|
+
render(display, idx);
|
|
9871
|
+
return { restored: false };
|
|
9872
|
+
}
|
|
9873
|
+
import_readline2.default.emitKeypressEvents(process.stdin);
|
|
9874
|
+
return new Promise((resolve) => {
|
|
9875
|
+
let done = false;
|
|
9876
|
+
render(display, idx);
|
|
9877
|
+
try {
|
|
9878
|
+
process.stdin.setRawMode(true);
|
|
9879
|
+
} catch {
|
|
9880
|
+
resolve({ restored: false });
|
|
9881
|
+
return;
|
|
9882
|
+
}
|
|
9883
|
+
process.stdin.resume();
|
|
9884
|
+
const cleanup = () => {
|
|
9885
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
9886
|
+
try {
|
|
9887
|
+
process.stdin.setRawMode(false);
|
|
9888
|
+
} catch {
|
|
9889
|
+
}
|
|
9890
|
+
process.stdin.pause();
|
|
9891
|
+
};
|
|
9892
|
+
const onKeypress = (_str, key) => {
|
|
9893
|
+
if (done) return;
|
|
9894
|
+
const name = key?.name ?? "";
|
|
9895
|
+
if (name === "left" || name === "h") {
|
|
9896
|
+
if (idx < display.length - 1) {
|
|
9897
|
+
idx++;
|
|
9898
|
+
render(display, idx);
|
|
9899
|
+
}
|
|
9900
|
+
} else if (name === "right" || name === "l") {
|
|
9901
|
+
if (idx > 0) {
|
|
9902
|
+
idx--;
|
|
9903
|
+
render(display, idx);
|
|
9904
|
+
}
|
|
9905
|
+
} else if (name === "s") {
|
|
9906
|
+
const start = sessionStart(display, idx);
|
|
9907
|
+
if (start !== idx) {
|
|
9908
|
+
idx = start;
|
|
9909
|
+
render(display, idx);
|
|
9910
|
+
}
|
|
9911
|
+
} else if (name === "return" || name === "y") {
|
|
9912
|
+
done = true;
|
|
9913
|
+
cleanup();
|
|
9914
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
9915
|
+
const entry = display[idx];
|
|
9916
|
+
process.stdout.write(import_chalk12.default.magenta.bold("\n\u23EA Restoring snapshot...\n\n"));
|
|
9917
|
+
if (applyUndo(entry.hash, entry.cwd)) {
|
|
9918
|
+
process.stdout.write(import_chalk12.default.green("\u2705 Reverted successfully.\n\n"));
|
|
9919
|
+
resolve({ restored: true });
|
|
9920
|
+
} else {
|
|
9921
|
+
process.stdout.write(import_chalk12.default.red("\u274C Undo failed.\n\n"));
|
|
9922
|
+
resolve({ restored: false });
|
|
9923
|
+
}
|
|
9924
|
+
} else if (name === "q" || key?.ctrl && name === "c") {
|
|
9925
|
+
done = true;
|
|
9926
|
+
cleanup();
|
|
9927
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
9928
|
+
process.stdout.write(import_chalk12.default.gray("\nCancelled.\n\n"));
|
|
9929
|
+
resolve({ restored: false });
|
|
9930
|
+
}
|
|
9931
|
+
};
|
|
9932
|
+
process.stdin.on("keypress", onKeypress);
|
|
9933
|
+
});
|
|
9934
|
+
}
|
|
9935
|
+
|
|
9936
|
+
// src/cli/commands/undo.ts
|
|
9937
|
+
function findMatchingCwd(startDir, history) {
|
|
9938
|
+
const cwds = new Set(history.map((e) => e.cwd));
|
|
9939
|
+
let dir = startDir;
|
|
9940
|
+
while (true) {
|
|
9941
|
+
if (cwds.has(dir)) return dir;
|
|
9942
|
+
const parent = import_path26.default.dirname(dir);
|
|
9943
|
+
if (parent === dir) return null;
|
|
9944
|
+
dir = parent;
|
|
9945
|
+
}
|
|
9946
|
+
}
|
|
9947
|
+
function formatAge2(timestamp) {
|
|
9948
|
+
const age = Math.round((Date.now() - timestamp) / 1e3);
|
|
9949
|
+
if (age < 60) return `${age}s ago`;
|
|
9950
|
+
if (age < 3600) return `${Math.round(age / 60)}m ago`;
|
|
9951
|
+
if (age < 86400) return `${Math.round(age / 3600)}h ago`;
|
|
9952
|
+
return `${Math.round(age / 86400)}d ago`;
|
|
9953
|
+
}
|
|
9670
9954
|
function registerUndoCommand(program2) {
|
|
9671
9955
|
program2.command("undo").description(
|
|
9672
|
-
"
|
|
9673
|
-
).option("--steps <n>", "
|
|
9674
|
-
const steps = Math.max(1, parseInt(options.steps, 10) || 1);
|
|
9956
|
+
"Browse and restore pre-AI snapshots. Arrow keys to navigate, Enter to restore. Use --steps N to go back N actions non-interactively, --list to print history."
|
|
9957
|
+
).option("--steps <n>", "Non-interactive: restore N steps back (default: 1)").option("--list", "Print snapshot history as a table and exit").option("--all", "Include snapshots from all directories, not just the current one").action(async (options) => {
|
|
9675
9958
|
const allHistory = getSnapshotHistory();
|
|
9676
|
-
const
|
|
9959
|
+
const matchedCwd = options.all ? null : findMatchingCwd(process.cwd(), allHistory);
|
|
9960
|
+
const history = options.all ? allHistory : allHistory.filter((s) => s.cwd === matchedCwd);
|
|
9677
9961
|
if (history.length === 0) {
|
|
9678
9962
|
if (!options.all && allHistory.length > 0) {
|
|
9679
9963
|
console.log(
|
|
9680
|
-
|
|
9964
|
+
import_chalk13.default.yellow(
|
|
9681
9965
|
`
|
|
9682
9966
|
\u2139\uFE0F No snapshots found for the current directory (${process.cwd()}).
|
|
9683
|
-
Run ${
|
|
9967
|
+
Run ${import_chalk13.default.cyan("node9 undo --all")} to see snapshots from all projects.
|
|
9684
9968
|
`
|
|
9685
9969
|
)
|
|
9686
9970
|
);
|
|
9687
9971
|
} else {
|
|
9688
|
-
console.log(
|
|
9972
|
+
console.log(import_chalk13.default.yellow("\n\u2139\uFE0F No undo snapshots found.\n"));
|
|
9689
9973
|
}
|
|
9690
9974
|
return;
|
|
9691
9975
|
}
|
|
9692
|
-
|
|
9693
|
-
|
|
9976
|
+
if (options.list) {
|
|
9977
|
+
console.log(import_chalk13.default.magenta.bold("\n\u23EA Snapshot History\n"));
|
|
9694
9978
|
console.log(
|
|
9695
|
-
|
|
9696
|
-
`
|
|
9697
|
-
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
9698
|
-
`
|
|
9979
|
+
import_chalk13.default.gray(
|
|
9980
|
+
` ${"#".padEnd(3)} ${"File / Command".padEnd(30)} ${"Tool".padEnd(8)} ${"When".padEnd(10)} Dir`
|
|
9699
9981
|
)
|
|
9700
9982
|
);
|
|
9983
|
+
console.log(import_chalk13.default.gray(" " + "\u2500".repeat(80)));
|
|
9984
|
+
const display = [...history].reverse();
|
|
9985
|
+
let prevTs = null;
|
|
9986
|
+
for (let i = 0; i < display.length; i++) {
|
|
9987
|
+
const e = display[i];
|
|
9988
|
+
const isGap = prevTs !== null && prevTs - e.timestamp > 6e4;
|
|
9989
|
+
if (isGap) console.log(import_chalk13.default.gray(" \u2500\u2500 earlier \u2500\u2500"));
|
|
9990
|
+
const label = (e.argsSummary || e.files?.[0] || "\u2014").slice(0, 30).padEnd(30);
|
|
9991
|
+
const tool = e.tool.slice(0, 8).padEnd(8);
|
|
9992
|
+
const when = formatAge2(e.timestamp).padEnd(10);
|
|
9993
|
+
const dir = e.cwd.length > 30 ? "\u2026" + e.cwd.slice(-29) : e.cwd;
|
|
9994
|
+
console.log(
|
|
9995
|
+
import_chalk13.default.white(
|
|
9996
|
+
` ${String(i + 1).padEnd(3)} ${label} ${import_chalk13.default.cyan(tool)} ${import_chalk13.default.gray(when)} ${import_chalk13.default.gray(dir)}`
|
|
9997
|
+
)
|
|
9998
|
+
);
|
|
9999
|
+
prevTs = e.timestamp;
|
|
10000
|
+
}
|
|
10001
|
+
console.log("");
|
|
9701
10002
|
return;
|
|
9702
10003
|
}
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
10004
|
+
if (options.steps !== void 0) {
|
|
10005
|
+
const steps = Math.max(1, parseInt(options.steps, 10) || 1);
|
|
10006
|
+
const idx = history.length - steps;
|
|
10007
|
+
if (idx < 0) {
|
|
10008
|
+
console.log(
|
|
10009
|
+
import_chalk13.default.yellow(
|
|
10010
|
+
`
|
|
10011
|
+
\u2139\uFE0F Only ${history.length} snapshot(s) available, cannot go back ${steps}.
|
|
10012
|
+
`
|
|
10013
|
+
)
|
|
10014
|
+
);
|
|
10015
|
+
return;
|
|
10016
|
+
}
|
|
10017
|
+
const snapshot = history[idx];
|
|
10018
|
+
const ageStr = formatAge2(snapshot.timestamp);
|
|
10019
|
+
console.log(
|
|
10020
|
+
import_chalk13.default.magenta.bold(`
|
|
9708
10021
|
\u23EA Node9 Undo${steps > 1 ? ` (${steps} steps back)` : ""}`)
|
|
9709
|
-
|
|
9710
|
-
console.log(
|
|
9711
|
-
import_chalk12.default.white(
|
|
9712
|
-
` Tool: ${import_chalk12.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk12.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
9713
|
-
)
|
|
9714
|
-
);
|
|
9715
|
-
console.log(import_chalk12.default.white(` When: ${import_chalk12.default.gray(ageStr)}`));
|
|
9716
|
-
console.log(import_chalk12.default.white(` Dir: ${import_chalk12.default.gray(snapshot.cwd)}`));
|
|
9717
|
-
if (steps > 1)
|
|
10022
|
+
);
|
|
9718
10023
|
console.log(
|
|
9719
|
-
|
|
10024
|
+
import_chalk13.default.white(
|
|
10025
|
+
` Tool: ${import_chalk13.default.cyan(snapshot.tool)}${snapshot.argsSummary ? import_chalk13.default.gray(" \u2192 " + snapshot.argsSummary) : ""}`
|
|
10026
|
+
)
|
|
9720
10027
|
);
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
console.log(
|
|
9734
|
-
|
|
9735
|
-
console.log(
|
|
10028
|
+
console.log(import_chalk13.default.white(` When: ${import_chalk13.default.gray(ageStr)}`));
|
|
10029
|
+
console.log(import_chalk13.default.white(` Dir: ${import_chalk13.default.gray(snapshot.cwd)}`));
|
|
10030
|
+
if (steps > 1)
|
|
10031
|
+
console.log(
|
|
10032
|
+
import_chalk13.default.yellow(` Note: This will also undo the ${steps - 1} action(s) after it.`)
|
|
10033
|
+
);
|
|
10034
|
+
console.log("");
|
|
10035
|
+
const diff = snapshot.diff ?? computeUndoDiff(snapshot.hash, snapshot.cwd);
|
|
10036
|
+
if (diff) {
|
|
10037
|
+
const lines = diff.split("\n").filter((l) => !l.startsWith("diff --git") && !l.startsWith("index "));
|
|
10038
|
+
for (const line of lines) {
|
|
10039
|
+
if (line.startsWith("+++") || line.startsWith("---")) console.log(import_chalk13.default.bold(line));
|
|
10040
|
+
else if (line.startsWith("+")) console.log(import_chalk13.default.green(line));
|
|
10041
|
+
else if (line.startsWith("-")) console.log(import_chalk13.default.red(line));
|
|
10042
|
+
else if (line.startsWith("@@")) console.log(import_chalk13.default.cyan(line));
|
|
10043
|
+
else console.log(import_chalk13.default.gray(line));
|
|
9736
10044
|
}
|
|
10045
|
+
console.log("");
|
|
10046
|
+
} else {
|
|
10047
|
+
console.log(
|
|
10048
|
+
import_chalk13.default.gray(" (no diff available \u2014 working tree may already match snapshot)\n")
|
|
10049
|
+
);
|
|
9737
10050
|
}
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
default: false
|
|
9747
|
-
});
|
|
9748
|
-
if (proceed) {
|
|
9749
|
-
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
9750
|
-
console.log(import_chalk12.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10051
|
+
const { confirm: confirm3 } = await import("@inquirer/prompts");
|
|
10052
|
+
const proceed = await confirm3({ message: `Revert to this snapshot?`, default: false });
|
|
10053
|
+
if (proceed) {
|
|
10054
|
+
if (applyUndo(snapshot.hash, snapshot.cwd)) {
|
|
10055
|
+
console.log(import_chalk13.default.green("\n\u2705 Reverted successfully.\n"));
|
|
10056
|
+
} else {
|
|
10057
|
+
console.error(import_chalk13.default.red("\n\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
10058
|
+
}
|
|
9751
10059
|
} else {
|
|
9752
|
-
console.
|
|
10060
|
+
console.log(import_chalk13.default.gray("\nCancelled.\n"));
|
|
9753
10061
|
}
|
|
9754
|
-
|
|
9755
|
-
console.log(import_chalk12.default.gray("\nCancelled.\n"));
|
|
10062
|
+
return;
|
|
9756
10063
|
}
|
|
10064
|
+
await runUndoNavigator(history);
|
|
9757
10065
|
});
|
|
9758
10066
|
}
|
|
9759
10067
|
|
|
9760
10068
|
// src/cli/commands/watch.ts
|
|
9761
|
-
var
|
|
10069
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
9762
10070
|
var import_child_process11 = require("child_process");
|
|
9763
10071
|
init_daemon();
|
|
9764
10072
|
function registerWatchCommand(program2) {
|
|
@@ -9775,7 +10083,7 @@ function registerWatchCommand(program2) {
|
|
|
9775
10083
|
throw new Error("not running");
|
|
9776
10084
|
}
|
|
9777
10085
|
} catch {
|
|
9778
|
-
console.error(
|
|
10086
|
+
console.error(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
9779
10087
|
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
9780
10088
|
detached: true,
|
|
9781
10089
|
stdio: "ignore",
|
|
@@ -9797,12 +10105,12 @@ function registerWatchCommand(program2) {
|
|
|
9797
10105
|
}
|
|
9798
10106
|
}
|
|
9799
10107
|
if (!ready) {
|
|
9800
|
-
console.error(
|
|
10108
|
+
console.error(import_chalk14.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
9801
10109
|
process.exit(1);
|
|
9802
10110
|
}
|
|
9803
10111
|
}
|
|
9804
10112
|
console.error(
|
|
9805
|
-
|
|
10113
|
+
import_chalk14.default.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + import_chalk14.default.dim(` \u2192 localhost:${port}`) + import_chalk14.default.dim(
|
|
9806
10114
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
9807
10115
|
)
|
|
9808
10116
|
);
|
|
@@ -9811,7 +10119,7 @@ function registerWatchCommand(program2) {
|
|
|
9811
10119
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
9812
10120
|
});
|
|
9813
10121
|
if (result.error) {
|
|
9814
|
-
console.error(
|
|
10122
|
+
console.error(import_chalk14.default.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
9815
10123
|
process.exit(1);
|
|
9816
10124
|
}
|
|
9817
10125
|
process.exit(result.status ?? 0);
|
|
@@ -9819,8 +10127,8 @@ function registerWatchCommand(program2) {
|
|
|
9819
10127
|
}
|
|
9820
10128
|
|
|
9821
10129
|
// src/mcp-gateway/index.ts
|
|
9822
|
-
var
|
|
9823
|
-
var
|
|
10130
|
+
var import_readline3 = __toESM(require("readline"));
|
|
10131
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
9824
10132
|
var import_child_process12 = require("child_process");
|
|
9825
10133
|
var import_execa3 = require("execa");
|
|
9826
10134
|
init_orchestrator();
|
|
@@ -9884,13 +10192,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
9884
10192
|
const prov = checkProvenance(executable);
|
|
9885
10193
|
if (prov.trustLevel === "suspect") {
|
|
9886
10194
|
console.error(
|
|
9887
|
-
|
|
10195
|
+
import_chalk15.default.red(
|
|
9888
10196
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
9889
10197
|
)
|
|
9890
10198
|
);
|
|
9891
|
-
console.error(
|
|
10199
|
+
console.error(import_chalk15.default.red(" Verify this binary is trusted before proceeding."));
|
|
9892
10200
|
}
|
|
9893
|
-
console.error(
|
|
10201
|
+
console.error(import_chalk15.default.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
9894
10202
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
9895
10203
|
"NODE_OPTIONS",
|
|
9896
10204
|
"NODE_PATH",
|
|
@@ -9918,7 +10226,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
9918
10226
|
let authPending = false;
|
|
9919
10227
|
let deferredExitCode = null;
|
|
9920
10228
|
let deferredStdinEnd = false;
|
|
9921
|
-
const agentIn =
|
|
10229
|
+
const agentIn = import_readline3.default.createInterface({ input: process.stdin, terminal: false });
|
|
9922
10230
|
agentIn.on("line", async (line) => {
|
|
9923
10231
|
let message;
|
|
9924
10232
|
try {
|
|
@@ -9954,10 +10262,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
9954
10262
|
mcpServer
|
|
9955
10263
|
});
|
|
9956
10264
|
if (!result.approved) {
|
|
9957
|
-
console.error(
|
|
10265
|
+
console.error(import_chalk15.default.red(`
|
|
9958
10266
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
9959
|
-
console.error(
|
|
9960
|
-
console.error(
|
|
10267
|
+
console.error(import_chalk15.default.gray(` Tool: ${toolName}`));
|
|
10268
|
+
console.error(import_chalk15.default.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
9961
10269
|
`));
|
|
9962
10270
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
9963
10271
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -10030,7 +10338,7 @@ function registerMcpGatewayCommand(program2) {
|
|
|
10030
10338
|
}
|
|
10031
10339
|
|
|
10032
10340
|
// src/cli/commands/trust.ts
|
|
10033
|
-
var
|
|
10341
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
10034
10342
|
init_trusted_hosts();
|
|
10035
10343
|
function isValidHost(host) {
|
|
10036
10344
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
@@ -10041,44 +10349,44 @@ function registerTrustCommand(program2) {
|
|
|
10041
10349
|
const normalized = normalizeHost(host.trim());
|
|
10042
10350
|
if (!isValidHost(normalized)) {
|
|
10043
10351
|
console.error(
|
|
10044
|
-
|
|
10352
|
+
import_chalk16.default.red(`
|
|
10045
10353
|
\u274C Invalid host: "${host}"
|
|
10046
|
-
`) +
|
|
10354
|
+
`) + import_chalk16.default.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
10047
10355
|
);
|
|
10048
10356
|
process.exit(1);
|
|
10049
10357
|
}
|
|
10050
10358
|
addTrustedHost(normalized);
|
|
10051
|
-
console.log(
|
|
10359
|
+
console.log(import_chalk16.default.green(`
|
|
10052
10360
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
10053
10361
|
console.log(
|
|
10054
|
-
|
|
10362
|
+
import_chalk16.default.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
10055
10363
|
);
|
|
10056
10364
|
});
|
|
10057
10365
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
10058
10366
|
const normalized = normalizeHost(host.trim());
|
|
10059
10367
|
const removed = removeTrustedHost(normalized);
|
|
10060
10368
|
if (!removed) {
|
|
10061
|
-
console.error(
|
|
10369
|
+
console.error(import_chalk16.default.yellow(`
|
|
10062
10370
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
10063
10371
|
`));
|
|
10064
10372
|
process.exit(1);
|
|
10065
10373
|
}
|
|
10066
|
-
console.log(
|
|
10374
|
+
console.log(import_chalk16.default.green(`
|
|
10067
10375
|
\u2705 ${normalized} removed from trusted hosts.
|
|
10068
10376
|
`));
|
|
10069
10377
|
});
|
|
10070
10378
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
10071
10379
|
const hosts = readTrustedHosts();
|
|
10072
10380
|
if (hosts.length === 0) {
|
|
10073
|
-
console.log(
|
|
10074
|
-
console.log(` Add one: ${
|
|
10381
|
+
console.log(import_chalk16.default.gray("\n No trusted hosts configured.\n"));
|
|
10382
|
+
console.log(` Add one: ${import_chalk16.default.cyan("node9 trust add api.mycompany.com")}
|
|
10075
10383
|
`);
|
|
10076
10384
|
return;
|
|
10077
10385
|
}
|
|
10078
|
-
console.log(
|
|
10386
|
+
console.log(import_chalk16.default.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
10079
10387
|
for (const entry of hosts) {
|
|
10080
10388
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
10081
|
-
console.log(` ${
|
|
10389
|
+
console.log(` ${import_chalk16.default.cyan(entry.host.padEnd(40))} ${import_chalk16.default.gray(`added ${date}`)}`);
|
|
10082
10390
|
}
|
|
10083
10391
|
console.log("");
|
|
10084
10392
|
});
|
|
@@ -10086,15 +10394,15 @@ function registerTrustCommand(program2) {
|
|
|
10086
10394
|
|
|
10087
10395
|
// src/cli.ts
|
|
10088
10396
|
var { version } = JSON.parse(
|
|
10089
|
-
import_fs26.default.readFileSync(
|
|
10397
|
+
import_fs26.default.readFileSync(import_path29.default.join(__dirname, "../package.json"), "utf-8")
|
|
10090
10398
|
);
|
|
10091
10399
|
var program = new import_commander.Command();
|
|
10092
10400
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
10093
10401
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
10094
10402
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
10095
|
-
const credPath =
|
|
10096
|
-
if (!import_fs26.default.existsSync(
|
|
10097
|
-
import_fs26.default.mkdirSync(
|
|
10403
|
+
const credPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "credentials.json");
|
|
10404
|
+
if (!import_fs26.default.existsSync(import_path29.default.dirname(credPath)))
|
|
10405
|
+
import_fs26.default.mkdirSync(import_path29.default.dirname(credPath), { recursive: true });
|
|
10098
10406
|
const profileName = options.profile || "default";
|
|
10099
10407
|
let existingCreds = {};
|
|
10100
10408
|
try {
|
|
@@ -10113,7 +10421,7 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
10113
10421
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
10114
10422
|
import_fs26.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
10115
10423
|
if (profileName === "default") {
|
|
10116
|
-
const configPath =
|
|
10424
|
+
const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
10117
10425
|
let config = {};
|
|
10118
10426
|
try {
|
|
10119
10427
|
if (import_fs26.default.existsSync(configPath))
|
|
@@ -10132,19 +10440,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
10132
10440
|
approvers.cloud = false;
|
|
10133
10441
|
}
|
|
10134
10442
|
s.approvers = approvers;
|
|
10135
|
-
if (!import_fs26.default.existsSync(
|
|
10136
|
-
import_fs26.default.mkdirSync(
|
|
10443
|
+
if (!import_fs26.default.existsSync(import_path29.default.dirname(configPath)))
|
|
10444
|
+
import_fs26.default.mkdirSync(import_path29.default.dirname(configPath), { recursive: true });
|
|
10137
10445
|
import_fs26.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
10138
10446
|
}
|
|
10139
10447
|
if (options.profile && profileName !== "default") {
|
|
10140
|
-
console.log(
|
|
10141
|
-
console.log(
|
|
10448
|
+
console.log(import_chalk18.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
10449
|
+
console.log(import_chalk18.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
10142
10450
|
} else if (options.local) {
|
|
10143
|
-
console.log(
|
|
10144
|
-
console.log(
|
|
10451
|
+
console.log(import_chalk18.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
10452
|
+
console.log(import_chalk18.default.gray(` All decisions stay on this machine.`));
|
|
10145
10453
|
} else {
|
|
10146
|
-
console.log(
|
|
10147
|
-
console.log(
|
|
10454
|
+
console.log(import_chalk18.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
10455
|
+
console.log(import_chalk18.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
10148
10456
|
}
|
|
10149
10457
|
});
|
|
10150
10458
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
@@ -10152,19 +10460,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
10152
10460
|
if (target === "claude") return await setupClaude();
|
|
10153
10461
|
if (target === "cursor") return await setupCursor();
|
|
10154
10462
|
if (target === "hud") return setupHud();
|
|
10155
|
-
console.error(
|
|
10463
|
+
console.error(import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
10156
10464
|
process.exit(1);
|
|
10157
10465
|
});
|
|
10158
10466
|
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
10159
10467
|
if (!target) {
|
|
10160
|
-
console.log(
|
|
10161
|
-
console.log(" Usage: " +
|
|
10468
|
+
console.log(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
10469
|
+
console.log(" Usage: " + import_chalk18.default.white("node9 setup <target>") + "\n");
|
|
10162
10470
|
console.log(" Targets:");
|
|
10163
|
-
console.log(" " +
|
|
10164
|
-
console.log(" " +
|
|
10165
|
-
console.log(" " +
|
|
10471
|
+
console.log(" " + import_chalk18.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
10472
|
+
console.log(" " + import_chalk18.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
10473
|
+
console.log(" " + import_chalk18.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
10166
10474
|
process.stdout.write(
|
|
10167
|
-
" " +
|
|
10475
|
+
" " + import_chalk18.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
10168
10476
|
);
|
|
10169
10477
|
console.log("");
|
|
10170
10478
|
return;
|
|
@@ -10174,7 +10482,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
10174
10482
|
if (t === "claude") return await setupClaude();
|
|
10175
10483
|
if (t === "cursor") return await setupCursor();
|
|
10176
10484
|
if (t === "hud") return setupHud();
|
|
10177
|
-
console.error(
|
|
10485
|
+
console.error(import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
10178
10486
|
process.exit(1);
|
|
10179
10487
|
});
|
|
10180
10488
|
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
|
|
@@ -10185,31 +10493,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
10185
10493
|
else if (target === "hud") fn = teardownHud;
|
|
10186
10494
|
else {
|
|
10187
10495
|
console.error(
|
|
10188
|
-
|
|
10496
|
+
import_chalk18.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
10189
10497
|
);
|
|
10190
10498
|
process.exit(1);
|
|
10191
10499
|
}
|
|
10192
|
-
console.log(
|
|
10500
|
+
console.log(import_chalk18.default.cyan(`
|
|
10193
10501
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
10194
10502
|
`));
|
|
10195
10503
|
try {
|
|
10196
10504
|
fn();
|
|
10197
10505
|
} catch (err) {
|
|
10198
|
-
console.error(
|
|
10506
|
+
console.error(import_chalk18.default.red(` \u26A0\uFE0F Failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
10199
10507
|
process.exit(1);
|
|
10200
10508
|
}
|
|
10201
|
-
console.log(
|
|
10509
|
+
console.log(import_chalk18.default.gray("\n Restart the agent for changes to take effect."));
|
|
10202
10510
|
});
|
|
10203
10511
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
10204
|
-
console.log(
|
|
10205
|
-
console.log(
|
|
10512
|
+
console.log(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
10513
|
+
console.log(import_chalk18.default.bold("Stopping daemon..."));
|
|
10206
10514
|
try {
|
|
10207
10515
|
stopDaemon();
|
|
10208
|
-
console.log(
|
|
10516
|
+
console.log(import_chalk18.default.green(" \u2705 Daemon stopped"));
|
|
10209
10517
|
} catch {
|
|
10210
|
-
console.log(
|
|
10518
|
+
console.log(import_chalk18.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
10211
10519
|
}
|
|
10212
|
-
console.log(
|
|
10520
|
+
console.log(import_chalk18.default.bold("\nRemoving hooks..."));
|
|
10213
10521
|
let teardownFailed = false;
|
|
10214
10522
|
for (const [label, fn] of [
|
|
10215
10523
|
["Claude", teardownClaude],
|
|
@@ -10221,16 +10529,16 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
10221
10529
|
} catch (err) {
|
|
10222
10530
|
teardownFailed = true;
|
|
10223
10531
|
console.error(
|
|
10224
|
-
|
|
10532
|
+
import_chalk18.default.red(
|
|
10225
10533
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err instanceof Error ? err.message : String(err)}`
|
|
10226
10534
|
)
|
|
10227
10535
|
);
|
|
10228
10536
|
}
|
|
10229
10537
|
}
|
|
10230
10538
|
if (options.purge) {
|
|
10231
|
-
const node9Dir =
|
|
10539
|
+
const node9Dir = import_path29.default.join(import_os22.default.homedir(), ".node9");
|
|
10232
10540
|
if (import_fs26.default.existsSync(node9Dir)) {
|
|
10233
|
-
const confirmed = await (0,
|
|
10541
|
+
const confirmed = await (0, import_prompts2.confirm)({
|
|
10234
10542
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
10235
10543
|
default: false
|
|
10236
10544
|
});
|
|
@@ -10238,28 +10546,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
10238
10546
|
import_fs26.default.rmSync(node9Dir, { recursive: true });
|
|
10239
10547
|
if (import_fs26.default.existsSync(node9Dir)) {
|
|
10240
10548
|
console.error(
|
|
10241
|
-
|
|
10549
|
+
import_chalk18.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
10242
10550
|
);
|
|
10243
10551
|
} else {
|
|
10244
|
-
console.log(
|
|
10552
|
+
console.log(import_chalk18.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
10245
10553
|
}
|
|
10246
10554
|
} else {
|
|
10247
|
-
console.log(
|
|
10555
|
+
console.log(import_chalk18.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
10248
10556
|
}
|
|
10249
10557
|
} else {
|
|
10250
|
-
console.log(
|
|
10558
|
+
console.log(import_chalk18.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
10251
10559
|
}
|
|
10252
10560
|
} else {
|
|
10253
10561
|
console.log(
|
|
10254
|
-
|
|
10562
|
+
import_chalk18.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
10255
10563
|
);
|
|
10256
10564
|
}
|
|
10257
10565
|
if (teardownFailed) {
|
|
10258
|
-
console.error(
|
|
10566
|
+
console.error(import_chalk18.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
10259
10567
|
process.exit(1);
|
|
10260
10568
|
}
|
|
10261
|
-
console.log(
|
|
10262
|
-
console.log(
|
|
10569
|
+
console.log(import_chalk18.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
10570
|
+
console.log(import_chalk18.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
10263
10571
|
});
|
|
10264
10572
|
registerDoctorCommand(program, version);
|
|
10265
10573
|
program.command("explain").description(
|
|
@@ -10272,7 +10580,7 @@ program.command("explain").description(
|
|
|
10272
10580
|
try {
|
|
10273
10581
|
args = JSON.parse(trimmed);
|
|
10274
10582
|
} catch {
|
|
10275
|
-
console.error(
|
|
10583
|
+
console.error(import_chalk18.default.red(`
|
|
10276
10584
|
\u274C Invalid JSON: ${trimmed}
|
|
10277
10585
|
`));
|
|
10278
10586
|
process.exit(1);
|
|
@@ -10283,54 +10591,54 @@ program.command("explain").description(
|
|
|
10283
10591
|
}
|
|
10284
10592
|
const result = await explainPolicy(tool, args);
|
|
10285
10593
|
console.log("");
|
|
10286
|
-
console.log(
|
|
10594
|
+
console.log(import_chalk18.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
10287
10595
|
console.log("");
|
|
10288
|
-
console.log(` ${
|
|
10596
|
+
console.log(` ${import_chalk18.default.bold("Tool:")} ${import_chalk18.default.white(result.tool)}`);
|
|
10289
10597
|
if (argsRaw) {
|
|
10290
10598
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
10291
|
-
console.log(` ${
|
|
10599
|
+
console.log(` ${import_chalk18.default.bold("Input:")} ${import_chalk18.default.gray(preview)}`);
|
|
10292
10600
|
}
|
|
10293
10601
|
console.log("");
|
|
10294
|
-
console.log(
|
|
10602
|
+
console.log(import_chalk18.default.bold("Config Sources (Waterfall):"));
|
|
10295
10603
|
for (const tier of result.waterfall) {
|
|
10296
|
-
const num =
|
|
10604
|
+
const num = import_chalk18.default.gray(` ${tier.tier}.`);
|
|
10297
10605
|
const label = tier.label.padEnd(16);
|
|
10298
10606
|
let statusStr;
|
|
10299
10607
|
if (tier.tier === 1) {
|
|
10300
|
-
statusStr =
|
|
10608
|
+
statusStr = import_chalk18.default.gray(tier.note ?? "");
|
|
10301
10609
|
} else if (tier.status === "active") {
|
|
10302
|
-
const loc = tier.path ?
|
|
10303
|
-
const note = tier.note ?
|
|
10304
|
-
statusStr =
|
|
10610
|
+
const loc = tier.path ? import_chalk18.default.gray(tier.path) : "";
|
|
10611
|
+
const note = tier.note ? import_chalk18.default.gray(`(${tier.note})`) : "";
|
|
10612
|
+
statusStr = import_chalk18.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
10305
10613
|
} else {
|
|
10306
|
-
statusStr =
|
|
10614
|
+
statusStr = import_chalk18.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
10307
10615
|
}
|
|
10308
|
-
console.log(`${num} ${
|
|
10616
|
+
console.log(`${num} ${import_chalk18.default.white(label)} ${statusStr}`);
|
|
10309
10617
|
}
|
|
10310
10618
|
console.log("");
|
|
10311
|
-
console.log(
|
|
10619
|
+
console.log(import_chalk18.default.bold("Policy Evaluation:"));
|
|
10312
10620
|
for (const step of result.steps) {
|
|
10313
10621
|
const isFinal = step.isFinal;
|
|
10314
10622
|
let icon;
|
|
10315
|
-
if (step.outcome === "allow") icon =
|
|
10316
|
-
else if (step.outcome === "review") icon =
|
|
10317
|
-
else if (step.outcome === "skip") icon =
|
|
10318
|
-
else icon =
|
|
10623
|
+
if (step.outcome === "allow") icon = import_chalk18.default.green(" \u2705");
|
|
10624
|
+
else if (step.outcome === "review") icon = import_chalk18.default.red(" \u{1F534}");
|
|
10625
|
+
else if (step.outcome === "skip") icon = import_chalk18.default.gray(" \u2500 ");
|
|
10626
|
+
else icon = import_chalk18.default.gray(" \u25CB ");
|
|
10319
10627
|
const name = step.name.padEnd(18);
|
|
10320
|
-
const nameStr = isFinal ?
|
|
10321
|
-
const detail = isFinal ?
|
|
10322
|
-
const arrow = isFinal ?
|
|
10628
|
+
const nameStr = isFinal ? import_chalk18.default.white.bold(name) : import_chalk18.default.white(name);
|
|
10629
|
+
const detail = isFinal ? import_chalk18.default.white(step.detail) : import_chalk18.default.gray(step.detail);
|
|
10630
|
+
const arrow = isFinal ? import_chalk18.default.yellow(" \u2190 STOP") : "";
|
|
10323
10631
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
10324
10632
|
}
|
|
10325
10633
|
console.log("");
|
|
10326
10634
|
if (result.decision === "allow") {
|
|
10327
|
-
console.log(
|
|
10635
|
+
console.log(import_chalk18.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk18.default.gray(" \u2014 no approval needed"));
|
|
10328
10636
|
} else {
|
|
10329
10637
|
console.log(
|
|
10330
|
-
|
|
10638
|
+
import_chalk18.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk18.default.gray(" \u2014 human approval required")
|
|
10331
10639
|
);
|
|
10332
10640
|
if (result.blockedByLabel) {
|
|
10333
|
-
console.log(
|
|
10641
|
+
console.log(import_chalk18.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
10334
10642
|
}
|
|
10335
10643
|
}
|
|
10336
10644
|
console.log("");
|
|
@@ -10344,7 +10652,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
10344
10652
|
try {
|
|
10345
10653
|
await startTail2(options);
|
|
10346
10654
|
} catch (err) {
|
|
10347
|
-
console.error(
|
|
10655
|
+
console.error(import_chalk18.default.red(`\u274C ${err instanceof Error ? err.message : String(err)}`));
|
|
10348
10656
|
process.exit(1);
|
|
10349
10657
|
}
|
|
10350
10658
|
});
|
|
@@ -10360,7 +10668,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
10360
10668
|
const ms = parseDuration(options.duration);
|
|
10361
10669
|
if (ms === null) {
|
|
10362
10670
|
console.error(
|
|
10363
|
-
|
|
10671
|
+
import_chalk18.default.red(`
|
|
10364
10672
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
10365
10673
|
`)
|
|
10366
10674
|
);
|
|
@@ -10368,20 +10676,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
10368
10676
|
}
|
|
10369
10677
|
pauseNode9(ms, options.duration);
|
|
10370
10678
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
10371
|
-
console.log(
|
|
10679
|
+
console.log(import_chalk18.default.yellow(`
|
|
10372
10680
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
10373
|
-
console.log(
|
|
10374
|
-
console.log(
|
|
10681
|
+
console.log(import_chalk18.default.gray(` All tool calls will be allowed without review.`));
|
|
10682
|
+
console.log(import_chalk18.default.gray(` Run "node9 resume" to re-enable early.
|
|
10375
10683
|
`));
|
|
10376
10684
|
});
|
|
10377
10685
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
10378
10686
|
const { paused } = checkPause();
|
|
10379
10687
|
if (!paused) {
|
|
10380
|
-
console.log(
|
|
10688
|
+
console.log(import_chalk18.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
10381
10689
|
return;
|
|
10382
10690
|
}
|
|
10383
10691
|
resumeNode9();
|
|
10384
|
-
console.log(
|
|
10692
|
+
console.log(import_chalk18.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
10385
10693
|
});
|
|
10386
10694
|
var HOOK_BASED_AGENTS = {
|
|
10387
10695
|
claude: "claude",
|
|
@@ -10394,15 +10702,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
10394
10702
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
10395
10703
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
10396
10704
|
console.error(
|
|
10397
|
-
|
|
10705
|
+
import_chalk18.default.yellow(`
|
|
10398
10706
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
10399
10707
|
);
|
|
10400
|
-
console.error(
|
|
10708
|
+
console.error(import_chalk18.default.white(`
|
|
10401
10709
|
"${target}" uses its own hook system. Use:`));
|
|
10402
10710
|
console.error(
|
|
10403
|
-
|
|
10711
|
+
import_chalk18.default.green(` node9 addto ${target} `) + import_chalk18.default.gray("# one-time setup")
|
|
10404
10712
|
);
|
|
10405
|
-
console.error(
|
|
10713
|
+
console.error(import_chalk18.default.green(` ${target} `) + import_chalk18.default.gray("# run normally"));
|
|
10406
10714
|
process.exit(1);
|
|
10407
10715
|
}
|
|
10408
10716
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -10419,12 +10727,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
10419
10727
|
}
|
|
10420
10728
|
);
|
|
10421
10729
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
10422
|
-
console.error(
|
|
10730
|
+
console.error(import_chalk18.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
10423
10731
|
const daemonReady = await autoStartDaemonAndWait();
|
|
10424
10732
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
10425
10733
|
}
|
|
10426
10734
|
if (result.noApprovalMechanism && process.stdout.isTTY) {
|
|
10427
|
-
const approved = await (0,
|
|
10735
|
+
const approved = await (0, import_prompts2.confirm)({
|
|
10428
10736
|
message: `\u{1F6E1}\uFE0F Node9: Allow "${fullCommand}"?`,
|
|
10429
10737
|
default: false
|
|
10430
10738
|
});
|
|
@@ -10432,12 +10740,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
10432
10740
|
}
|
|
10433
10741
|
if (!result.approved) {
|
|
10434
10742
|
console.error(
|
|
10435
|
-
|
|
10743
|
+
import_chalk18.default.red(`
|
|
10436
10744
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
10437
10745
|
);
|
|
10438
10746
|
process.exit(1);
|
|
10439
10747
|
}
|
|
10440
|
-
console.error(
|
|
10748
|
+
console.error(import_chalk18.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
10441
10749
|
await runProxy(fullCommand);
|
|
10442
10750
|
} else {
|
|
10443
10751
|
program.help();
|
|
@@ -10452,7 +10760,7 @@ if (process.argv[2] !== "daemon") {
|
|
|
10452
10760
|
const isCheckHook = process.argv[2] === "check";
|
|
10453
10761
|
if (isCheckHook) {
|
|
10454
10762
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
10455
|
-
const logPath =
|
|
10763
|
+
const logPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log");
|
|
10456
10764
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
10457
10765
|
import_fs26.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
10458
10766
|
`);
|