@node9/proxy 1.44.0 → 1.45.0
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/dist/cli.js +1171 -666
- package/dist/cli.mjs +1171 -666
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -240,8 +240,8 @@ function sanitizeConfig(raw) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
const lines = result.error.issues.map((issue) => {
|
|
243
|
-
const
|
|
244
|
-
return ` \u2022 ${
|
|
243
|
+
const path64 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
244
|
+
return ` \u2022 ${path64}: ${issue.message}`;
|
|
245
245
|
});
|
|
246
246
|
return {
|
|
247
247
|
sanitized,
|
|
@@ -1357,9 +1357,9 @@ function matchesPattern(text, patterns) {
|
|
|
1357
1357
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1358
1358
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1359
1359
|
}
|
|
1360
|
-
function getNestedValue(obj,
|
|
1360
|
+
function getNestedValue(obj, path64) {
|
|
1361
1361
|
if (!obj || typeof obj !== "object") return null;
|
|
1362
|
-
const segments =
|
|
1362
|
+
const segments = path64.split(".");
|
|
1363
1363
|
for (const seg of segments) {
|
|
1364
1364
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
1365
1365
|
}
|
|
@@ -4171,7 +4171,7 @@ function installShield(name, shieldJson) {
|
|
|
4171
4171
|
import_fs2.default.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
4172
4172
|
import_fs2.default.renameSync(tmp, filePath);
|
|
4173
4173
|
}
|
|
4174
|
-
var import_fs2, import_path2, import_os2, import_crypto4, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS;
|
|
4174
|
+
var import_fs2, import_path2, import_os2, import_crypto4, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE, RULE_KEY_MIGRATIONS, USER_SHIELDS_DIR_PATH;
|
|
4175
4175
|
var init_shields = __esm({
|
|
4176
4176
|
"src/shields.ts"() {
|
|
4177
4177
|
"use strict";
|
|
@@ -4192,6 +4192,7 @@ var init_shields = __esm({
|
|
|
4192
4192
|
// around the review-read-credentials rule.
|
|
4193
4193
|
["shield:project-jail:review-read-env-any-tool", "shield:project-jail:block-read-env-any-tool"]
|
|
4194
4194
|
];
|
|
4195
|
+
USER_SHIELDS_DIR_PATH = USER_SHIELDS_DIR;
|
|
4195
4196
|
}
|
|
4196
4197
|
});
|
|
4197
4198
|
|
|
@@ -5059,7 +5060,30 @@ function explainIsSqlTool(toolName, toolInspection) {
|
|
|
5059
5060
|
const fieldName = toolInspection[matchingPattern];
|
|
5060
5061
|
return fieldName === "sql" || fieldName === "query";
|
|
5061
5062
|
}
|
|
5062
|
-
async function explainPolicy(toolName, args) {
|
|
5063
|
+
async function explainPolicy(toolName, args, agent = EXPLAIN_AGENT) {
|
|
5064
|
+
const derived = await deriveExplainTrace(toolName, args);
|
|
5065
|
+
const engine = await evaluatePolicy2(toolName, args, agent);
|
|
5066
|
+
if (derived.decision === engine.decision) return derived;
|
|
5067
|
+
if (process.env.NODE9_DEBUG) {
|
|
5068
|
+
console.error(
|
|
5069
|
+
`[node9 explain] decision drift: trace=${derived.decision} engine=${engine.decision} for ${toolName} \u2014 engine wins (${engine.blockedByLabel ?? "engine"}).`
|
|
5070
|
+
);
|
|
5071
|
+
}
|
|
5072
|
+
const engineStep = {
|
|
5073
|
+
name: "Engine verdict (authoritative)",
|
|
5074
|
+
outcome: engine.decision,
|
|
5075
|
+
detail: `${engine.blockedByLabel ?? "policy engine"}${engine.reason ? `: ${engine.reason}` : ""} \u2014 this is the verdict the live hook actually enforces. It differs from the preview trace above, which models the policy tiers separately and can drift; the engine is authoritative.`,
|
|
5076
|
+
isFinal: true
|
|
5077
|
+
};
|
|
5078
|
+
return {
|
|
5079
|
+
...derived,
|
|
5080
|
+
steps: [...derived.steps, engineStep],
|
|
5081
|
+
decision: engine.decision,
|
|
5082
|
+
blockedByLabel: engine.blockedByLabel ?? derived.blockedByLabel,
|
|
5083
|
+
ruleDescription: engine.ruleDescription ?? derived.ruleDescription
|
|
5084
|
+
};
|
|
5085
|
+
}
|
|
5086
|
+
async function deriveExplainTrace(toolName, args) {
|
|
5063
5087
|
const steps = [];
|
|
5064
5088
|
const globalPath = import_path7.default.join(import_os6.default.homedir(), ".node9", "config.json");
|
|
5065
5089
|
const projectPath = import_path7.default.join(process.cwd(), "node9.config.json");
|
|
@@ -5341,7 +5365,7 @@ async function explainPolicy(toolName, args) {
|
|
|
5341
5365
|
});
|
|
5342
5366
|
return { tool: toolName, args, waterfall, steps, decision: "allow" };
|
|
5343
5367
|
}
|
|
5344
|
-
var import_fs7, import_path7, import_os6, import_picomatch2, SQL_DML_KEYWORDS2;
|
|
5368
|
+
var import_fs7, import_path7, import_os6, import_picomatch2, SQL_DML_KEYWORDS2, EXPLAIN_AGENT;
|
|
5345
5369
|
var init_policy = __esm({
|
|
5346
5370
|
"src/policy/index.ts"() {
|
|
5347
5371
|
"use strict";
|
|
@@ -5356,6 +5380,7 @@ var init_policy = __esm({
|
|
|
5356
5380
|
init_dist();
|
|
5357
5381
|
init_dist();
|
|
5358
5382
|
SQL_DML_KEYWORDS2 = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
5383
|
+
EXPLAIN_AGENT = "agent";
|
|
5359
5384
|
}
|
|
5360
5385
|
});
|
|
5361
5386
|
|
|
@@ -8629,9 +8654,9 @@ function writeToml(filePath, data) {
|
|
|
8629
8654
|
async function setupCodex() {
|
|
8630
8655
|
seedMcpPinsIfMissing();
|
|
8631
8656
|
const homeDir2 = import_os12.default.homedir();
|
|
8632
|
-
const
|
|
8657
|
+
const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
|
|
8633
8658
|
const hooksPath = import_path15.default.join(homeDir2, ".codex", "hooks.json");
|
|
8634
|
-
const config = readToml(
|
|
8659
|
+
const config = readToml(configPath) ?? {};
|
|
8635
8660
|
const servers = config.mcp_servers ?? {};
|
|
8636
8661
|
let anythingChanged = false;
|
|
8637
8662
|
const hooksFile = readJson(hooksPath) ?? {};
|
|
@@ -8706,7 +8731,7 @@ async function setupCodex() {
|
|
|
8706
8731
|
if (!hasNode9McpServer(servers)) {
|
|
8707
8732
|
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
8708
8733
|
config.mcp_servers = servers;
|
|
8709
|
-
writeToml(
|
|
8734
|
+
writeToml(configPath, config);
|
|
8710
8735
|
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
8711
8736
|
anythingChanged = true;
|
|
8712
8737
|
}
|
|
@@ -8718,7 +8743,7 @@ async function setupCodex() {
|
|
|
8718
8743
|
}
|
|
8719
8744
|
if (serversToWrap.length > 0) {
|
|
8720
8745
|
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
8721
|
-
console.log(import_chalk.default.white(` ${
|
|
8746
|
+
console.log(import_chalk.default.white(` ${configPath}`));
|
|
8722
8747
|
for (const { name, upstream } of serversToWrap) {
|
|
8723
8748
|
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8724
8749
|
}
|
|
@@ -8733,7 +8758,7 @@ async function setupCodex() {
|
|
|
8733
8758
|
};
|
|
8734
8759
|
}
|
|
8735
8760
|
config.mcp_servers = servers;
|
|
8736
|
-
writeToml(
|
|
8761
|
+
writeToml(configPath, config);
|
|
8737
8762
|
console.log(import_chalk.default.green(`
|
|
8738
8763
|
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
8739
8764
|
anythingChanged = true;
|
|
@@ -8781,7 +8806,7 @@ async function setupCodex() {
|
|
|
8781
8806
|
}
|
|
8782
8807
|
function teardownCodex() {
|
|
8783
8808
|
const homeDir2 = import_os12.default.homedir();
|
|
8784
|
-
const
|
|
8809
|
+
const configPath = import_path15.default.join(homeDir2, ".codex", "config.toml");
|
|
8785
8810
|
const hooksPath = import_path15.default.join(homeDir2, ".codex", "hooks.json");
|
|
8786
8811
|
const hooksFile = readJson(hooksPath);
|
|
8787
8812
|
if (hooksFile?.hooks) {
|
|
@@ -8799,7 +8824,7 @@ function teardownCodex() {
|
|
|
8799
8824
|
console.log(import_chalk.default.green(" \u2705 Removed Node9 hooks from ~/.codex/hooks.json"));
|
|
8800
8825
|
}
|
|
8801
8826
|
}
|
|
8802
|
-
const config = readToml(
|
|
8827
|
+
const config = readToml(configPath);
|
|
8803
8828
|
if (!config?.mcp_servers) {
|
|
8804
8829
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.codex/config.toml not found \u2014 nothing to remove"));
|
|
8805
8830
|
return;
|
|
@@ -8822,7 +8847,7 @@ function teardownCodex() {
|
|
|
8822
8847
|
}
|
|
8823
8848
|
}
|
|
8824
8849
|
if (changed) {
|
|
8825
|
-
writeToml(
|
|
8850
|
+
writeToml(configPath, config);
|
|
8826
8851
|
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.codex/config.toml"));
|
|
8827
8852
|
} else {
|
|
8828
8853
|
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in ~/.codex/config.toml"));
|
|
@@ -9077,18 +9102,18 @@ function teardownVSCode() {
|
|
|
9077
9102
|
}
|
|
9078
9103
|
async function setupClaudeDesktop() {
|
|
9079
9104
|
seedMcpPinsIfMissing();
|
|
9080
|
-
const
|
|
9081
|
-
if (!
|
|
9105
|
+
const configPath = claudeDesktopConfigPath();
|
|
9106
|
+
if (!configPath) {
|
|
9082
9107
|
console.log(import_chalk.default.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
|
|
9083
9108
|
return;
|
|
9084
9109
|
}
|
|
9085
|
-
const config = readJson(
|
|
9110
|
+
const config = readJson(configPath) ?? {};
|
|
9086
9111
|
const servers = config.mcpServers ?? {};
|
|
9087
9112
|
let anythingChanged = false;
|
|
9088
9113
|
if (!hasNode9McpServer(servers)) {
|
|
9089
9114
|
servers["node9"] = NODE9_MCP_SERVER_ENTRY;
|
|
9090
9115
|
config.mcpServers = servers;
|
|
9091
|
-
writeJson(
|
|
9116
|
+
writeJson(configPath, config);
|
|
9092
9117
|
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
9093
9118
|
anythingChanged = true;
|
|
9094
9119
|
}
|
|
@@ -9099,7 +9124,7 @@ async function setupClaudeDesktop() {
|
|
|
9099
9124
|
}
|
|
9100
9125
|
if (serversToWrap.length > 0) {
|
|
9101
9126
|
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
9102
|
-
console.log(import_chalk.default.white(` ${
|
|
9127
|
+
console.log(import_chalk.default.white(` ${configPath}`));
|
|
9103
9128
|
for (const { name, upstream } of serversToWrap) {
|
|
9104
9129
|
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
9105
9130
|
}
|
|
@@ -9114,7 +9139,7 @@ async function setupClaudeDesktop() {
|
|
|
9114
9139
|
};
|
|
9115
9140
|
}
|
|
9116
9141
|
config.mcpServers = servers;
|
|
9117
|
-
writeJson(
|
|
9142
|
+
writeJson(configPath, config);
|
|
9118
9143
|
console.log(import_chalk.default.green(`
|
|
9119
9144
|
\u2705 ${serversToWrap.length} MCP server(s) wrapped`));
|
|
9120
9145
|
anythingChanged = true;
|
|
@@ -9141,12 +9166,12 @@ async function setupClaudeDesktop() {
|
|
|
9141
9166
|
}
|
|
9142
9167
|
}
|
|
9143
9168
|
function teardownClaudeDesktop() {
|
|
9144
|
-
const
|
|
9145
|
-
if (!
|
|
9169
|
+
const configPath = claudeDesktopConfigPath();
|
|
9170
|
+
if (!configPath) {
|
|
9146
9171
|
console.log(import_chalk.default.yellow(" \u26A0\uFE0F Claude Desktop is not supported on this platform."));
|
|
9147
9172
|
return;
|
|
9148
9173
|
}
|
|
9149
|
-
const config = readJson(
|
|
9174
|
+
const config = readJson(configPath);
|
|
9150
9175
|
if (!config?.mcpServers) {
|
|
9151
9176
|
console.log(import_chalk.default.blue(" \u2139\uFE0F Claude Desktop config not found \u2014 nothing to remove"));
|
|
9152
9177
|
return;
|
|
@@ -9154,7 +9179,7 @@ function teardownClaudeDesktop() {
|
|
|
9154
9179
|
let changed = false;
|
|
9155
9180
|
if (removeNode9McpServer(config.mcpServers)) {
|
|
9156
9181
|
changed = true;
|
|
9157
|
-
console.log(import_chalk.default.green(` \u2705 Removed node9 MCP server entry from ${
|
|
9182
|
+
console.log(import_chalk.default.green(` \u2705 Removed node9 MCP server entry from ${configPath}`));
|
|
9158
9183
|
}
|
|
9159
9184
|
for (const [name, server] of Object.entries(config.mcpServers)) {
|
|
9160
9185
|
const args = server.args;
|
|
@@ -9169,7 +9194,7 @@ function teardownClaudeDesktop() {
|
|
|
9169
9194
|
}
|
|
9170
9195
|
}
|
|
9171
9196
|
if (changed) {
|
|
9172
|
-
writeJson(
|
|
9197
|
+
writeJson(configPath, config);
|
|
9173
9198
|
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in Claude Desktop config"));
|
|
9174
9199
|
} else {
|
|
9175
9200
|
console.log(import_chalk.default.blue(" \u2139\uFE0F No Node9-wrapped MCP servers found in Claude Desktop config"));
|
|
@@ -9197,7 +9222,7 @@ async function setupOpencode() {
|
|
|
9197
9222
|
const homeDir2 = import_os12.default.homedir();
|
|
9198
9223
|
const configDir = import_path15.default.join(homeDir2, ".config", "opencode");
|
|
9199
9224
|
const pluginsDir = import_path15.default.join(configDir, "plugins");
|
|
9200
|
-
const
|
|
9225
|
+
const configPath = import_path15.default.join(configDir, "opencode.json");
|
|
9201
9226
|
const pluginPath = import_path15.default.join(pluginsDir, OPENCODE_PLUGIN_NAME);
|
|
9202
9227
|
try {
|
|
9203
9228
|
import_fs13.default.mkdirSync(pluginsDir, { recursive: true });
|
|
@@ -9231,7 +9256,7 @@ async function setupOpencode() {
|
|
|
9231
9256
|
);
|
|
9232
9257
|
}
|
|
9233
9258
|
}
|
|
9234
|
-
const config = readJson(
|
|
9259
|
+
const config = readJson(configPath) ?? {};
|
|
9235
9260
|
const mcp = config.mcp ?? {};
|
|
9236
9261
|
let configChanged = false;
|
|
9237
9262
|
const desiredCommand = [...node9ArgvForShim(), "mcp-server"];
|
|
@@ -9252,7 +9277,7 @@ async function setupOpencode() {
|
|
|
9252
9277
|
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
9253
9278
|
}
|
|
9254
9279
|
}
|
|
9255
|
-
if (configChanged) writeJson(
|
|
9280
|
+
if (configChanged) writeJson(configPath, config);
|
|
9256
9281
|
if (pluginChanged || configChanged) {
|
|
9257
9282
|
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Opencode!"));
|
|
9258
9283
|
console.log(import_chalk.default.gray(" Restart Opencode for changes to take effect."));
|
|
@@ -9265,7 +9290,7 @@ function teardownOpencode() {
|
|
|
9265
9290
|
const homeDir2 = import_os12.default.homedir();
|
|
9266
9291
|
const configDir = import_path15.default.join(homeDir2, ".config", "opencode");
|
|
9267
9292
|
const pluginsDir = import_path15.default.join(configDir, "plugins");
|
|
9268
|
-
const
|
|
9293
|
+
const configPath = import_path15.default.join(configDir, "opencode.json");
|
|
9269
9294
|
const pluginPath = import_path15.default.join(pluginsDir, OPENCODE_PLUGIN_NAME);
|
|
9270
9295
|
try {
|
|
9271
9296
|
if (import_fs13.default.existsSync(pluginPath)) {
|
|
@@ -9275,7 +9300,7 @@ function teardownOpencode() {
|
|
|
9275
9300
|
} catch (err2) {
|
|
9276
9301
|
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not remove ${pluginPath}: ${String(err2)}`));
|
|
9277
9302
|
}
|
|
9278
|
-
const config = readJson(
|
|
9303
|
+
const config = readJson(configPath);
|
|
9279
9304
|
if (!config) {
|
|
9280
9305
|
console.log(import_chalk.default.blue(" \u2139\uFE0F ~/.config/opencode/opencode.json not found \u2014 nothing to remove"));
|
|
9281
9306
|
return;
|
|
@@ -9291,7 +9316,7 @@ function teardownOpencode() {
|
|
|
9291
9316
|
}
|
|
9292
9317
|
if (changed) {
|
|
9293
9318
|
config.mcp = mcp;
|
|
9294
|
-
writeJson(
|
|
9319
|
+
writeJson(configPath, config);
|
|
9295
9320
|
} else {
|
|
9296
9321
|
console.log(import_chalk.default.blue(" \u2139\uFE0F No node9 entries found in ~/.config/opencode/opencode.json"));
|
|
9297
9322
|
}
|
|
@@ -9362,15 +9387,15 @@ function hermesAllowlistPath(homeDir2 = import_os12.default.homedir()) {
|
|
|
9362
9387
|
}
|
|
9363
9388
|
function setupHermes() {
|
|
9364
9389
|
const homeDir2 = import_os12.default.homedir();
|
|
9365
|
-
const
|
|
9390
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
9366
9391
|
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
9367
|
-
if (!import_fs13.default.existsSync(
|
|
9368
|
-
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config not found at ${
|
|
9392
|
+
if (!import_fs13.default.existsSync(configPath)) {
|
|
9393
|
+
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
|
|
9369
9394
|
console.log(import_chalk.default.gray(" Run `hermes setup` first, then re-run node9 agents add hermes."));
|
|
9370
9395
|
return;
|
|
9371
9396
|
}
|
|
9372
9397
|
let anythingChanged = false;
|
|
9373
|
-
const raw = import_fs13.default.readFileSync(
|
|
9398
|
+
const raw = import_fs13.default.readFileSync(configPath, "utf-8");
|
|
9374
9399
|
const doc = yaml.parseDocument(raw);
|
|
9375
9400
|
if (doc.errors.length > 0) {
|
|
9376
9401
|
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
|
|
@@ -9410,7 +9435,7 @@ function setupHermes() {
|
|
|
9410
9435
|
anythingChanged = true;
|
|
9411
9436
|
}
|
|
9412
9437
|
if (anythingChanged) {
|
|
9413
|
-
import_fs13.default.writeFileSync(
|
|
9438
|
+
import_fs13.default.writeFileSync(configPath, doc.toString());
|
|
9414
9439
|
}
|
|
9415
9440
|
let allowlist = {};
|
|
9416
9441
|
if (import_fs13.default.existsSync(allowlistPath)) {
|
|
@@ -9453,24 +9478,24 @@ function setupHermes() {
|
|
|
9453
9478
|
}
|
|
9454
9479
|
function teardownHermes() {
|
|
9455
9480
|
const homeDir2 = import_os12.default.homedir();
|
|
9456
|
-
const
|
|
9481
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
9457
9482
|
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
9458
|
-
if (!import_fs13.default.existsSync(
|
|
9459
|
-
console.log(import_chalk.default.blue(` \u2139\uFE0F ${
|
|
9483
|
+
if (!import_fs13.default.existsSync(configPath)) {
|
|
9484
|
+
console.log(import_chalk.default.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
|
|
9460
9485
|
return;
|
|
9461
9486
|
}
|
|
9462
|
-
const raw = import_fs13.default.readFileSync(
|
|
9487
|
+
const raw = import_fs13.default.readFileSync(configPath, "utf-8");
|
|
9463
9488
|
const doc = yaml.parseDocument(raw);
|
|
9464
9489
|
if (doc.errors.length > 0) {
|
|
9465
9490
|
console.log(
|
|
9466
|
-
import_chalk.default.yellow(` \u26A0\uFE0F Skipping ${
|
|
9491
|
+
import_chalk.default.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
|
|
9467
9492
|
);
|
|
9468
9493
|
} else {
|
|
9469
|
-
teardownHermesConfigDoc(doc,
|
|
9494
|
+
teardownHermesConfigDoc(doc, configPath);
|
|
9470
9495
|
}
|
|
9471
9496
|
teardownHermesAllowlist(allowlistPath);
|
|
9472
9497
|
}
|
|
9473
|
-
function teardownHermesConfigDoc(doc,
|
|
9498
|
+
function teardownHermesConfigDoc(doc, configPath) {
|
|
9474
9499
|
let anythingChanged = false;
|
|
9475
9500
|
const current = doc.toJS() ?? {};
|
|
9476
9501
|
for (const { event } of HERMES_HOOK_PLAN) {
|
|
@@ -9492,10 +9517,10 @@ function teardownHermesConfigDoc(doc, configPath2) {
|
|
|
9492
9517
|
anythingChanged = true;
|
|
9493
9518
|
}
|
|
9494
9519
|
if (anythingChanged) {
|
|
9495
|
-
import_fs13.default.writeFileSync(
|
|
9496
|
-
console.log(import_chalk.default.green(` \u2705 Removed Node9 hooks from ${
|
|
9520
|
+
import_fs13.default.writeFileSync(configPath, doc.toString());
|
|
9521
|
+
console.log(import_chalk.default.green(` \u2705 Removed Node9 hooks from ${configPath}`));
|
|
9497
9522
|
} else {
|
|
9498
|
-
console.log(import_chalk.default.blue(` \u2139\uFE0F No Node9 hooks found in ${
|
|
9523
|
+
console.log(import_chalk.default.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
|
|
9499
9524
|
}
|
|
9500
9525
|
}
|
|
9501
9526
|
function teardownHermesAllowlist(allowlistPath) {
|
|
@@ -12110,8 +12135,8 @@ function countScanFiles() {
|
|
|
12110
12135
|
const geminiDir = import_path25.default.join(import_os22.default.homedir(), ".gemini", "tmp");
|
|
12111
12136
|
if (import_fs23.default.existsSync(geminiDir)) {
|
|
12112
12137
|
try {
|
|
12113
|
-
for (const
|
|
12114
|
-
const p = import_path25.default.join(geminiDir,
|
|
12138
|
+
for (const slug2 of import_fs23.default.readdirSync(geminiDir)) {
|
|
12139
|
+
const p = import_path25.default.join(geminiDir, slug2);
|
|
12115
12140
|
try {
|
|
12116
12141
|
if (!import_fs23.default.statSync(p).isDirectory()) continue;
|
|
12117
12142
|
const chatsDir = import_path25.default.join(p, "chats");
|
|
@@ -12508,14 +12533,14 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12508
12533
|
return result;
|
|
12509
12534
|
}
|
|
12510
12535
|
const ruleSources = buildRuleSources();
|
|
12511
|
-
for (const
|
|
12512
|
-
const slugPath = import_path25.default.join(tmpDir,
|
|
12536
|
+
for (const slug2 of slugDirs) {
|
|
12537
|
+
const slugPath = import_path25.default.join(tmpDir, slug2);
|
|
12513
12538
|
try {
|
|
12514
12539
|
if (!import_fs23.default.statSync(slugPath).isDirectory()) continue;
|
|
12515
12540
|
} catch {
|
|
12516
12541
|
continue;
|
|
12517
12542
|
}
|
|
12518
|
-
let projLabel = stripTerminalEscapes(
|
|
12543
|
+
let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
|
|
12519
12544
|
try {
|
|
12520
12545
|
projLabel = stripTerminalEscapes(
|
|
12521
12546
|
import_fs23.default.readFileSync(import_path25.default.join(slugPath, ".project_root"), "utf-8").trim()
|
|
@@ -13854,7 +13879,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
13854
13879
|
const hitShieldSet = new Set(
|
|
13855
13880
|
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
13856
13881
|
);
|
|
13857
|
-
const zeroHitBuiltins = Object.keys(
|
|
13882
|
+
const zeroHitBuiltins = Object.keys(BUILTIN_SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
13858
13883
|
if (zeroHitBuiltins.length > 0) {
|
|
13859
13884
|
shieldLines.push(mkLine([""]));
|
|
13860
13885
|
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), import_chalk5.default.dim]));
|
|
@@ -18747,20 +18772,20 @@ function getModelContextLimit(model) {
|
|
|
18747
18772
|
return 2e5;
|
|
18748
18773
|
}
|
|
18749
18774
|
function readSessionUsage() {
|
|
18750
|
-
const projectsDir =
|
|
18751
|
-
if (!
|
|
18775
|
+
const projectsDir = import_path61.default.join(import_os54.default.homedir(), ".claude", "projects");
|
|
18776
|
+
if (!import_fs64.default.existsSync(projectsDir)) return null;
|
|
18752
18777
|
let latestFile = null;
|
|
18753
18778
|
let latestMtime = 0;
|
|
18754
18779
|
try {
|
|
18755
|
-
for (const dir of
|
|
18756
|
-
const dirPath =
|
|
18780
|
+
for (const dir of import_fs64.default.readdirSync(projectsDir)) {
|
|
18781
|
+
const dirPath = import_path61.default.join(projectsDir, dir);
|
|
18757
18782
|
try {
|
|
18758
|
-
if (!
|
|
18759
|
-
for (const file of
|
|
18783
|
+
if (!import_fs64.default.statSync(dirPath).isDirectory()) continue;
|
|
18784
|
+
for (const file of import_fs64.default.readdirSync(dirPath)) {
|
|
18760
18785
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
18761
|
-
const filePath =
|
|
18786
|
+
const filePath = import_path61.default.join(dirPath, file);
|
|
18762
18787
|
try {
|
|
18763
|
-
const mtime =
|
|
18788
|
+
const mtime = import_fs64.default.statSync(filePath).mtimeMs;
|
|
18764
18789
|
if (mtime > latestMtime) {
|
|
18765
18790
|
latestMtime = mtime;
|
|
18766
18791
|
latestFile = filePath;
|
|
@@ -18775,7 +18800,7 @@ function readSessionUsage() {
|
|
|
18775
18800
|
}
|
|
18776
18801
|
if (!latestFile) return null;
|
|
18777
18802
|
try {
|
|
18778
|
-
const lines =
|
|
18803
|
+
const lines = import_fs64.default.readFileSync(latestFile, "utf-8").split("\n");
|
|
18779
18804
|
let lastModel = "";
|
|
18780
18805
|
let lastInput = 0;
|
|
18781
18806
|
let lastOutput = 0;
|
|
@@ -18800,10 +18825,10 @@ function readSessionUsage() {
|
|
|
18800
18825
|
}
|
|
18801
18826
|
}
|
|
18802
18827
|
function formatContextStat(stat) {
|
|
18803
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
18828
|
+
const pctColor = stat.fillPct >= 80 ? import_chalk35.default.red : stat.fillPct >= 50 ? import_chalk35.default.yellow : import_chalk35.default.cyan;
|
|
18804
18829
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
18805
18830
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
18806
|
-
return
|
|
18831
|
+
return import_chalk35.default.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + import_chalk35.default.dim(
|
|
18807
18832
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
18808
18833
|
);
|
|
18809
18834
|
}
|
|
@@ -18826,32 +18851,32 @@ function agentLabel(agent, mcpServer, sessionId) {
|
|
|
18826
18851
|
const tag = sessionTag(sessionId);
|
|
18827
18852
|
const tagSuffix = tag ? `\xB7${tag}` : "";
|
|
18828
18853
|
if (!agent || agent === "Terminal") {
|
|
18829
|
-
return mcpServer ?
|
|
18854
|
+
return mcpServer ? import_chalk35.default.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
18830
18855
|
}
|
|
18831
18856
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
18832
|
-
if (!short) return mcpServer ?
|
|
18833
|
-
return mcpServer ?
|
|
18857
|
+
if (!short) return mcpServer ? import_chalk35.default.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
18858
|
+
return mcpServer ? import_chalk35.default.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : import_chalk35.default.dim(`[${short}${tagSuffix}] `);
|
|
18834
18859
|
}
|
|
18835
18860
|
function formatBase(activity) {
|
|
18836
18861
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
18837
18862
|
const icon = getIcon(activity.tool);
|
|
18838
18863
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
18839
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
18864
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os54.default.homedir(), "~");
|
|
18840
18865
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
18841
|
-
return `${
|
|
18866
|
+
return `${import_chalk35.default.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${import_chalk35.default.white.bold(toolName)} ${import_chalk35.default.dim(argsPreview)}`;
|
|
18842
18867
|
}
|
|
18843
18868
|
function renderResult(activity, result) {
|
|
18844
18869
|
const base = formatBase(activity);
|
|
18845
18870
|
let status;
|
|
18846
18871
|
if (result.status === "allow") {
|
|
18847
|
-
status =
|
|
18872
|
+
status = import_chalk35.default.green("\u2713 ALLOW");
|
|
18848
18873
|
} else if (result.status === "dlp") {
|
|
18849
|
-
status =
|
|
18874
|
+
status = import_chalk35.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
18850
18875
|
} else {
|
|
18851
|
-
status =
|
|
18876
|
+
status = import_chalk35.default.red("\u2717 BLOCK");
|
|
18852
18877
|
}
|
|
18853
18878
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
18854
|
-
const costSuffix = cost == null ? "" :
|
|
18879
|
+
const costSuffix = cost == null ? "" : import_chalk35.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
18855
18880
|
if (process.stdout.isTTY) {
|
|
18856
18881
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
18857
18882
|
import_readline6.default.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -18868,19 +18893,19 @@ function renderResult(activity, result) {
|
|
|
18868
18893
|
}
|
|
18869
18894
|
function renderPending(activity) {
|
|
18870
18895
|
if (!process.stdout.isTTY) return;
|
|
18871
|
-
const line = `${formatBase(activity)} ${
|
|
18896
|
+
const line = `${formatBase(activity)} ${import_chalk35.default.yellow("\u25CF \u2026")}`;
|
|
18872
18897
|
pendingShownForId = activity.id;
|
|
18873
18898
|
pendingWrappedLines = wrappedLineCount(line);
|
|
18874
18899
|
process.stdout.write(`${line}\r`);
|
|
18875
18900
|
}
|
|
18876
18901
|
async function ensureDaemon() {
|
|
18877
18902
|
let pidPort = null;
|
|
18878
|
-
if (
|
|
18903
|
+
if (import_fs64.default.existsSync(PID_FILE)) {
|
|
18879
18904
|
try {
|
|
18880
|
-
const { port } = JSON.parse(
|
|
18905
|
+
const { port } = JSON.parse(import_fs64.default.readFileSync(PID_FILE, "utf-8"));
|
|
18881
18906
|
pidPort = port;
|
|
18882
18907
|
} catch {
|
|
18883
|
-
console.error(
|
|
18908
|
+
console.error(import_chalk35.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
18884
18909
|
}
|
|
18885
18910
|
}
|
|
18886
18911
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -18891,7 +18916,7 @@ async function ensureDaemon() {
|
|
|
18891
18916
|
if (res.ok) return checkPort;
|
|
18892
18917
|
} catch {
|
|
18893
18918
|
}
|
|
18894
|
-
console.log(
|
|
18919
|
+
console.log(import_chalk35.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
18895
18920
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
18896
18921
|
detached: true,
|
|
18897
18922
|
stdio: "ignore",
|
|
@@ -18908,7 +18933,7 @@ async function ensureDaemon() {
|
|
|
18908
18933
|
} catch {
|
|
18909
18934
|
}
|
|
18910
18935
|
}
|
|
18911
|
-
console.error(
|
|
18936
|
+
console.error(import_chalk35.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
18912
18937
|
process.exit(1);
|
|
18913
18938
|
}
|
|
18914
18939
|
function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
@@ -18977,7 +19002,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
18977
19002
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
18978
19003
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
18979
19004
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
18980
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${
|
|
19005
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${import_chalk35.default.dim(`(${req.agent})`)}` : "";
|
|
18981
19006
|
const lines = [
|
|
18982
19007
|
``,
|
|
18983
19008
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
@@ -19033,9 +19058,9 @@ function buildRecoveryCardLines(req) {
|
|
|
19033
19058
|
];
|
|
19034
19059
|
}
|
|
19035
19060
|
function readApproversFromDisk() {
|
|
19036
|
-
const
|
|
19061
|
+
const configPath = import_path61.default.join(import_os54.default.homedir(), ".node9", "config.json");
|
|
19037
19062
|
try {
|
|
19038
|
-
const raw = JSON.parse(
|
|
19063
|
+
const raw = JSON.parse(import_fs64.default.readFileSync(configPath, "utf-8"));
|
|
19039
19064
|
const settings = raw.settings ?? {};
|
|
19040
19065
|
return settings.approvers ?? {};
|
|
19041
19066
|
} catch {
|
|
@@ -19046,20 +19071,20 @@ function approverStatusLine() {
|
|
|
19046
19071
|
const a = readApproversFromDisk();
|
|
19047
19072
|
const fmt = (label2, key) => {
|
|
19048
19073
|
const on = a[key] !== false;
|
|
19049
|
-
return `[${key[0]}]${label2.slice(1)} ${on ?
|
|
19074
|
+
return `[${key[0]}]${label2.slice(1)} ${on ? import_chalk35.default.green("\u2713") : import_chalk35.default.dim("\u2717")}`;
|
|
19050
19075
|
};
|
|
19051
19076
|
return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
19052
19077
|
}
|
|
19053
19078
|
function toggleApprover(channel) {
|
|
19054
|
-
const
|
|
19079
|
+
const configPath = import_path61.default.join(import_os54.default.homedir(), ".node9", "config.json");
|
|
19055
19080
|
try {
|
|
19056
|
-
const raw = JSON.parse(
|
|
19081
|
+
const raw = JSON.parse(import_fs64.default.readFileSync(configPath, "utf-8"));
|
|
19057
19082
|
const settings = raw.settings ?? {};
|
|
19058
19083
|
const approvers = settings.approvers ?? {};
|
|
19059
19084
|
approvers[channel] = approvers[channel] === false;
|
|
19060
19085
|
settings.approvers = approvers;
|
|
19061
19086
|
raw.settings = settings;
|
|
19062
|
-
|
|
19087
|
+
import_fs64.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
19063
19088
|
} catch (err2) {
|
|
19064
19089
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
19065
19090
|
`);
|
|
@@ -19091,7 +19116,7 @@ async function startTail(options = {}) {
|
|
|
19091
19116
|
req2.end();
|
|
19092
19117
|
});
|
|
19093
19118
|
if (result.ok) {
|
|
19094
|
-
console.log(
|
|
19119
|
+
console.log(import_chalk35.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
19095
19120
|
} else if (result.code === "ECONNREFUSED") {
|
|
19096
19121
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
19097
19122
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -19137,7 +19162,7 @@ async function startTail(options = {}) {
|
|
|
19137
19162
|
const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
19138
19163
|
if (channel) {
|
|
19139
19164
|
toggleApprover(channel);
|
|
19140
|
-
console.log(
|
|
19165
|
+
console.log(import_chalk35.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
19141
19166
|
}
|
|
19142
19167
|
};
|
|
19143
19168
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -19203,7 +19228,7 @@ async function startTail(options = {}) {
|
|
|
19203
19228
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
19204
19229
|
)
|
|
19205
19230
|
);
|
|
19206
|
-
const decisionStamp = action === "always-allow" ?
|
|
19231
|
+
const decisionStamp = action === "always-allow" ? import_chalk35.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk35.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk35.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk35.default.yellow("\u21A9 REDIRECT AI") : import_chalk35.default.red("\u2717 DENIED");
|
|
19207
19232
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
19208
19233
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
19209
19234
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -19231,8 +19256,8 @@ async function startTail(options = {}) {
|
|
|
19231
19256
|
}
|
|
19232
19257
|
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
|
|
19233
19258
|
try {
|
|
19234
|
-
|
|
19235
|
-
|
|
19259
|
+
import_fs64.default.appendFileSync(
|
|
19260
|
+
import_path61.default.join(import_os54.default.homedir(), ".node9", "hook-debug.log"),
|
|
19236
19261
|
`[tail] POST /decision failed: ${String(err2)}
|
|
19237
19262
|
`
|
|
19238
19263
|
);
|
|
@@ -19254,7 +19279,7 @@ async function startTail(options = {}) {
|
|
|
19254
19279
|
);
|
|
19255
19280
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
19256
19281
|
if (externalDecision) {
|
|
19257
|
-
const source = externalDecision === "allow" ?
|
|
19282
|
+
const source = externalDecision === "allow" ? import_chalk35.default.green("\u2713 ALLOWED") : import_chalk35.default.red("\u2717 DENIED");
|
|
19258
19283
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
19259
19284
|
}
|
|
19260
19285
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -19296,31 +19321,31 @@ async function startTail(options = {}) {
|
|
|
19296
19321
|
};
|
|
19297
19322
|
process.stdin.on("keypress", onKeypress);
|
|
19298
19323
|
}
|
|
19299
|
-
const auditLog =
|
|
19324
|
+
const auditLog = import_path61.default.join(import_os54.default.homedir(), ".node9", "audit.log");
|
|
19300
19325
|
try {
|
|
19301
|
-
const unackedDlp =
|
|
19326
|
+
const unackedDlp = import_fs64.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
19302
19327
|
if (unackedDlp > 0) {
|
|
19303
19328
|
console.log("");
|
|
19304
19329
|
console.log(
|
|
19305
|
-
|
|
19330
|
+
import_chalk35.default.bgRed.white.bold(
|
|
19306
19331
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
19307
19332
|
)
|
|
19308
19333
|
);
|
|
19309
19334
|
}
|
|
19310
19335
|
} catch {
|
|
19311
19336
|
}
|
|
19312
|
-
console.log(
|
|
19337
|
+
console.log(import_chalk35.default.cyan.bold(`
|
|
19313
19338
|
\u{1F6F0}\uFE0F Node9 tail`));
|
|
19314
19339
|
if (canApprove) {
|
|
19315
|
-
console.log(
|
|
19316
|
-
console.log(
|
|
19340
|
+
console.log(import_chalk35.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
19341
|
+
console.log(import_chalk35.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
19317
19342
|
}
|
|
19318
19343
|
const ctxStat = readSessionUsage();
|
|
19319
19344
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
19320
19345
|
if (options.history) {
|
|
19321
|
-
console.log(
|
|
19346
|
+
console.log(import_chalk35.default.dim("Showing history + live events.\n"));
|
|
19322
19347
|
} else {
|
|
19323
|
-
console.log(
|
|
19348
|
+
console.log(import_chalk35.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
19324
19349
|
}
|
|
19325
19350
|
process.on("SIGINT", () => {
|
|
19326
19351
|
exitIdleMode();
|
|
@@ -19330,7 +19355,7 @@ async function startTail(options = {}) {
|
|
|
19330
19355
|
import_readline6.default.clearLine(process.stdout, 0);
|
|
19331
19356
|
import_readline6.default.cursorTo(process.stdout, 0);
|
|
19332
19357
|
}
|
|
19333
|
-
console.log(
|
|
19358
|
+
console.log(import_chalk35.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
19334
19359
|
process.exit(0);
|
|
19335
19360
|
});
|
|
19336
19361
|
const STALL_THRESHOLD_MS = 6e4;
|
|
@@ -19338,11 +19363,11 @@ async function startTail(options = {}) {
|
|
|
19338
19363
|
if (stallWarned) return;
|
|
19339
19364
|
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
19340
19365
|
try {
|
|
19341
|
-
const auditMtime =
|
|
19366
|
+
const auditMtime = import_fs64.default.statSync(auditLog).mtimeMs;
|
|
19342
19367
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
19343
19368
|
console.log("");
|
|
19344
19369
|
console.log(
|
|
19345
|
-
|
|
19370
|
+
import_chalk35.default.yellow(
|
|
19346
19371
|
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
19347
19372
|
)
|
|
19348
19373
|
);
|
|
@@ -19359,7 +19384,7 @@ async function startTail(options = {}) {
|
|
|
19359
19384
|
},
|
|
19360
19385
|
(res) => {
|
|
19361
19386
|
if (res.statusCode !== 200) {
|
|
19362
|
-
console.error(
|
|
19387
|
+
console.error(import_chalk35.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
19363
19388
|
process.exit(1);
|
|
19364
19389
|
}
|
|
19365
19390
|
if (canApprove) enterIdleMode();
|
|
@@ -19390,7 +19415,7 @@ async function startTail(options = {}) {
|
|
|
19390
19415
|
import_readline6.default.clearLine(process.stdout, 0);
|
|
19391
19416
|
import_readline6.default.cursorTo(process.stdout, 0);
|
|
19392
19417
|
}
|
|
19393
|
-
console.log(
|
|
19418
|
+
console.log(import_chalk35.default.red("\n\u274C Daemon disconnected."));
|
|
19394
19419
|
process.exit(1);
|
|
19395
19420
|
});
|
|
19396
19421
|
}
|
|
@@ -19403,7 +19428,7 @@ async function startTail(options = {}) {
|
|
|
19403
19428
|
const parsed = JSON.parse(rawData);
|
|
19404
19429
|
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
19405
19430
|
console.log("");
|
|
19406
|
-
console.log(
|
|
19431
|
+
console.log(import_chalk35.default.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
19407
19432
|
} catch {
|
|
19408
19433
|
}
|
|
19409
19434
|
return;
|
|
@@ -19488,9 +19513,9 @@ async function startTail(options = {}) {
|
|
|
19488
19513
|
const rawSummary = data.argsSummary ?? data.tool;
|
|
19489
19514
|
const summary = shortenPathSummary(rawSummary);
|
|
19490
19515
|
const fileCount = data.fileCount ?? 0;
|
|
19491
|
-
const files = fileCount > 0 ?
|
|
19516
|
+
const files = fileCount > 0 ? import_chalk35.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
19492
19517
|
process.stdout.write(
|
|
19493
|
-
`${
|
|
19518
|
+
`${import_chalk35.default.dim(time)} ${import_chalk35.default.cyan("\u{1F4F8} snapshot")} ${import_chalk35.default.dim(hash)} ${summary}${files}
|
|
19494
19519
|
`
|
|
19495
19520
|
);
|
|
19496
19521
|
return;
|
|
@@ -19507,36 +19532,36 @@ async function startTail(options = {}) {
|
|
|
19507
19532
|
if (event === "execution-result") {
|
|
19508
19533
|
const exec = data;
|
|
19509
19534
|
const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
|
|
19510
|
-
const arrow = exec.isError ?
|
|
19535
|
+
const arrow = exec.isError ? import_chalk35.default.red(" \u21B3 \u2717") : import_chalk35.default.green(" \u21B3 \u2713");
|
|
19511
19536
|
const label2 = agentLabel(exec.agent, exec.mcpServer);
|
|
19512
19537
|
const tool = (exec.tool ?? "").slice(0, 16);
|
|
19513
|
-
const duration = typeof exec.durationMs === "number" ?
|
|
19538
|
+
const duration = typeof exec.durationMs === "number" ? import_chalk35.default.dim(` (${exec.durationMs}ms)`) : "";
|
|
19514
19539
|
console.log(
|
|
19515
|
-
`${
|
|
19540
|
+
`${import_chalk35.default.gray(time)} ${arrow} ${label2}${import_chalk35.default.dim(tool)}${import_chalk35.default.dim(" completed")}${duration}`
|
|
19516
19541
|
);
|
|
19517
19542
|
}
|
|
19518
19543
|
}
|
|
19519
19544
|
req.on("error", (err2) => {
|
|
19520
19545
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
19521
|
-
console.error(
|
|
19546
|
+
console.error(import_chalk35.default.red(`
|
|
19522
19547
|
\u274C ${msg}`));
|
|
19523
19548
|
process.exit(1);
|
|
19524
19549
|
});
|
|
19525
19550
|
}
|
|
19526
|
-
var import_http3,
|
|
19551
|
+
var import_http3, import_chalk35, import_fs64, import_os54, import_path61, import_readline6, import_child_process14, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
19527
19552
|
var init_tail = __esm({
|
|
19528
19553
|
"src/tui/tail.ts"() {
|
|
19529
19554
|
"use strict";
|
|
19530
19555
|
import_http3 = __toESM(require("http"));
|
|
19531
|
-
|
|
19532
|
-
|
|
19533
|
-
|
|
19534
|
-
|
|
19556
|
+
import_chalk35 = __toESM(require("chalk"));
|
|
19557
|
+
import_fs64 = __toESM(require("fs"));
|
|
19558
|
+
import_os54 = __toESM(require("os"));
|
|
19559
|
+
import_path61 = __toESM(require("path"));
|
|
19535
19560
|
import_readline6 = __toESM(require("readline"));
|
|
19536
19561
|
import_child_process14 = require("child_process");
|
|
19537
19562
|
init_daemon2();
|
|
19538
19563
|
init_daemon();
|
|
19539
|
-
PID_FILE =
|
|
19564
|
+
PID_FILE = import_path61.default.join(import_os54.default.homedir(), ".node9", "daemon.pid");
|
|
19540
19565
|
ICONS = {
|
|
19541
19566
|
bash: "\u{1F4BB}",
|
|
19542
19567
|
shell: "\u{1F4BB}",
|
|
@@ -19658,9 +19683,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
19658
19683
|
return ` (${m}m left)`;
|
|
19659
19684
|
}
|
|
19660
19685
|
function safeReadJson(filePath) {
|
|
19661
|
-
if (!
|
|
19686
|
+
if (!import_fs65.default.existsSync(filePath)) return null;
|
|
19662
19687
|
try {
|
|
19663
|
-
return JSON.parse(
|
|
19688
|
+
return JSON.parse(import_fs65.default.readFileSync(filePath, "utf-8"));
|
|
19664
19689
|
} catch {
|
|
19665
19690
|
return null;
|
|
19666
19691
|
}
|
|
@@ -19681,12 +19706,12 @@ function countHooksInFile(filePath) {
|
|
|
19681
19706
|
return Object.keys(cfg.hooks).length;
|
|
19682
19707
|
}
|
|
19683
19708
|
function countRulesInDir(rulesDir) {
|
|
19684
|
-
if (!
|
|
19709
|
+
if (!import_fs65.default.existsSync(rulesDir)) return 0;
|
|
19685
19710
|
let count = 0;
|
|
19686
19711
|
try {
|
|
19687
|
-
for (const entry of
|
|
19712
|
+
for (const entry of import_fs65.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
19688
19713
|
if (entry.isDirectory()) {
|
|
19689
|
-
count += countRulesInDir(
|
|
19714
|
+
count += countRulesInDir(import_path62.default.join(rulesDir, entry.name));
|
|
19690
19715
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
19691
19716
|
count++;
|
|
19692
19717
|
}
|
|
@@ -19697,46 +19722,46 @@ function countRulesInDir(rulesDir) {
|
|
|
19697
19722
|
}
|
|
19698
19723
|
function isSamePath(a, b) {
|
|
19699
19724
|
try {
|
|
19700
|
-
return
|
|
19725
|
+
return import_path62.default.resolve(a) === import_path62.default.resolve(b);
|
|
19701
19726
|
} catch {
|
|
19702
19727
|
return false;
|
|
19703
19728
|
}
|
|
19704
19729
|
}
|
|
19705
19730
|
function countConfigs(cwd) {
|
|
19706
|
-
const homeDir2 =
|
|
19707
|
-
const claudeDir =
|
|
19731
|
+
const homeDir2 = import_os55.default.homedir();
|
|
19732
|
+
const claudeDir = import_path62.default.join(homeDir2, ".claude");
|
|
19708
19733
|
let claudeMdCount = 0;
|
|
19709
19734
|
let rulesCount = 0;
|
|
19710
19735
|
let hooksCount = 0;
|
|
19711
19736
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
19712
19737
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
19713
|
-
if (
|
|
19714
|
-
rulesCount += countRulesInDir(
|
|
19715
|
-
const userSettings =
|
|
19738
|
+
if (import_fs65.default.existsSync(import_path62.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
19739
|
+
rulesCount += countRulesInDir(import_path62.default.join(claudeDir, "rules"));
|
|
19740
|
+
const userSettings = import_path62.default.join(claudeDir, "settings.json");
|
|
19716
19741
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
19717
19742
|
hooksCount += countHooksInFile(userSettings);
|
|
19718
|
-
const userClaudeJson =
|
|
19743
|
+
const userClaudeJson = import_path62.default.join(homeDir2, ".claude.json");
|
|
19719
19744
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
19720
19745
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
19721
19746
|
userMcpServers.delete(name);
|
|
19722
19747
|
}
|
|
19723
19748
|
if (cwd) {
|
|
19724
|
-
if (
|
|
19725
|
-
if (
|
|
19726
|
-
const projectClaudeDir =
|
|
19749
|
+
if (import_fs65.default.existsSync(import_path62.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
19750
|
+
if (import_fs65.default.existsSync(import_path62.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
19751
|
+
const projectClaudeDir = import_path62.default.join(cwd, ".claude");
|
|
19727
19752
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
19728
19753
|
if (!overlapsUserScope) {
|
|
19729
|
-
if (
|
|
19730
|
-
rulesCount += countRulesInDir(
|
|
19731
|
-
const projSettings =
|
|
19754
|
+
if (import_fs65.default.existsSync(import_path62.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
19755
|
+
rulesCount += countRulesInDir(import_path62.default.join(projectClaudeDir, "rules"));
|
|
19756
|
+
const projSettings = import_path62.default.join(projectClaudeDir, "settings.json");
|
|
19732
19757
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
19733
19758
|
hooksCount += countHooksInFile(projSettings);
|
|
19734
19759
|
}
|
|
19735
|
-
if (
|
|
19736
|
-
const localSettings =
|
|
19760
|
+
if (import_fs65.default.existsSync(import_path62.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
19761
|
+
const localSettings = import_path62.default.join(projectClaudeDir, "settings.local.json");
|
|
19737
19762
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
19738
19763
|
hooksCount += countHooksInFile(localSettings);
|
|
19739
|
-
const mcpJsonServers = getMcpServerNames(
|
|
19764
|
+
const mcpJsonServers = getMcpServerNames(import_path62.default.join(cwd, ".mcp.json"));
|
|
19740
19765
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
19741
19766
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
19742
19767
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -19769,12 +19794,12 @@ function readActiveShieldsHud() {
|
|
|
19769
19794
|
return shieldsCache.value;
|
|
19770
19795
|
}
|
|
19771
19796
|
try {
|
|
19772
|
-
const shieldsPath =
|
|
19773
|
-
if (!
|
|
19797
|
+
const shieldsPath = import_path62.default.join(import_os55.default.homedir(), ".node9", "shields.json");
|
|
19798
|
+
if (!import_fs65.default.existsSync(shieldsPath)) {
|
|
19774
19799
|
shieldsCache = { value: [], ts: now };
|
|
19775
19800
|
return [];
|
|
19776
19801
|
}
|
|
19777
|
-
const parsed = JSON.parse(
|
|
19802
|
+
const parsed = JSON.parse(import_fs65.default.readFileSync(shieldsPath, "utf-8"));
|
|
19778
19803
|
if (!Array.isArray(parsed.active)) {
|
|
19779
19804
|
shieldsCache = { value: [], ts: now };
|
|
19780
19805
|
return [];
|
|
@@ -19876,17 +19901,17 @@ function renderContextLine(stdin) {
|
|
|
19876
19901
|
async function main() {
|
|
19877
19902
|
try {
|
|
19878
19903
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
19879
|
-
if (
|
|
19904
|
+
if (import_fs65.default.existsSync(import_path62.default.join(import_os55.default.homedir(), ".node9", "hud-debug"))) {
|
|
19880
19905
|
try {
|
|
19881
|
-
const logPath =
|
|
19906
|
+
const logPath = import_path62.default.join(import_os55.default.homedir(), ".node9", "hud-debug.log");
|
|
19882
19907
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
19883
19908
|
let size = 0;
|
|
19884
19909
|
try {
|
|
19885
|
-
size =
|
|
19910
|
+
size = import_fs65.default.statSync(logPath).size;
|
|
19886
19911
|
} catch {
|
|
19887
19912
|
}
|
|
19888
19913
|
if (size < MAX_LOG_SIZE) {
|
|
19889
|
-
|
|
19914
|
+
import_fs65.default.appendFileSync(
|
|
19890
19915
|
logPath,
|
|
19891
19916
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
19892
19917
|
);
|
|
@@ -19906,12 +19931,12 @@ async function main() {
|
|
|
19906
19931
|
const showEnvCounts = (() => {
|
|
19907
19932
|
try {
|
|
19908
19933
|
const cwd = stdin.cwd ?? process.cwd();
|
|
19909
|
-
for (const
|
|
19910
|
-
|
|
19911
|
-
|
|
19934
|
+
for (const configPath of [
|
|
19935
|
+
import_path62.default.join(cwd, "node9.config.json"),
|
|
19936
|
+
import_path62.default.join(import_os55.default.homedir(), ".node9", "config.json")
|
|
19912
19937
|
]) {
|
|
19913
|
-
if (!
|
|
19914
|
-
const cfg = JSON.parse(
|
|
19938
|
+
if (!import_fs65.default.existsSync(configPath)) continue;
|
|
19939
|
+
const cfg = JSON.parse(import_fs65.default.readFileSync(configPath, "utf-8"));
|
|
19915
19940
|
const hud = cfg.settings?.hud;
|
|
19916
19941
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
19917
19942
|
}
|
|
@@ -19929,13 +19954,13 @@ async function main() {
|
|
|
19929
19954
|
renderOffline();
|
|
19930
19955
|
}
|
|
19931
19956
|
}
|
|
19932
|
-
var
|
|
19957
|
+
var import_fs65, import_path62, import_os55, import_http4, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
19933
19958
|
var init_hud = __esm({
|
|
19934
19959
|
"src/cli/hud.ts"() {
|
|
19935
19960
|
"use strict";
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19961
|
+
import_fs65 = __toESM(require("fs"));
|
|
19962
|
+
import_path62 = __toESM(require("path"));
|
|
19963
|
+
import_os55 = __toESM(require("os"));
|
|
19939
19964
|
import_http4 = __toESM(require("http"));
|
|
19940
19965
|
init_daemon();
|
|
19941
19966
|
RESET3 = "\x1B[0m";
|
|
@@ -19961,10 +19986,10 @@ var import_commander = require("commander");
|
|
|
19961
19986
|
init_core();
|
|
19962
19987
|
init_setup();
|
|
19963
19988
|
init_daemon2();
|
|
19964
|
-
var
|
|
19965
|
-
var
|
|
19966
|
-
var
|
|
19967
|
-
var
|
|
19989
|
+
var import_chalk36 = __toESM(require("chalk"));
|
|
19990
|
+
var import_fs66 = __toESM(require("fs"));
|
|
19991
|
+
var import_path63 = __toESM(require("path"));
|
|
19992
|
+
var import_os56 = __toESM(require("os"));
|
|
19968
19993
|
var import_child_process15 = require("child_process");
|
|
19969
19994
|
var import_prompts2 = require("@inquirer/prompts");
|
|
19970
19995
|
|
|
@@ -21593,8 +21618,134 @@ function registerLogCommand(program2) {
|
|
|
21593
21618
|
|
|
21594
21619
|
// src/cli/commands/shield.ts
|
|
21595
21620
|
var import_chalk10 = __toESM(require("chalk"));
|
|
21621
|
+
var import_fs46 = __toESM(require("fs"));
|
|
21622
|
+
init_shields();
|
|
21623
|
+
|
|
21624
|
+
// src/shields/build.ts
|
|
21625
|
+
function escapeRegex(s) {
|
|
21626
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21627
|
+
}
|
|
21628
|
+
function slug(s) {
|
|
21629
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "rule";
|
|
21630
|
+
}
|
|
21631
|
+
var B = "[\\s/\\\\]";
|
|
21632
|
+
var SEP = "[/\\\\]";
|
|
21633
|
+
function pathToRegexFragment(rawPath) {
|
|
21634
|
+
const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
|
|
21635
|
+
const segments = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
|
|
21636
|
+
if (segments.length === 0) return "";
|
|
21637
|
+
return `(^|${B})${segments.join(SEP)}(${B}|$)`;
|
|
21638
|
+
}
|
|
21639
|
+
function toolRule(tool, verdict, reason) {
|
|
21640
|
+
return {
|
|
21641
|
+
name: `${verdict}-${slug(tool)}`,
|
|
21642
|
+
tool,
|
|
21643
|
+
conditions: [],
|
|
21644
|
+
verdict,
|
|
21645
|
+
reason: reason ?? `${tool} is restricted by this shield`
|
|
21646
|
+
};
|
|
21647
|
+
}
|
|
21648
|
+
function pathRules(rawPath, verdict, reason) {
|
|
21649
|
+
const value = pathToRegexFragment(rawPath);
|
|
21650
|
+
if (!value) return [];
|
|
21651
|
+
const why = reason ?? `Accessing ${rawPath} is restricted by this shield`;
|
|
21652
|
+
const s = slug(rawPath);
|
|
21653
|
+
return [
|
|
21654
|
+
{
|
|
21655
|
+
name: `${verdict}-path-${s}-bash`,
|
|
21656
|
+
tool: "bash",
|
|
21657
|
+
conditions: [{ field: "command", op: "matches", value }],
|
|
21658
|
+
verdict,
|
|
21659
|
+
reason: why
|
|
21660
|
+
},
|
|
21661
|
+
{
|
|
21662
|
+
name: `${verdict}-path-${s}-anytool`,
|
|
21663
|
+
tool: "*",
|
|
21664
|
+
conditions: [{ field: "file_path", op: "matches", value }],
|
|
21665
|
+
verdict,
|
|
21666
|
+
reason: why
|
|
21667
|
+
}
|
|
21668
|
+
];
|
|
21669
|
+
}
|
|
21670
|
+
function buildShield(input) {
|
|
21671
|
+
const smartRules = [
|
|
21672
|
+
...(input.blockTools ?? []).map((t) => toolRule(t, "block")),
|
|
21673
|
+
...(input.reviewTools ?? []).map((t) => toolRule(t, "review")),
|
|
21674
|
+
...(input.blockPaths ?? []).flatMap((p) => pathRules(p, "block")),
|
|
21675
|
+
...(input.reviewPaths ?? []).flatMap((p) => pathRules(p, "review"))
|
|
21676
|
+
];
|
|
21677
|
+
return {
|
|
21678
|
+
name: input.name,
|
|
21679
|
+
description: input.description ?? `Custom shield "${input.name}" created with node9 shield create`,
|
|
21680
|
+
aliases: input.aliases ?? [],
|
|
21681
|
+
smartRules,
|
|
21682
|
+
dangerousWords: []
|
|
21683
|
+
};
|
|
21684
|
+
}
|
|
21685
|
+
|
|
21686
|
+
// src/shields/create.ts
|
|
21687
|
+
var import_fs45 = __toESM(require("fs"));
|
|
21688
|
+
var import_path43 = __toESM(require("path"));
|
|
21689
|
+
init_dist();
|
|
21596
21690
|
init_shields();
|
|
21597
21691
|
init_audit();
|
|
21692
|
+
function builtinNames() {
|
|
21693
|
+
const names = /* @__PURE__ */ new Set();
|
|
21694
|
+
for (const def of Object.values(BUILTIN_SHIELDS)) {
|
|
21695
|
+
names.add(def.name.toLowerCase());
|
|
21696
|
+
for (const a of def.aliases ?? []) names.add(a.toLowerCase());
|
|
21697
|
+
}
|
|
21698
|
+
return names;
|
|
21699
|
+
}
|
|
21700
|
+
function createShield(def, opts = {}) {
|
|
21701
|
+
const name = def.name;
|
|
21702
|
+
if (builtinNames().has(name.toLowerCase())) {
|
|
21703
|
+
return {
|
|
21704
|
+
ok: false,
|
|
21705
|
+
error: `"${name}" is a built-in shield \u2014 choose a different name (a user shield with this name would shadow the built-in).`
|
|
21706
|
+
};
|
|
21707
|
+
}
|
|
21708
|
+
const filePath = import_path43.default.join(USER_SHIELDS_DIR_PATH, `${name}.json`);
|
|
21709
|
+
if (!opts.overwrite && import_fs45.default.existsSync(filePath)) {
|
|
21710
|
+
return {
|
|
21711
|
+
ok: false,
|
|
21712
|
+
error: `Shield "${name}" already exists at ${filePath}. Pass --overwrite to replace it.`
|
|
21713
|
+
};
|
|
21714
|
+
}
|
|
21715
|
+
if (def.smartRules.length === 0) {
|
|
21716
|
+
return {
|
|
21717
|
+
ok: false,
|
|
21718
|
+
error: "Shield has no rules \u2014 add at least one --block/--review tool or path."
|
|
21719
|
+
};
|
|
21720
|
+
}
|
|
21721
|
+
if (opts.viaMcp && def.smartRules.some((r) => r.verdict === "allow")) {
|
|
21722
|
+
return {
|
|
21723
|
+
ok: false,
|
|
21724
|
+
error: "allow-verdict rules are not permitted over MCP (they would weaken node9). Use the CLI."
|
|
21725
|
+
};
|
|
21726
|
+
}
|
|
21727
|
+
const v = validateShieldDefinition(def);
|
|
21728
|
+
if ("error" in v) {
|
|
21729
|
+
return { ok: false, error: v.error };
|
|
21730
|
+
}
|
|
21731
|
+
installShield(name, def);
|
|
21732
|
+
let enabled = false;
|
|
21733
|
+
if (opts.enable) {
|
|
21734
|
+
const active = readActiveShields();
|
|
21735
|
+
if (!active.includes(name)) writeActiveShields([...active, name]);
|
|
21736
|
+
enabled = true;
|
|
21737
|
+
}
|
|
21738
|
+
appendConfigAudit({
|
|
21739
|
+
event: "shield-create",
|
|
21740
|
+
shield: name,
|
|
21741
|
+
via: opts.viaMcp ? "mcp" : "cli",
|
|
21742
|
+
enabled
|
|
21743
|
+
});
|
|
21744
|
+
return { ok: true, path: filePath, enabled, ruleCount: def.smartRules.length };
|
|
21745
|
+
}
|
|
21746
|
+
|
|
21747
|
+
// src/cli/commands/shield.ts
|
|
21748
|
+
init_audit();
|
|
21598
21749
|
init_config();
|
|
21599
21750
|
|
|
21600
21751
|
// src/utils/https-fetch.ts
|
|
@@ -21898,6 +22049,73 @@ function registerShieldCommand(program2) {
|
|
|
21898
22049
|
process.exit(1);
|
|
21899
22050
|
});
|
|
21900
22051
|
});
|
|
22052
|
+
const collect = (val, prev) => [...prev, val];
|
|
22053
|
+
shieldCmd.command("create <name>").description("Author a new shield from block/review tools and paths").option("--desc <text>", "Shield description").option("--block-tool <tool>", "Block a tool entirely (repeatable)", collect, []).option("--review-tool <tool>", "Require approval for a tool (repeatable)", collect, []).option("--block-path <path>", "Block access to a path (repeatable)", collect, []).option("--review-path <path>", "Require approval to access a path (repeatable)", collect, []).option("--from-file <path>", "Build from a shield JSON file (overrides inline flags)").option("--enable", "Activate the shield immediately").option("--overwrite", "Replace an existing user shield of the same name").action(
|
|
22054
|
+
(name, opts) => {
|
|
22055
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
22056
|
+
console.error(
|
|
22057
|
+
import_chalk10.default.red(
|
|
22058
|
+
`
|
|
22059
|
+
\u274C Invalid shield name: only alphanumeric characters, hyphens, and underscores are allowed
|
|
22060
|
+
`
|
|
22061
|
+
)
|
|
22062
|
+
);
|
|
22063
|
+
process.exit(1);
|
|
22064
|
+
}
|
|
22065
|
+
let def;
|
|
22066
|
+
if (opts.fromFile) {
|
|
22067
|
+
let raw;
|
|
22068
|
+
try {
|
|
22069
|
+
raw = JSON.parse(import_fs46.default.readFileSync(opts.fromFile, "utf-8"));
|
|
22070
|
+
} catch (err2) {
|
|
22071
|
+
console.error(
|
|
22072
|
+
import_chalk10.default.red(`
|
|
22073
|
+
\u274C Could not read/parse ${opts.fromFile}: ${String(err2)}
|
|
22074
|
+
`)
|
|
22075
|
+
);
|
|
22076
|
+
process.exit(1);
|
|
22077
|
+
return;
|
|
22078
|
+
}
|
|
22079
|
+
def = { ...raw, name };
|
|
22080
|
+
} else {
|
|
22081
|
+
def = buildShield({
|
|
22082
|
+
name,
|
|
22083
|
+
description: opts.desc,
|
|
22084
|
+
blockTools: opts.blockTool,
|
|
22085
|
+
reviewTools: opts.reviewTool,
|
|
22086
|
+
blockPaths: opts.blockPath,
|
|
22087
|
+
reviewPaths: opts.reviewPath
|
|
22088
|
+
});
|
|
22089
|
+
}
|
|
22090
|
+
const res = createShield(def, {
|
|
22091
|
+
enable: opts.enable,
|
|
22092
|
+
overwrite: opts.overwrite,
|
|
22093
|
+
viaMcp: false
|
|
22094
|
+
});
|
|
22095
|
+
if (!res.ok) {
|
|
22096
|
+
console.error(import_chalk10.default.red(`
|
|
22097
|
+
\u274C ${res.error}
|
|
22098
|
+
`));
|
|
22099
|
+
process.exit(1);
|
|
22100
|
+
return;
|
|
22101
|
+
}
|
|
22102
|
+
console.log(
|
|
22103
|
+
import_chalk10.default.green(`
|
|
22104
|
+
\u2705 Shield "${name}" created`) + import_chalk10.default.gray(` \u2014 ${res.ruleCount} rule(s) \u2192 ${res.path}`)
|
|
22105
|
+
);
|
|
22106
|
+
if (res.enabled) {
|
|
22107
|
+
console.log(import_chalk10.default.gray(` Active now.`));
|
|
22108
|
+
} else {
|
|
22109
|
+
console.log(
|
|
22110
|
+
import_chalk10.default.gray(` Activate it with: ${import_chalk10.default.cyan(`node9 shield enable ${name}`)}`)
|
|
22111
|
+
);
|
|
22112
|
+
}
|
|
22113
|
+
console.log(
|
|
22114
|
+
import_chalk10.default.gray(` Preview a call: ${import_chalk10.default.cyan(`node9 explain bash "<command>"`)}
|
|
22115
|
+
`)
|
|
22116
|
+
);
|
|
22117
|
+
}
|
|
22118
|
+
);
|
|
21901
22119
|
}
|
|
21902
22120
|
function registerConfigShowCommand(program2) {
|
|
21903
22121
|
program2.command("config show").description(
|
|
@@ -21968,8 +22186,8 @@ function registerConfigShowCommand(program2) {
|
|
|
21968
22186
|
|
|
21969
22187
|
// src/cli/commands/doctor.ts
|
|
21970
22188
|
var import_chalk11 = __toESM(require("chalk"));
|
|
21971
|
-
var
|
|
21972
|
-
var
|
|
22189
|
+
var import_fs47 = __toESM(require("fs"));
|
|
22190
|
+
var import_path44 = __toESM(require("path"));
|
|
21973
22191
|
var import_os40 = __toESM(require("os"));
|
|
21974
22192
|
var import_child_process8 = require("child_process");
|
|
21975
22193
|
init_daemon();
|
|
@@ -22023,10 +22241,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
22023
22241
|
);
|
|
22024
22242
|
}
|
|
22025
22243
|
section("Configuration");
|
|
22026
|
-
const globalConfigPath =
|
|
22027
|
-
if (
|
|
22244
|
+
const globalConfigPath = import_path44.default.join(homeDir2, ".node9", "config.json");
|
|
22245
|
+
if (import_fs47.default.existsSync(globalConfigPath)) {
|
|
22028
22246
|
try {
|
|
22029
|
-
JSON.parse(
|
|
22247
|
+
JSON.parse(import_fs47.default.readFileSync(globalConfigPath, "utf-8"));
|
|
22030
22248
|
pass("~/.node9/config.json found and valid");
|
|
22031
22249
|
} catch {
|
|
22032
22250
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -22034,10 +22252,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
22034
22252
|
} else {
|
|
22035
22253
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
22036
22254
|
}
|
|
22037
|
-
const projectConfigPath =
|
|
22038
|
-
if (
|
|
22255
|
+
const projectConfigPath = import_path44.default.join(process.cwd(), "node9.config.json");
|
|
22256
|
+
if (import_fs47.default.existsSync(projectConfigPath)) {
|
|
22039
22257
|
try {
|
|
22040
|
-
JSON.parse(
|
|
22258
|
+
JSON.parse(import_fs47.default.readFileSync(projectConfigPath, "utf-8"));
|
|
22041
22259
|
pass("node9.config.json found and valid (project)");
|
|
22042
22260
|
} catch {
|
|
22043
22261
|
fail(
|
|
@@ -22046,8 +22264,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
22046
22264
|
);
|
|
22047
22265
|
}
|
|
22048
22266
|
}
|
|
22049
|
-
const credsPath =
|
|
22050
|
-
if (
|
|
22267
|
+
const credsPath = import_path44.default.join(homeDir2, ".node9", "credentials.json");
|
|
22268
|
+
if (import_fs47.default.existsSync(credsPath)) {
|
|
22051
22269
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
22052
22270
|
} else {
|
|
22053
22271
|
warn(
|
|
@@ -22091,7 +22309,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
22091
22309
|
try {
|
|
22092
22310
|
const { shipLagBytes: shipLagBytes2, readWatermark: readWatermark2, AUDIT_SHIP_WATERMARK: AUDIT_SHIP_WATERMARK2 } = await Promise.resolve().then(() => (init_audit_shipper(), audit_shipper_exports));
|
|
22093
22311
|
const cfg = getConfig();
|
|
22094
|
-
const creds =
|
|
22312
|
+
const creds = import_fs47.default.existsSync(import_path44.default.join(import_os40.default.homedir(), ".node9", "credentials.json"));
|
|
22095
22313
|
if (!creds) {
|
|
22096
22314
|
warn("Not logged in \u2014 audit rows stay local", "Run: node9 login <api-key>");
|
|
22097
22315
|
} else if (!cfg.settings.approvers.cloud) {
|
|
@@ -22141,8 +22359,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
22141
22359
|
|
|
22142
22360
|
// src/cli/commands/audit.ts
|
|
22143
22361
|
var import_chalk12 = __toESM(require("chalk"));
|
|
22144
|
-
var
|
|
22145
|
-
var
|
|
22362
|
+
var import_fs48 = __toESM(require("fs"));
|
|
22363
|
+
var import_path45 = __toESM(require("path"));
|
|
22146
22364
|
var import_os41 = __toESM(require("os"));
|
|
22147
22365
|
function formatRelativeTime(timestamp) {
|
|
22148
22366
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
@@ -22156,14 +22374,14 @@ function formatRelativeTime(timestamp) {
|
|
|
22156
22374
|
}
|
|
22157
22375
|
function registerAuditCommand(program2) {
|
|
22158
22376
|
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) => {
|
|
22159
|
-
const logPath =
|
|
22160
|
-
if (!
|
|
22377
|
+
const logPath = import_path45.default.join(import_os41.default.homedir(), ".node9", "audit.log");
|
|
22378
|
+
if (!import_fs48.default.existsSync(logPath)) {
|
|
22161
22379
|
console.log(
|
|
22162
22380
|
import_chalk12.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
22163
22381
|
);
|
|
22164
22382
|
return;
|
|
22165
22383
|
}
|
|
22166
|
-
const raw =
|
|
22384
|
+
const raw = import_fs48.default.readFileSync(logPath, "utf-8");
|
|
22167
22385
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
22168
22386
|
let entries = lines.flatMap((line) => {
|
|
22169
22387
|
try {
|
|
@@ -22219,9 +22437,9 @@ function registerAuditCommand(program2) {
|
|
|
22219
22437
|
var import_chalk13 = __toESM(require("chalk"));
|
|
22220
22438
|
|
|
22221
22439
|
// src/cli/aggregate/report-audit.ts
|
|
22222
|
-
var
|
|
22440
|
+
var import_fs49 = __toESM(require("fs"));
|
|
22223
22441
|
var import_os42 = __toESM(require("os"));
|
|
22224
|
-
var
|
|
22442
|
+
var import_path46 = __toESM(require("path"));
|
|
22225
22443
|
init_costSync();
|
|
22226
22444
|
init_litellm();
|
|
22227
22445
|
init_cost_codex();
|
|
@@ -22304,8 +22522,8 @@ function getDateRange(period, now) {
|
|
|
22304
22522
|
}
|
|
22305
22523
|
}
|
|
22306
22524
|
function parseAuditLog(logPath) {
|
|
22307
|
-
if (!
|
|
22308
|
-
const raw =
|
|
22525
|
+
if (!import_fs49.default.existsSync(logPath)) return [];
|
|
22526
|
+
const raw = import_fs49.default.readFileSync(logPath, "utf-8");
|
|
22309
22527
|
return raw.split("\n").flatMap((line) => {
|
|
22310
22528
|
if (!line.trim()) return [];
|
|
22311
22529
|
try {
|
|
@@ -22352,25 +22570,25 @@ function freezeClaudeCost(acc) {
|
|
|
22352
22570
|
};
|
|
22353
22571
|
}
|
|
22354
22572
|
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
22355
|
-
const projPath =
|
|
22573
|
+
const projPath = import_path46.default.join(projectsDir, proj);
|
|
22356
22574
|
let files;
|
|
22357
22575
|
try {
|
|
22358
|
-
const stat =
|
|
22576
|
+
const stat = import_fs49.default.statSync(projPath);
|
|
22359
22577
|
if (!stat.isDirectory()) return;
|
|
22360
|
-
files =
|
|
22578
|
+
files = import_fs49.default.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
22361
22579
|
} catch {
|
|
22362
22580
|
return;
|
|
22363
22581
|
}
|
|
22364
22582
|
const startMs = start.getTime();
|
|
22365
22583
|
for (const file of files) {
|
|
22366
|
-
const filePath =
|
|
22584
|
+
const filePath = import_path46.default.join(projPath, file);
|
|
22367
22585
|
try {
|
|
22368
|
-
if (
|
|
22586
|
+
if (import_fs49.default.statSync(filePath).mtimeMs < startMs) continue;
|
|
22369
22587
|
} catch {
|
|
22370
22588
|
continue;
|
|
22371
22589
|
}
|
|
22372
22590
|
try {
|
|
22373
|
-
const raw =
|
|
22591
|
+
const raw = import_fs49.default.readFileSync(filePath, "utf-8");
|
|
22374
22592
|
for (const line of raw.split("\n")) {
|
|
22375
22593
|
if (!line.trim()) continue;
|
|
22376
22594
|
let entry;
|
|
@@ -22420,10 +22638,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
22420
22638
|
}
|
|
22421
22639
|
function loadClaudeCost(start, end, projectsDir) {
|
|
22422
22640
|
const acc = emptyClaudeCostAccumulator();
|
|
22423
|
-
if (!
|
|
22641
|
+
if (!import_fs49.default.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
22424
22642
|
let dirs;
|
|
22425
22643
|
try {
|
|
22426
|
-
dirs =
|
|
22644
|
+
dirs = import_fs49.default.readdirSync(projectsDir);
|
|
22427
22645
|
} catch {
|
|
22428
22646
|
return freezeClaudeCost(acc);
|
|
22429
22647
|
}
|
|
@@ -22435,7 +22653,7 @@ function loadClaudeCost(start, end, projectsDir) {
|
|
|
22435
22653
|
function processCodexCostFile(filePath, start, end, acc) {
|
|
22436
22654
|
let lines;
|
|
22437
22655
|
try {
|
|
22438
|
-
lines =
|
|
22656
|
+
lines = import_fs49.default.readFileSync(filePath, "utf-8").split("\n");
|
|
22439
22657
|
} catch {
|
|
22440
22658
|
return;
|
|
22441
22659
|
}
|
|
@@ -22490,31 +22708,31 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
22490
22708
|
}
|
|
22491
22709
|
function listCodexSessionFiles2(sessionsBase) {
|
|
22492
22710
|
const jsonlFiles = [];
|
|
22493
|
-
if (!
|
|
22711
|
+
if (!import_fs49.default.existsSync(sessionsBase)) return jsonlFiles;
|
|
22494
22712
|
try {
|
|
22495
|
-
for (const year of
|
|
22496
|
-
const yearPath =
|
|
22713
|
+
for (const year of import_fs49.default.readdirSync(sessionsBase)) {
|
|
22714
|
+
const yearPath = import_path46.default.join(sessionsBase, year);
|
|
22497
22715
|
try {
|
|
22498
|
-
if (!
|
|
22716
|
+
if (!import_fs49.default.statSync(yearPath).isDirectory()) continue;
|
|
22499
22717
|
} catch {
|
|
22500
22718
|
continue;
|
|
22501
22719
|
}
|
|
22502
|
-
for (const month of
|
|
22503
|
-
const monthPath =
|
|
22720
|
+
for (const month of import_fs49.default.readdirSync(yearPath)) {
|
|
22721
|
+
const monthPath = import_path46.default.join(yearPath, month);
|
|
22504
22722
|
try {
|
|
22505
|
-
if (!
|
|
22723
|
+
if (!import_fs49.default.statSync(monthPath).isDirectory()) continue;
|
|
22506
22724
|
} catch {
|
|
22507
22725
|
continue;
|
|
22508
22726
|
}
|
|
22509
|
-
for (const day of
|
|
22510
|
-
const dayPath =
|
|
22727
|
+
for (const day of import_fs49.default.readdirSync(monthPath)) {
|
|
22728
|
+
const dayPath = import_path46.default.join(monthPath, day);
|
|
22511
22729
|
try {
|
|
22512
|
-
if (!
|
|
22730
|
+
if (!import_fs49.default.statSync(dayPath).isDirectory()) continue;
|
|
22513
22731
|
} catch {
|
|
22514
22732
|
continue;
|
|
22515
22733
|
}
|
|
22516
|
-
for (const file of
|
|
22517
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
22734
|
+
for (const file of import_fs49.default.readdirSync(dayPath)) {
|
|
22735
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path46.default.join(dayPath, file));
|
|
22518
22736
|
}
|
|
22519
22737
|
}
|
|
22520
22738
|
}
|
|
@@ -22579,13 +22797,13 @@ function freezeGeminiCost(acc) {
|
|
|
22579
22797
|
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
22580
22798
|
const startMs = start.getTime();
|
|
22581
22799
|
try {
|
|
22582
|
-
if (
|
|
22800
|
+
if (import_fs49.default.statSync(filePath).mtimeMs < startMs) return;
|
|
22583
22801
|
} catch {
|
|
22584
22802
|
return;
|
|
22585
22803
|
}
|
|
22586
22804
|
let raw;
|
|
22587
22805
|
try {
|
|
22588
|
-
raw =
|
|
22806
|
+
raw = import_fs49.default.readFileSync(filePath, "utf-8");
|
|
22589
22807
|
} catch {
|
|
22590
22808
|
return;
|
|
22591
22809
|
}
|
|
@@ -22634,30 +22852,30 @@ function listGeminiSessionFiles2(geminiTmpDir2) {
|
|
|
22634
22852
|
const out = [];
|
|
22635
22853
|
let dirs;
|
|
22636
22854
|
try {
|
|
22637
|
-
if (!
|
|
22638
|
-
dirs =
|
|
22855
|
+
if (!import_fs49.default.statSync(geminiTmpDir2).isDirectory()) return out;
|
|
22856
|
+
dirs = import_fs49.default.readdirSync(geminiTmpDir2);
|
|
22639
22857
|
} catch {
|
|
22640
22858
|
return out;
|
|
22641
22859
|
}
|
|
22642
22860
|
for (const proj of dirs) {
|
|
22643
|
-
const chatsDir =
|
|
22861
|
+
const chatsDir = import_path46.default.join(geminiTmpDir2, proj, "chats");
|
|
22644
22862
|
let files;
|
|
22645
22863
|
try {
|
|
22646
|
-
if (!
|
|
22647
|
-
files =
|
|
22864
|
+
if (!import_fs49.default.statSync(chatsDir).isDirectory()) continue;
|
|
22865
|
+
files = import_fs49.default.readdirSync(chatsDir);
|
|
22648
22866
|
} catch {
|
|
22649
22867
|
continue;
|
|
22650
22868
|
}
|
|
22651
22869
|
for (const f of files) {
|
|
22652
22870
|
if (!f.endsWith(".jsonl")) continue;
|
|
22653
|
-
out.push({ projectKey: proj, file:
|
|
22871
|
+
out.push({ projectKey: proj, file: import_path46.default.join(chatsDir, f) });
|
|
22654
22872
|
}
|
|
22655
22873
|
}
|
|
22656
22874
|
return out;
|
|
22657
22875
|
}
|
|
22658
22876
|
function loadGeminiCost(start, end, geminiTmpDir2) {
|
|
22659
22877
|
const acc = emptyGeminiAccumulator();
|
|
22660
|
-
if (!
|
|
22878
|
+
if (!import_fs49.default.existsSync(geminiTmpDir2)) return freezeGeminiCost(acc);
|
|
22661
22879
|
for (const { projectKey, file } of listGeminiSessionFiles2(geminiTmpDir2)) {
|
|
22662
22880
|
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
22663
22881
|
}
|
|
@@ -22665,11 +22883,11 @@ function loadGeminiCost(start, end, geminiTmpDir2) {
|
|
|
22665
22883
|
}
|
|
22666
22884
|
function aggregateReportFromAudit(period, opts = {}) {
|
|
22667
22885
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
22668
|
-
const auditLogPath = opts.auditLogPath ??
|
|
22669
|
-
const claudeProjectsDir = opts.claudeProjectsDir ??
|
|
22670
|
-
const codexSessionsDir2 = opts.codexSessionsDir ??
|
|
22671
|
-
const geminiTmpDir2 = opts.geminiTmpDir ??
|
|
22672
|
-
const hasAuditFile =
|
|
22886
|
+
const auditLogPath = opts.auditLogPath ?? import_path46.default.join(import_os42.default.homedir(), ".node9", "audit.log");
|
|
22887
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? import_path46.default.join(import_os42.default.homedir(), ".claude", "projects");
|
|
22888
|
+
const codexSessionsDir2 = opts.codexSessionsDir ?? import_path46.default.join(import_os42.default.homedir(), ".codex", "sessions");
|
|
22889
|
+
const geminiTmpDir2 = opts.geminiTmpDir ?? import_path46.default.join(import_os42.default.homedir(), ".gemini", "tmp");
|
|
22890
|
+
const hasAuditFile = import_fs49.default.existsSync(auditLogPath);
|
|
22673
22891
|
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath);
|
|
22674
22892
|
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
22675
22893
|
const { start, end } = getDateRange(period, now);
|
|
@@ -23365,8 +23583,8 @@ function registerDaemonCommand(program2) {
|
|
|
23365
23583
|
|
|
23366
23584
|
// src/cli/commands/status.ts
|
|
23367
23585
|
var import_chalk15 = __toESM(require("chalk"));
|
|
23368
|
-
var
|
|
23369
|
-
var
|
|
23586
|
+
var import_fs50 = __toESM(require("fs"));
|
|
23587
|
+
var import_path47 = __toESM(require("path"));
|
|
23370
23588
|
var import_os43 = __toESM(require("os"));
|
|
23371
23589
|
init_core();
|
|
23372
23590
|
init_daemon();
|
|
@@ -23424,13 +23642,13 @@ function registerStatusCommand(program2) {
|
|
|
23424
23642
|
console.log("");
|
|
23425
23643
|
const modeLabel = settings.mode === "audit" ? import_chalk15.default.blue("audit") : settings.mode === "strict" ? import_chalk15.default.red("strict") : import_chalk15.default.white("standard");
|
|
23426
23644
|
console.log(` Mode: ${modeLabel}`);
|
|
23427
|
-
const projectConfig =
|
|
23428
|
-
const globalConfig =
|
|
23645
|
+
const projectConfig = import_path47.default.join(process.cwd(), "node9.config.json");
|
|
23646
|
+
const globalConfig = import_path47.default.join(import_os43.default.homedir(), ".node9", "config.json");
|
|
23429
23647
|
console.log(
|
|
23430
|
-
` Local: ${
|
|
23648
|
+
` Local: ${import_fs50.default.existsSync(projectConfig) ? import_chalk15.default.green("Active (node9.config.json)") : import_chalk15.default.gray("Not present")}`
|
|
23431
23649
|
);
|
|
23432
23650
|
console.log(
|
|
23433
|
-
` Global: ${
|
|
23651
|
+
` Global: ${import_fs50.default.existsSync(globalConfig) ? import_chalk15.default.green("Active (~/.node9/config.json)") : import_chalk15.default.gray("Not present")}`
|
|
23434
23652
|
);
|
|
23435
23653
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
23436
23654
|
console.log(
|
|
@@ -23472,8 +23690,8 @@ function registerStatusCommand(program2) {
|
|
|
23472
23690
|
|
|
23473
23691
|
// src/cli/commands/init.ts
|
|
23474
23692
|
var import_chalk16 = __toESM(require("chalk"));
|
|
23475
|
-
var
|
|
23476
|
-
var
|
|
23693
|
+
var import_fs51 = __toESM(require("fs"));
|
|
23694
|
+
var import_path48 = __toESM(require("path"));
|
|
23477
23695
|
var import_os44 = __toESM(require("os"));
|
|
23478
23696
|
var import_https5 = __toESM(require("https"));
|
|
23479
23697
|
init_core();
|
|
@@ -23564,32 +23782,32 @@ function registerInitCommand(program2) {
|
|
|
23564
23782
|
}
|
|
23565
23783
|
console.log("");
|
|
23566
23784
|
}
|
|
23567
|
-
const
|
|
23568
|
-
const isFirstInstall = !
|
|
23569
|
-
if (
|
|
23785
|
+
const configPath = import_path48.default.join(import_os44.default.homedir(), ".node9", "config.json");
|
|
23786
|
+
const isFirstInstall = !import_fs51.default.existsSync(configPath);
|
|
23787
|
+
if (import_fs51.default.existsSync(configPath) && !options.force) {
|
|
23570
23788
|
try {
|
|
23571
|
-
const existing = JSON.parse(
|
|
23789
|
+
const existing = JSON.parse(import_fs51.default.readFileSync(configPath, "utf-8"));
|
|
23572
23790
|
const settings = existing.settings ?? {};
|
|
23573
23791
|
if (settings.mode !== chosenMode) {
|
|
23574
23792
|
settings.mode = chosenMode;
|
|
23575
23793
|
existing.settings = settings;
|
|
23576
|
-
|
|
23794
|
+
import_fs51.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
23577
23795
|
console.log(import_chalk16.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
23578
23796
|
} else {
|
|
23579
|
-
console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${
|
|
23797
|
+
console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
23580
23798
|
}
|
|
23581
23799
|
} catch {
|
|
23582
|
-
console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${
|
|
23800
|
+
console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
23583
23801
|
}
|
|
23584
23802
|
} else {
|
|
23585
23803
|
const configToSave = {
|
|
23586
23804
|
...DEFAULT_CONFIG,
|
|
23587
23805
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
23588
23806
|
};
|
|
23589
|
-
const dir =
|
|
23590
|
-
if (!
|
|
23591
|
-
|
|
23592
|
-
console.log(import_chalk16.default.green(`\u2705 Config created: ${
|
|
23807
|
+
const dir = import_path48.default.dirname(configPath);
|
|
23808
|
+
if (!import_fs51.default.existsSync(dir)) import_fs51.default.mkdirSync(dir, { recursive: true });
|
|
23809
|
+
import_fs51.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
23810
|
+
console.log(import_chalk16.default.green(`\u2705 Config created: ${configPath}`));
|
|
23593
23811
|
console.log(import_chalk16.default.gray(` Mode: ${chosenMode}`));
|
|
23594
23812
|
}
|
|
23595
23813
|
if (options.skipSetup) return;
|
|
@@ -23693,7 +23911,7 @@ function registerInitCommand(program2) {
|
|
|
23693
23911
|
}
|
|
23694
23912
|
|
|
23695
23913
|
// src/cli/commands/undo.ts
|
|
23696
|
-
var
|
|
23914
|
+
var import_path49 = __toESM(require("path"));
|
|
23697
23915
|
var import_chalk18 = __toESM(require("chalk"));
|
|
23698
23916
|
|
|
23699
23917
|
// src/tui/undo-navigator.ts
|
|
@@ -23852,7 +24070,7 @@ function findMatchingCwd(startDir, history) {
|
|
|
23852
24070
|
let dir = startDir;
|
|
23853
24071
|
while (true) {
|
|
23854
24072
|
if (cwds.has(dir)) return dir;
|
|
23855
|
-
const parent =
|
|
24073
|
+
const parent = import_path49.default.dirname(dir);
|
|
23856
24074
|
if (parent === dir) return null;
|
|
23857
24075
|
dir = parent;
|
|
23858
24076
|
}
|
|
@@ -24487,13 +24705,83 @@ function registerMcpGatewayCommand(program2) {
|
|
|
24487
24705
|
|
|
24488
24706
|
// src/mcp-server/index.ts
|
|
24489
24707
|
var import_readline5 = __toESM(require("readline"));
|
|
24490
|
-
var
|
|
24491
|
-
var
|
|
24492
|
-
var
|
|
24708
|
+
var import_fs53 = __toESM(require("fs"));
|
|
24709
|
+
var import_os46 = __toESM(require("os"));
|
|
24710
|
+
var import_path51 = __toESM(require("path"));
|
|
24493
24711
|
var import_child_process11 = require("child_process");
|
|
24494
24712
|
init_core();
|
|
24495
24713
|
init_daemon();
|
|
24496
24714
|
init_shields();
|
|
24715
|
+
|
|
24716
|
+
// src/auth/egress-config.ts
|
|
24717
|
+
var import_fs52 = __toESM(require("fs"));
|
|
24718
|
+
var import_os45 = __toESM(require("os"));
|
|
24719
|
+
var import_path50 = __toESM(require("path"));
|
|
24720
|
+
var DEFAULT_EGRESS = {
|
|
24721
|
+
enabled: false,
|
|
24722
|
+
mode: "review",
|
|
24723
|
+
allow: [],
|
|
24724
|
+
deny: [],
|
|
24725
|
+
allowPrivate: true
|
|
24726
|
+
};
|
|
24727
|
+
function egressConfigPath() {
|
|
24728
|
+
return import_path50.default.join(import_os45.default.homedir(), ".node9", "config.json");
|
|
24729
|
+
}
|
|
24730
|
+
function readEgressRawConfig() {
|
|
24731
|
+
let text;
|
|
24732
|
+
try {
|
|
24733
|
+
text = import_fs52.default.readFileSync(egressConfigPath(), "utf8");
|
|
24734
|
+
} catch (err2) {
|
|
24735
|
+
if (err2.code === "ENOENT") return {};
|
|
24736
|
+
throw err2;
|
|
24737
|
+
}
|
|
24738
|
+
try {
|
|
24739
|
+
return JSON.parse(text);
|
|
24740
|
+
} catch {
|
|
24741
|
+
throw new Error(
|
|
24742
|
+
`${egressConfigPath()} is not valid JSON \u2014 fix it before changing egress (refusing to overwrite).`
|
|
24743
|
+
);
|
|
24744
|
+
}
|
|
24745
|
+
}
|
|
24746
|
+
function writeEgressRawConfig(config) {
|
|
24747
|
+
const p = egressConfigPath();
|
|
24748
|
+
import_fs52.default.mkdirSync(import_path50.default.dirname(p), { recursive: true });
|
|
24749
|
+
import_fs52.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
24750
|
+
}
|
|
24751
|
+
function applyEgress(config, change) {
|
|
24752
|
+
const policy = config.policy = config.policy ?? {};
|
|
24753
|
+
const existing = policy.egress ?? {};
|
|
24754
|
+
policy.egress = { ...DEFAULT_EGRESS, ...existing, ...change };
|
|
24755
|
+
return config;
|
|
24756
|
+
}
|
|
24757
|
+
function getEgress() {
|
|
24758
|
+
const raw = readEgressRawConfig();
|
|
24759
|
+
const existing = raw.policy?.egress ?? {};
|
|
24760
|
+
return { ...DEFAULT_EGRESS, ...existing };
|
|
24761
|
+
}
|
|
24762
|
+
function setEgress(change) {
|
|
24763
|
+
const config = readEgressRawConfig();
|
|
24764
|
+
applyEgress(config, change);
|
|
24765
|
+
writeEgressRawConfig(config);
|
|
24766
|
+
}
|
|
24767
|
+
function addEgressHost(list, host) {
|
|
24768
|
+
const config = readEgressRawConfig();
|
|
24769
|
+
const existing = config.policy?.egress ?? {};
|
|
24770
|
+
const current = { ...DEFAULT_EGRESS, ...existing };
|
|
24771
|
+
const updated = current[list].includes(host) ? current[list] : [...current[list], host];
|
|
24772
|
+
applyEgress(config, { [list]: updated });
|
|
24773
|
+
writeEgressRawConfig(config);
|
|
24774
|
+
}
|
|
24775
|
+
function normalizeEgressHost(host) {
|
|
24776
|
+
return host.trim().toLowerCase();
|
|
24777
|
+
}
|
|
24778
|
+
var EGRESS_HOST_RE = /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/;
|
|
24779
|
+
function isValidEgressHost(host) {
|
|
24780
|
+
return EGRESS_HOST_RE.test(host);
|
|
24781
|
+
}
|
|
24782
|
+
|
|
24783
|
+
// src/mcp-server/index.ts
|
|
24784
|
+
init_dist();
|
|
24497
24785
|
function ok(id, result) {
|
|
24498
24786
|
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, result });
|
|
24499
24787
|
}
|
|
@@ -24504,12 +24792,20 @@ var TOOL_CAPABILITY = {
|
|
|
24504
24792
|
// weaken — gated over MCP
|
|
24505
24793
|
node9_shield_disable: "weaken",
|
|
24506
24794
|
node9_approver_set: "weaken",
|
|
24795
|
+
// NOTE: egress LOOSENING (allow a host / turn egress off) is intentionally NOT
|
|
24796
|
+
// exposed over MCP — it has no legitimate agent use case (it's exactly the
|
|
24797
|
+
// exfil-exit the egress gate exists to prevent) and would be attack surface
|
|
24798
|
+
// even gated. A human loosens egress at the CLI: `node9 egress allow|off`.
|
|
24507
24799
|
// add / restorative — always allowed
|
|
24508
24800
|
node9_shield_enable: "add",
|
|
24509
24801
|
node9_rule_add: "add",
|
|
24510
24802
|
// already block/review-only — handleRuleAdd rejects "allow"
|
|
24511
24803
|
node9_undo_revert: "add",
|
|
24512
24804
|
// restorative
|
|
24805
|
+
node9_egress_protect: "add",
|
|
24806
|
+
// enable/strengthen egress (monotonic — never reduces)
|
|
24807
|
+
node9_egress_deny: "add",
|
|
24808
|
+
// add a deny host (deny always wins)
|
|
24513
24809
|
// readonly
|
|
24514
24810
|
node9_status: "readonly",
|
|
24515
24811
|
node9_config_get: "readonly",
|
|
@@ -24523,7 +24819,8 @@ var TOOL_CAPABILITY = {
|
|
|
24523
24819
|
node9_shield_list: "readonly",
|
|
24524
24820
|
node9_approver_list: "readonly",
|
|
24525
24821
|
node9_undo_list: "readonly",
|
|
24526
|
-
node9_undo_detail: "readonly"
|
|
24822
|
+
node9_undo_detail: "readonly",
|
|
24823
|
+
node9_egress_status: "readonly"
|
|
24527
24824
|
};
|
|
24528
24825
|
function capabilityOf(tool) {
|
|
24529
24826
|
return TOOL_CAPABILITY[tool] ?? "readonly";
|
|
@@ -24779,7 +25076,41 @@ var TOOLS = [
|
|
|
24779
25076
|
},
|
|
24780
25077
|
required: ["name", "tool", "field", "pattern", "verdict", "reason"]
|
|
24781
25078
|
}
|
|
25079
|
+
},
|
|
25080
|
+
{
|
|
25081
|
+
name: "node9_egress_status",
|
|
25082
|
+
description: "Show egress (outbound network) control: whether it is enabled, the mode (off / review / block), and your allow + deny host lists. Common dev/LLM hosts (github, npm, pypi, anthropic, \u2026) are always allowed by a built-in list. Read-only.",
|
|
25083
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
25084
|
+
},
|
|
25085
|
+
{
|
|
25086
|
+
name: "node9_egress_protect",
|
|
25087
|
+
description: 'Turn on egress control (or strengthen it). mode="review" prompts on unknown hosts; mode="block" hard-blocks them. Monotonic \u2014 it only ever ADDS protection and never reduces it (a request for review will not downgrade an existing block). Only adds protection, so it is always allowed over MCP.',
|
|
25088
|
+
inputSchema: {
|
|
25089
|
+
type: "object",
|
|
25090
|
+
properties: {
|
|
25091
|
+
mode: {
|
|
25092
|
+
type: "string",
|
|
25093
|
+
enum: ["review", "block"],
|
|
25094
|
+
description: 'Enforcement to apply. "review" = prompt, "block" = deny. Defaults to review.'
|
|
25095
|
+
}
|
|
25096
|
+
},
|
|
25097
|
+
required: []
|
|
25098
|
+
}
|
|
25099
|
+
},
|
|
25100
|
+
{
|
|
25101
|
+
name: "node9_egress_deny",
|
|
25102
|
+
description: "Add a host to the egress deny list (deny always wins over allow). Only adds protection, so it is always allowed over MCP. Host is an FQDN or wildcard glob (e.g. evil.com or *.evil.com).",
|
|
25103
|
+
inputSchema: {
|
|
25104
|
+
type: "object",
|
|
25105
|
+
properties: {
|
|
25106
|
+
host: { type: "string", description: "Host to deny (FQDN or *.glob)." }
|
|
25107
|
+
},
|
|
25108
|
+
required: ["host"]
|
|
25109
|
+
}
|
|
24782
25110
|
}
|
|
25111
|
+
// Egress LOOSENING (allow a host / turn egress off) is deliberately CLI-only —
|
|
25112
|
+
// see the note in TOOL_CAPABILITY. The agent can see and tighten egress over
|
|
25113
|
+
// MCP, but never loosen it.
|
|
24783
25114
|
];
|
|
24784
25115
|
function handleStatus() {
|
|
24785
25116
|
const config = getConfig();
|
|
@@ -24800,13 +25131,13 @@ function handleStatus() {
|
|
|
24800
25131
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
24801
25132
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
24802
25133
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
24803
|
-
const projectConfig =
|
|
24804
|
-
const globalConfig =
|
|
25134
|
+
const projectConfig = import_path51.default.join(process.cwd(), "node9.config.json");
|
|
25135
|
+
const globalConfig = import_path51.default.join(import_os46.default.homedir(), ".node9", "config.json");
|
|
24805
25136
|
lines.push(
|
|
24806
|
-
`Project config (node9.config.json): ${
|
|
25137
|
+
`Project config (node9.config.json): ${import_fs53.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
24807
25138
|
);
|
|
24808
25139
|
lines.push(
|
|
24809
|
-
`Global config (~/.node9/config.json): ${
|
|
25140
|
+
`Global config (~/.node9/config.json): ${import_fs53.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
24810
25141
|
);
|
|
24811
25142
|
return lines.join("\n");
|
|
24812
25143
|
}
|
|
@@ -24880,21 +25211,53 @@ function handleShieldDisable(args) {
|
|
|
24880
25211
|
writeActiveShields(active.filter((s) => s !== name));
|
|
24881
25212
|
return `Shield "${name}" disabled.`;
|
|
24882
25213
|
}
|
|
24883
|
-
|
|
25214
|
+
function handleEgressStatus() {
|
|
25215
|
+
const e = getConfig().policy.egress;
|
|
25216
|
+
const state = !e.enabled ? "OFF \u2014 the agent can reach any host" : e.mode === "block" ? "LOCKED (block) \u2014 unknown hosts are denied" : "WATCHING (review) \u2014 unknown hosts prompt for approval";
|
|
25217
|
+
const lines = [
|
|
25218
|
+
`Egress control: ${state}`,
|
|
25219
|
+
`${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`,
|
|
25220
|
+
`Your allow list: ${e.allow.length ? e.allow.join(", ") : "(none)"}`,
|
|
25221
|
+
`Your deny list: ${e.deny.length ? e.deny.join(", ") : "(none)"}`
|
|
25222
|
+
];
|
|
25223
|
+
return lines.join("\n");
|
|
25224
|
+
}
|
|
25225
|
+
function handleEgressProtect(args) {
|
|
25226
|
+
const rawMode = args.mode;
|
|
25227
|
+
if (rawMode !== void 0 && rawMode !== "review" && rawMode !== "block") {
|
|
25228
|
+
throw new Error('mode must be "review" or "block".');
|
|
25229
|
+
}
|
|
25230
|
+
const requested = rawMode ?? "review";
|
|
25231
|
+
const current = getEgress();
|
|
25232
|
+
const mode = current.enabled && current.mode === "block" ? "block" : requested;
|
|
25233
|
+
setEgress({ enabled: true, mode });
|
|
25234
|
+
return mode === "block" ? "Egress is now LOCKED (block) \u2014 unknown hosts are denied. Routine hosts stay allowed." : "Egress is now WATCHED (review) \u2014 unknown hosts prompt for approval.";
|
|
25235
|
+
}
|
|
25236
|
+
function handleEgressDeny(args) {
|
|
25237
|
+
const host = typeof args.host === "string" ? normalizeEgressHost(args.host) : "";
|
|
25238
|
+
if (!isValidEgressHost(host)) {
|
|
25239
|
+
throw new Error(
|
|
25240
|
+
`Invalid host: "${String(args.host)}". Use an FQDN or *.glob (e.g. *.evil.com).`
|
|
25241
|
+
);
|
|
25242
|
+
}
|
|
25243
|
+
addEgressHost("deny", host);
|
|
25244
|
+
return `Denied egress to ${host} (deny always wins over allow).`;
|
|
25245
|
+
}
|
|
25246
|
+
var GLOBAL_CONFIG_PATH = import_path51.default.join(import_os46.default.homedir(), ".node9", "config.json");
|
|
24884
25247
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
24885
25248
|
function readGlobalConfigRaw() {
|
|
24886
25249
|
try {
|
|
24887
|
-
if (
|
|
24888
|
-
return JSON.parse(
|
|
25250
|
+
if (import_fs53.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
25251
|
+
return JSON.parse(import_fs53.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
24889
25252
|
}
|
|
24890
25253
|
} catch {
|
|
24891
25254
|
}
|
|
24892
25255
|
return {};
|
|
24893
25256
|
}
|
|
24894
25257
|
function writeGlobalConfigRaw(data) {
|
|
24895
|
-
const dir =
|
|
24896
|
-
if (!
|
|
24897
|
-
|
|
25258
|
+
const dir = import_path51.default.dirname(GLOBAL_CONFIG_PATH);
|
|
25259
|
+
if (!import_fs53.default.existsSync(dir)) import_fs53.default.mkdirSync(dir, { recursive: true });
|
|
25260
|
+
import_fs53.default.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
24898
25261
|
}
|
|
24899
25262
|
function handleApproverList() {
|
|
24900
25263
|
const config = getConfig();
|
|
@@ -24938,9 +25301,9 @@ function handleApproverSet(args) {
|
|
|
24938
25301
|
function handleAuditGet(args) {
|
|
24939
25302
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
24940
25303
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
24941
|
-
const auditPath =
|
|
24942
|
-
if (!
|
|
24943
|
-
const rawLines =
|
|
25304
|
+
const auditPath = import_path51.default.join(import_os46.default.homedir(), ".node9", "audit.log");
|
|
25305
|
+
if (!import_fs53.default.existsSync(auditPath)) return "No audit log found.";
|
|
25306
|
+
const rawLines = import_fs53.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
24944
25307
|
const parsed = [];
|
|
24945
25308
|
for (const line of rawLines) {
|
|
24946
25309
|
try {
|
|
@@ -25218,6 +25581,12 @@ function runMcpServer() {
|
|
|
25218
25581
|
text = handlePostureMcp(toolArgs);
|
|
25219
25582
|
} else if (toolName === "node9_explain") {
|
|
25220
25583
|
text = handleExplainMcp(toolArgs);
|
|
25584
|
+
} else if (toolName === "node9_egress_status") {
|
|
25585
|
+
text = handleEgressStatus();
|
|
25586
|
+
} else if (toolName === "node9_egress_protect") {
|
|
25587
|
+
text = handleEgressProtect(toolArgs);
|
|
25588
|
+
} else if (toolName === "node9_egress_deny") {
|
|
25589
|
+
text = handleEgressDeny(toolArgs);
|
|
25221
25590
|
} else {
|
|
25222
25591
|
process.stdout.write(err(id, -32601, `Unknown tool: ${toolName}`) + "\n");
|
|
25223
25592
|
return;
|
|
@@ -25308,7 +25677,7 @@ function registerTrustCommand(program2) {
|
|
|
25308
25677
|
// src/cli/commands/mcp-pin.ts
|
|
25309
25678
|
var import_chalk21 = __toESM(require("chalk"));
|
|
25310
25679
|
init_mcp_pin();
|
|
25311
|
-
var
|
|
25680
|
+
var import_fs54 = __toESM(require("fs"));
|
|
25312
25681
|
function registerMcpPinCommand(program2) {
|
|
25313
25682
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
25314
25683
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -25319,7 +25688,7 @@ function registerMcpPinCommand(program2) {
|
|
|
25319
25688
|
let repoCorrupt = false;
|
|
25320
25689
|
if (found.source === "repo") {
|
|
25321
25690
|
try {
|
|
25322
|
-
const raw =
|
|
25691
|
+
const raw = import_fs54.default.readFileSync(found.path, "utf-8");
|
|
25323
25692
|
const parsed = JSON.parse(raw);
|
|
25324
25693
|
repoEntries = parsed.servers ?? {};
|
|
25325
25694
|
} catch {
|
|
@@ -25806,52 +26175,12 @@ function registerPostureCommand(program2) {
|
|
|
25806
26175
|
|
|
25807
26176
|
// src/cli/commands/egress.ts
|
|
25808
26177
|
var import_chalk26 = __toESM(require("chalk"));
|
|
25809
|
-
var import_fs52 = __toESM(require("fs"));
|
|
25810
|
-
var import_os46 = __toESM(require("os"));
|
|
25811
|
-
var import_path50 = __toESM(require("path"));
|
|
25812
26178
|
init_config();
|
|
25813
26179
|
init_dist();
|
|
25814
|
-
|
|
25815
|
-
enabled: false,
|
|
25816
|
-
mode: "review",
|
|
25817
|
-
allow: [],
|
|
25818
|
-
deny: [],
|
|
25819
|
-
allowPrivate: true
|
|
25820
|
-
};
|
|
25821
|
-
function configPath() {
|
|
25822
|
-
return import_path50.default.join(import_os46.default.homedir(), ".node9", "config.json");
|
|
25823
|
-
}
|
|
25824
|
-
function readRawConfig() {
|
|
25825
|
-
let text;
|
|
25826
|
-
try {
|
|
25827
|
-
text = import_fs52.default.readFileSync(configPath(), "utf8");
|
|
25828
|
-
} catch (err2) {
|
|
25829
|
-
if (err2.code === "ENOENT") return {};
|
|
25830
|
-
throw err2;
|
|
25831
|
-
}
|
|
25832
|
-
try {
|
|
25833
|
-
return JSON.parse(text);
|
|
25834
|
-
} catch {
|
|
25835
|
-
throw new Error(
|
|
25836
|
-
`${configPath()} is not valid JSON \u2014 fix it before changing egress (refusing to overwrite).`
|
|
25837
|
-
);
|
|
25838
|
-
}
|
|
25839
|
-
}
|
|
25840
|
-
function writeRawConfig(config) {
|
|
25841
|
-
const p = configPath();
|
|
25842
|
-
import_fs52.default.mkdirSync(import_path50.default.dirname(p), { recursive: true });
|
|
25843
|
-
import_fs52.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
25844
|
-
}
|
|
25845
|
-
function applyEgress(config, change) {
|
|
25846
|
-
const policy = config.policy = config.policy ?? {};
|
|
25847
|
-
const existing = policy.egress ?? {};
|
|
25848
|
-
policy.egress = { ...DEFAULT_EGRESS, ...existing, ...change };
|
|
25849
|
-
return config;
|
|
25850
|
-
}
|
|
25851
|
-
function withConfig(fn) {
|
|
25852
|
-
let config;
|
|
26180
|
+
function guard(fn) {
|
|
25853
26181
|
try {
|
|
25854
|
-
|
|
26182
|
+
fn();
|
|
26183
|
+
return true;
|
|
25855
26184
|
} catch (err2) {
|
|
25856
26185
|
console.error(import_chalk26.default.red(`
|
|
25857
26186
|
\u2717 ${err2.message}
|
|
@@ -25859,20 +26188,12 @@ function withConfig(fn) {
|
|
|
25859
26188
|
process.exitCode = 1;
|
|
25860
26189
|
return false;
|
|
25861
26190
|
}
|
|
25862
|
-
fn(config);
|
|
25863
|
-
writeRawConfig(config);
|
|
25864
|
-
return true;
|
|
25865
26191
|
}
|
|
25866
26192
|
function mutate(change) {
|
|
25867
|
-
return
|
|
26193
|
+
return guard(() => setEgress(change));
|
|
25868
26194
|
}
|
|
25869
26195
|
function addHost(list, host) {
|
|
25870
|
-
return
|
|
25871
|
-
const existing = config.policy?.egress ?? {};
|
|
25872
|
-
const current = { ...DEFAULT_EGRESS, ...existing };
|
|
25873
|
-
const updated = current[list].includes(host) ? current[list] : [...current[list], host];
|
|
25874
|
-
applyEgress(config, { [list]: updated });
|
|
25875
|
-
});
|
|
26196
|
+
return guard(() => addEgressHost(list, host));
|
|
25876
26197
|
}
|
|
25877
26198
|
function showStatus() {
|
|
25878
26199
|
const e = getConfig().policy.egress;
|
|
@@ -25931,16 +26252,191 @@ function registerEgressCommand(program2) {
|
|
|
25931
26252
|
egress.action(showStatus);
|
|
25932
26253
|
}
|
|
25933
26254
|
|
|
25934
|
-
// src/cli/commands/
|
|
26255
|
+
// src/cli/commands/jail.ts
|
|
25935
26256
|
var import_chalk27 = __toESM(require("chalk"));
|
|
26257
|
+
|
|
26258
|
+
// src/shields/jail.ts
|
|
25936
26259
|
var import_fs55 = __toESM(require("fs"));
|
|
25937
|
-
var
|
|
26260
|
+
var import_os47 = __toESM(require("os"));
|
|
26261
|
+
var import_path52 = __toESM(require("path"));
|
|
26262
|
+
init_shields();
|
|
26263
|
+
var USER_JAIL_SHIELD = "user-jail";
|
|
26264
|
+
function jailStorePath() {
|
|
26265
|
+
return import_path52.default.join(import_os47.default.homedir(), ".node9", "jail-paths.json");
|
|
26266
|
+
}
|
|
26267
|
+
function readJailPaths() {
|
|
26268
|
+
let text;
|
|
26269
|
+
try {
|
|
26270
|
+
text = import_fs55.default.readFileSync(jailStorePath(), "utf8");
|
|
26271
|
+
} catch (err2) {
|
|
26272
|
+
if (err2.code === "ENOENT") return [];
|
|
26273
|
+
throw err2;
|
|
26274
|
+
}
|
|
26275
|
+
let parsed;
|
|
26276
|
+
try {
|
|
26277
|
+
parsed = JSON.parse(text);
|
|
26278
|
+
} catch {
|
|
26279
|
+
throw new Error(`${jailStorePath()} is not valid JSON \u2014 fix it before changing the jail.`);
|
|
26280
|
+
}
|
|
26281
|
+
if (!Array.isArray(parsed.paths)) return [];
|
|
26282
|
+
return parsed.paths.filter(
|
|
26283
|
+
(p) => !!p && typeof p.path === "string" && (p.verdict === "block" || p.verdict === "review")
|
|
26284
|
+
);
|
|
26285
|
+
}
|
|
26286
|
+
function writeJailPaths(paths) {
|
|
26287
|
+
const p = jailStorePath();
|
|
26288
|
+
import_fs55.default.mkdirSync(import_path52.default.dirname(p), { recursive: true });
|
|
26289
|
+
import_fs55.default.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
26290
|
+
}
|
|
26291
|
+
function addJailPath(rawPath, verdict) {
|
|
26292
|
+
const norm = rawPath.trim();
|
|
26293
|
+
if (!pathToRegexFragment(norm)) {
|
|
26294
|
+
throw new Error(
|
|
26295
|
+
`"${rawPath}" is too broad to jail \u2014 give a specific path (e.g. ~/.gmail-mcp), not a home or root directory.`
|
|
26296
|
+
);
|
|
26297
|
+
}
|
|
26298
|
+
const next = [...readJailPaths().filter((p) => p.path !== norm), { path: norm, verdict }];
|
|
26299
|
+
writeJailPaths(next);
|
|
26300
|
+
return next;
|
|
26301
|
+
}
|
|
26302
|
+
function removeJailPath(rawPath) {
|
|
26303
|
+
const norm = rawPath.trim();
|
|
26304
|
+
const before = readJailPaths();
|
|
26305
|
+
const after = before.filter((p) => p.path !== norm);
|
|
26306
|
+
const removed = after.length !== before.length;
|
|
26307
|
+
if (removed) writeJailPaths(after);
|
|
26308
|
+
return { removed, paths: after };
|
|
26309
|
+
}
|
|
26310
|
+
function regenerateUserJail(paths) {
|
|
26311
|
+
const file = import_path52.default.join(USER_SHIELDS_DIR_PATH, `${USER_JAIL_SHIELD}.json`);
|
|
26312
|
+
if (paths.length === 0) {
|
|
26313
|
+
const active2 = readActiveShields();
|
|
26314
|
+
if (active2.includes(USER_JAIL_SHIELD)) {
|
|
26315
|
+
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
26316
|
+
}
|
|
26317
|
+
try {
|
|
26318
|
+
import_fs55.default.rmSync(file, { force: true });
|
|
26319
|
+
} catch {
|
|
26320
|
+
}
|
|
26321
|
+
return;
|
|
26322
|
+
}
|
|
26323
|
+
const def = buildShield({
|
|
26324
|
+
name: USER_JAIL_SHIELD,
|
|
26325
|
+
description: "User-added credential jail paths (node9 jail add)",
|
|
26326
|
+
blockPaths: paths.filter((p) => p.verdict === "block").map((p) => p.path),
|
|
26327
|
+
reviewPaths: paths.filter((p) => p.verdict === "review").map((p) => p.path)
|
|
26328
|
+
});
|
|
26329
|
+
installShield(USER_JAIL_SHIELD, def);
|
|
26330
|
+
const active = readActiveShields();
|
|
26331
|
+
if (!active.includes(USER_JAIL_SHIELD)) writeActiveShields([...active, USER_JAIL_SHIELD]);
|
|
26332
|
+
}
|
|
26333
|
+
|
|
26334
|
+
// src/cli/commands/jail.ts
|
|
26335
|
+
init_audit();
|
|
26336
|
+
var BUILTIN_JAIL = [
|
|
26337
|
+
"~/.ssh \u2014 SSH private keys",
|
|
26338
|
+
"~/.aws \u2014 AWS credentials",
|
|
26339
|
+
".env files",
|
|
26340
|
+
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
|
|
26341
|
+
];
|
|
26342
|
+
function registerJailCommand(program2) {
|
|
26343
|
+
const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
|
|
26344
|
+
jail.command("add <path>").description("Add a path to the jail (default: block reads; --review to soften)").option("--review", "Require human approval instead of hard-blocking reads").action((p, opts) => {
|
|
26345
|
+
const verdict = opts.review ? "review" : "block";
|
|
26346
|
+
try {
|
|
26347
|
+
const paths = addJailPath(p, verdict);
|
|
26348
|
+
regenerateUserJail(paths);
|
|
26349
|
+
appendConfigAudit({ event: "jail-add", path: p.trim(), verdict });
|
|
26350
|
+
} catch (err2) {
|
|
26351
|
+
console.error(import_chalk27.default.red(`
|
|
26352
|
+
\u274C ${err2.message}
|
|
26353
|
+
`));
|
|
26354
|
+
process.exit(1);
|
|
26355
|
+
return;
|
|
26356
|
+
}
|
|
26357
|
+
console.log(import_chalk27.default.green(`
|
|
26358
|
+
\u2705 Jailed ${p} (${verdict}).`));
|
|
26359
|
+
console.log(
|
|
26360
|
+
import_chalk27.default.gray(
|
|
26361
|
+
` AI reads of this path now ${verdict === "block" ? "BLOCK" : "require approval"}.`
|
|
26362
|
+
)
|
|
26363
|
+
);
|
|
26364
|
+
console.log(import_chalk27.default.gray(` Preview: ${import_chalk27.default.cyan(`node9 explain bash "cat ${p}"`)}
|
|
26365
|
+
`));
|
|
26366
|
+
});
|
|
26367
|
+
jail.command("remove <path>").description("Remove a user-added jail path (built-in paths are not removable)").action((p) => {
|
|
26368
|
+
let result;
|
|
26369
|
+
try {
|
|
26370
|
+
result = removeJailPath(p);
|
|
26371
|
+
} catch (err2) {
|
|
26372
|
+
console.error(import_chalk27.default.red(`
|
|
26373
|
+
\u274C ${err2.message}
|
|
26374
|
+
`));
|
|
26375
|
+
process.exit(1);
|
|
26376
|
+
return;
|
|
26377
|
+
}
|
|
26378
|
+
if (!result.removed) {
|
|
26379
|
+
console.error(import_chalk27.default.yellow(`
|
|
26380
|
+
\u2139\uFE0F "${p}" is not a user-added jail path.
|
|
26381
|
+
`));
|
|
26382
|
+
console.error(import_chalk27.default.gray(` Run ${import_chalk27.default.cyan("node9 jail list")} to see your paths.
|
|
26383
|
+
`));
|
|
26384
|
+
process.exit(1);
|
|
26385
|
+
return;
|
|
26386
|
+
}
|
|
26387
|
+
try {
|
|
26388
|
+
regenerateUserJail(result.paths);
|
|
26389
|
+
appendConfigAudit({ event: "jail-remove", path: p.trim() });
|
|
26390
|
+
} catch (err2) {
|
|
26391
|
+
console.error(import_chalk27.default.red(`
|
|
26392
|
+
\u274C ${err2.message}
|
|
26393
|
+
`));
|
|
26394
|
+
process.exit(1);
|
|
26395
|
+
return;
|
|
26396
|
+
}
|
|
26397
|
+
console.log(import_chalk27.default.green(`
|
|
26398
|
+
\u2705 Removed ${p} from the jail.
|
|
26399
|
+
`));
|
|
26400
|
+
});
|
|
26401
|
+
jail.command("list").description("Show built-in + user-added jail paths").action(() => {
|
|
26402
|
+
console.log(import_chalk27.default.bold("\n\u{1F512} Credential Jail\n"));
|
|
26403
|
+
console.log(import_chalk27.default.gray(" Built-in (always on, not removable):"));
|
|
26404
|
+
for (const b of BUILTIN_JAIL) console.log(` ${import_chalk27.default.gray("\u2022")} ${b}`);
|
|
26405
|
+
console.log("");
|
|
26406
|
+
let user;
|
|
26407
|
+
try {
|
|
26408
|
+
user = readJailPaths();
|
|
26409
|
+
} catch (err2) {
|
|
26410
|
+
console.error(import_chalk27.default.red(` \u2717 ${err2.message}
|
|
26411
|
+
`));
|
|
26412
|
+
process.exit(1);
|
|
26413
|
+
return;
|
|
26414
|
+
}
|
|
26415
|
+
if (user.length === 0) {
|
|
26416
|
+
console.log(
|
|
26417
|
+
import_chalk27.default.gray(" Your paths: (none) \u2014 add one with ") + import_chalk27.default.cyan("node9 jail add <path>")
|
|
26418
|
+
);
|
|
26419
|
+
} else {
|
|
26420
|
+
console.log(import_chalk27.default.gray(" Your paths (removable):"));
|
|
26421
|
+
for (const u of user) {
|
|
26422
|
+
const v = u.verdict === "block" ? import_chalk27.default.red("block ") : import_chalk27.default.yellow("review");
|
|
26423
|
+
console.log(` ${v} ${import_chalk27.default.cyan(u.path)}`);
|
|
26424
|
+
}
|
|
26425
|
+
}
|
|
26426
|
+
console.log("");
|
|
26427
|
+
});
|
|
26428
|
+
}
|
|
26429
|
+
|
|
26430
|
+
// src/cli/commands/sandbox.ts
|
|
26431
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
26432
|
+
var import_fs58 = __toESM(require("fs"));
|
|
26433
|
+
var import_path55 = __toESM(require("path"));
|
|
25938
26434
|
var import_child_process13 = require("child_process");
|
|
25939
26435
|
init_config();
|
|
25940
26436
|
|
|
25941
26437
|
// src/sandbox/config.ts
|
|
25942
|
-
var
|
|
25943
|
-
var
|
|
26438
|
+
var import_fs56 = __toESM(require("fs"));
|
|
26439
|
+
var import_path53 = __toESM(require("path"));
|
|
25944
26440
|
var import_yaml = require("yaml");
|
|
25945
26441
|
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
25946
26442
|
var FORBIDDEN_ENV = /* @__PURE__ */ new Set(["NODE9_API_KEY", "NODE9_API_URL"]);
|
|
@@ -26013,16 +26509,16 @@ function scaffoldSandboxYaml(agent) {
|
|
|
26013
26509
|
return header + (0, import_yaml.stringify)(defaultSandboxConfig(agent));
|
|
26014
26510
|
}
|
|
26015
26511
|
function sandboxConfigPath(cwd = process.cwd()) {
|
|
26016
|
-
return
|
|
26512
|
+
return import_path53.default.join(cwd, SANDBOX_CONFIG_FILE);
|
|
26017
26513
|
}
|
|
26018
26514
|
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
26019
26515
|
const p = sandboxConfigPath(cwd);
|
|
26020
|
-
if (!
|
|
26516
|
+
if (!import_fs56.default.existsSync(p)) {
|
|
26021
26517
|
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
26022
26518
|
}
|
|
26023
26519
|
let raw;
|
|
26024
26520
|
try {
|
|
26025
|
-
raw = (0, import_yaml.parse)(
|
|
26521
|
+
raw = (0, import_yaml.parse)(import_fs56.default.readFileSync(p, "utf-8"));
|
|
26026
26522
|
} catch (err2) {
|
|
26027
26523
|
throw new Error(
|
|
26028
26524
|
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
@@ -26080,14 +26576,14 @@ function compileAllowlist(input) {
|
|
|
26080
26576
|
init_templates();
|
|
26081
26577
|
|
|
26082
26578
|
// src/sandbox/runtime.ts
|
|
26083
|
-
var
|
|
26084
|
-
var
|
|
26085
|
-
var
|
|
26579
|
+
var import_fs57 = __toESM(require("fs"));
|
|
26580
|
+
var import_os48 = __toESM(require("os"));
|
|
26581
|
+
var import_path54 = __toESM(require("path"));
|
|
26086
26582
|
var import_crypto13 = __toESM(require("crypto"));
|
|
26087
26583
|
var import_child_process12 = require("child_process");
|
|
26088
26584
|
init_templates();
|
|
26089
26585
|
function sandboxDataDir(cwd = process.cwd()) {
|
|
26090
|
-
return
|
|
26586
|
+
return import_path54.default.join(cwd, ".node9", "sandbox", "data");
|
|
26091
26587
|
}
|
|
26092
26588
|
function detectEngine(engine) {
|
|
26093
26589
|
const r = (0, import_child_process12.spawnSync)(engine, ["--version"], { encoding: "utf-8" });
|
|
@@ -26098,7 +26594,7 @@ function detectEngine(engine) {
|
|
|
26098
26594
|
}
|
|
26099
26595
|
function agentCredentialsMount(agent) {
|
|
26100
26596
|
const rel = agent === "codex" ? ".codex/auth.json" : ".claude/.credentials.json";
|
|
26101
|
-
return { hostPath:
|
|
26597
|
+
return { hostPath: import_path54.default.join(import_os48.default.homedir(), rel), target: `/home/${RUN_AS_USER}/${rel}` };
|
|
26102
26598
|
}
|
|
26103
26599
|
function buildRunArgs(opts) {
|
|
26104
26600
|
const { config, workspaceHostPath, dataHostPath, allowlistHostPath, agentArgs } = opts;
|
|
@@ -26108,7 +26604,7 @@ function buildRunArgs(opts) {
|
|
|
26108
26604
|
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
26109
26605
|
if (config.node9.mountAgentCredentials) {
|
|
26110
26606
|
const creds = agentCredentialsMount(config.agent);
|
|
26111
|
-
if (
|
|
26607
|
+
if (import_fs57.default.existsSync(creds.hostPath)) {
|
|
26112
26608
|
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
26113
26609
|
}
|
|
26114
26610
|
}
|
|
@@ -26126,30 +26622,30 @@ function imageContentHash(dockerfile, entrypoint) {
|
|
|
26126
26622
|
return import_crypto13.default.createHash("sha256").update(dockerfile).update("\0").update(entrypoint).digest("hex").slice(0, 16);
|
|
26127
26623
|
}
|
|
26128
26624
|
function sandboxBuildDir(cwd = process.cwd()) {
|
|
26129
|
-
return
|
|
26625
|
+
return import_path54.default.join(cwd, ".node9", "sandbox", "build");
|
|
26130
26626
|
}
|
|
26131
26627
|
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
26132
26628
|
const dir = sandboxBuildDir(cwd);
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
|
|
26629
|
+
import_fs57.default.mkdirSync(dir, { recursive: true });
|
|
26630
|
+
import_fs57.default.writeFileSync(import_path54.default.join(dir, "Dockerfile"), dockerfile);
|
|
26631
|
+
import_fs57.default.writeFileSync(import_path54.default.join(dir, "entrypoint.sh"), entrypoint);
|
|
26136
26632
|
return dir;
|
|
26137
26633
|
}
|
|
26138
26634
|
function writeAllowlist(cwd, hosts) {
|
|
26139
|
-
const dir =
|
|
26140
|
-
|
|
26141
|
-
const p =
|
|
26142
|
-
|
|
26635
|
+
const dir = import_path54.default.join(cwd, ".node9", "sandbox");
|
|
26636
|
+
import_fs57.default.mkdirSync(dir, { recursive: true });
|
|
26637
|
+
const p = import_path54.default.join(dir, "allowed-domains.txt");
|
|
26638
|
+
import_fs57.default.writeFileSync(p, hosts.join("\n") + "\n");
|
|
26143
26639
|
return p;
|
|
26144
26640
|
}
|
|
26145
26641
|
function resolveHomePath(p) {
|
|
26146
|
-
return p.startsWith("~") ?
|
|
26642
|
+
return p.startsWith("~") ? import_path54.default.join(import_os48.default.homedir(), p.slice(1)) : import_path54.default.resolve(p);
|
|
26147
26643
|
}
|
|
26148
26644
|
|
|
26149
26645
|
// src/cli/commands/sandbox.ts
|
|
26150
26646
|
function seedDataDirConfig(dataDir, sandbox) {
|
|
26151
|
-
|
|
26152
|
-
const
|
|
26647
|
+
import_fs58.default.mkdirSync(dataDir, { recursive: true });
|
|
26648
|
+
const configPath = import_path55.default.join(dataDir, "config.json");
|
|
26153
26649
|
const seed = {
|
|
26154
26650
|
settings: {
|
|
26155
26651
|
approvers: {
|
|
@@ -26160,7 +26656,7 @@ function seedDataDirConfig(dataDir, sandbox) {
|
|
|
26160
26656
|
}
|
|
26161
26657
|
}
|
|
26162
26658
|
};
|
|
26163
|
-
|
|
26659
|
+
import_fs58.default.writeFileSync(configPath, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
26164
26660
|
}
|
|
26165
26661
|
function registerSandboxCommand(program2, version2) {
|
|
26166
26662
|
const node9Version2 = pinnedNode9Version(version2);
|
|
@@ -26168,18 +26664,18 @@ function registerSandboxCommand(program2, version2) {
|
|
|
26168
26664
|
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
26169
26665
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
26170
26666
|
const p = sandboxConfigPath();
|
|
26171
|
-
if (
|
|
26667
|
+
if (import_fs58.default.existsSync(p)) {
|
|
26172
26668
|
console.log(
|
|
26173
|
-
|
|
26669
|
+
import_chalk28.default.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
26174
26670
|
);
|
|
26175
26671
|
return;
|
|
26176
26672
|
}
|
|
26177
|
-
|
|
26673
|
+
import_fs58.default.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
26178
26674
|
console.log(
|
|
26179
|
-
|
|
26675
|
+
import_chalk28.default.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + import_chalk28.default.dim(` (agent: ${agent})`)
|
|
26180
26676
|
);
|
|
26181
26677
|
console.log(
|
|
26182
|
-
|
|
26678
|
+
import_chalk28.default.dim(" Edit it (mounts / allow / expose), then: ") + import_chalk28.default.cyan("node9 sandbox run")
|
|
26183
26679
|
);
|
|
26184
26680
|
});
|
|
26185
26681
|
cmd.command("run [agent]").description("Build (if needed) + run the agent jailed. Extra args after -- go to the agent.").allowUnknownOption(true).allowExcessArguments(true).action((agentArg, _opts, command) => {
|
|
@@ -26189,7 +26685,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
26189
26685
|
const engine = detectEngine(sandbox.runtime.engine);
|
|
26190
26686
|
if (!engine.available) {
|
|
26191
26687
|
console.error(
|
|
26192
|
-
|
|
26688
|
+
import_chalk28.default.red(` ${sandbox.runtime.engine} not found.`) + import_chalk28.default.dim(` Install it first \u2014 node9 sandbox needs a container runtime.`)
|
|
26193
26689
|
);
|
|
26194
26690
|
process.exit(1);
|
|
26195
26691
|
}
|
|
@@ -26202,11 +26698,11 @@ function registerSandboxCommand(program2, version2) {
|
|
|
26202
26698
|
});
|
|
26203
26699
|
if (compiled.rejected.length) {
|
|
26204
26700
|
console.log(
|
|
26205
|
-
|
|
26701
|
+
import_chalk28.default.yellow(` \u26A0 ignoring invalid allow hosts: ${compiled.rejected.join(", ")}`)
|
|
26206
26702
|
);
|
|
26207
26703
|
}
|
|
26208
26704
|
if (compiled.denied.length) {
|
|
26209
|
-
console.log(
|
|
26705
|
+
console.log(import_chalk28.default.dim(` (denied: ${compiled.denied.join(", ")})`));
|
|
26210
26706
|
}
|
|
26211
26707
|
const allowlistPath = writeAllowlist(cwd, compiled.allow);
|
|
26212
26708
|
const dockerfile = renderDockerfile(sandbox, node9Version2);
|
|
@@ -26214,20 +26710,20 @@ function registerSandboxCommand(program2, version2) {
|
|
|
26214
26710
|
const buildDir = writeBuildContext(cwd, dockerfile, entrypoint);
|
|
26215
26711
|
const hash = imageContentHash(dockerfile, entrypoint);
|
|
26216
26712
|
const image = sandbox.runtime.image;
|
|
26217
|
-
const hashFile =
|
|
26218
|
-
const lastHash =
|
|
26713
|
+
const hashFile = import_path55.default.join(sandboxBuildDir(cwd), ".image-hash");
|
|
26714
|
+
const lastHash = import_fs58.default.existsSync(hashFile) ? import_fs58.default.readFileSync(hashFile, "utf-8").trim() : "";
|
|
26219
26715
|
const imageExists = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
26220
26716
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
26221
26717
|
if (needBuild) {
|
|
26222
|
-
console.log(
|
|
26718
|
+
console.log(import_chalk28.default.dim(` building ${image} \u2026`));
|
|
26223
26719
|
const b = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["build", "-t", image, buildDir], {
|
|
26224
26720
|
stdio: "inherit"
|
|
26225
26721
|
});
|
|
26226
26722
|
if (b.status !== 0) {
|
|
26227
|
-
console.error(
|
|
26723
|
+
console.error(import_chalk28.default.red(" build failed."));
|
|
26228
26724
|
process.exit(b.status ?? 1);
|
|
26229
26725
|
}
|
|
26230
|
-
|
|
26726
|
+
import_fs58.default.writeFileSync(hashFile, hash);
|
|
26231
26727
|
}
|
|
26232
26728
|
const dataDir = sandboxDataDir(cwd);
|
|
26233
26729
|
seedDataDirConfig(dataDir, sandbox);
|
|
@@ -26241,36 +26737,36 @@ function registerSandboxCommand(program2, version2) {
|
|
|
26241
26737
|
});
|
|
26242
26738
|
if (sandbox.node9.mountAgentCredentials) {
|
|
26243
26739
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
26244
|
-
if (
|
|
26245
|
-
console.log(
|
|
26740
|
+
if (import_fs58.default.existsSync(creds.hostPath)) {
|
|
26741
|
+
console.log(import_chalk28.default.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
26246
26742
|
} else {
|
|
26247
26743
|
console.log(
|
|
26248
|
-
|
|
26744
|
+
import_chalk28.default.yellow(` \u26A0 ${creds.hostPath} not found \u2014 `) + import_chalk28.default.dim(`the agent must auth via an env key in env.pass.`)
|
|
26249
26745
|
);
|
|
26250
26746
|
}
|
|
26251
26747
|
}
|
|
26252
26748
|
console.log(
|
|
26253
|
-
|
|
26749
|
+
import_chalk28.default.green(` \u{1F6E1}\uFE0F ${sandbox.agent} jailed \u2014 ${compiled.allow.length} hosts allowed
|
|
26254
26750
|
`)
|
|
26255
26751
|
);
|
|
26256
26752
|
const r = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, runArgs, { stdio: "inherit" });
|
|
26257
26753
|
process.exit(r.status ?? 0);
|
|
26258
26754
|
});
|
|
26259
26755
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
26260
|
-
const auditPath =
|
|
26261
|
-
if (!
|
|
26262
|
-
console.log(
|
|
26756
|
+
const auditPath = import_path55.default.join(sandboxDataDir(), "audit.log");
|
|
26757
|
+
if (!import_fs58.default.existsSync(auditPath)) {
|
|
26758
|
+
console.log(import_chalk28.default.dim(" no sandbox audit yet."));
|
|
26263
26759
|
return;
|
|
26264
26760
|
}
|
|
26265
26761
|
(0, import_child_process13.spawnSync)("tail", ["-f", auditPath], { stdio: "inherit" });
|
|
26266
26762
|
});
|
|
26267
26763
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
26268
|
-
const auditPath =
|
|
26269
|
-
if (!
|
|
26270
|
-
console.log(
|
|
26764
|
+
const auditPath = import_path55.default.join(sandboxDataDir(), "audit.log");
|
|
26765
|
+
if (!import_fs58.default.existsSync(auditPath)) {
|
|
26766
|
+
console.log(import_chalk28.default.dim(" no sandbox audit yet."));
|
|
26271
26767
|
return;
|
|
26272
26768
|
}
|
|
26273
|
-
process.stdout.write(
|
|
26769
|
+
process.stdout.write(import_fs58.default.readFileSync(auditPath, "utf-8"));
|
|
26274
26770
|
});
|
|
26275
26771
|
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
26276
26772
|
const cwd = process.cwd();
|
|
@@ -26284,16 +26780,16 @@ function registerSandboxCommand(program2, version2) {
|
|
|
26284
26780
|
stdio: "ignore"
|
|
26285
26781
|
});
|
|
26286
26782
|
}
|
|
26287
|
-
|
|
26288
|
-
console.log(
|
|
26783
|
+
import_fs58.default.rmSync(import_path55.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
26784
|
+
console.log(import_chalk28.default.green(" \u2713 sandbox image + build + data removed."));
|
|
26289
26785
|
});
|
|
26290
26786
|
}
|
|
26291
26787
|
|
|
26292
26788
|
// src/cli/commands/sessions.ts
|
|
26293
|
-
var
|
|
26294
|
-
var
|
|
26295
|
-
var
|
|
26296
|
-
var
|
|
26789
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
26790
|
+
var import_fs59 = __toESM(require("fs"));
|
|
26791
|
+
var import_path56 = __toESM(require("path"));
|
|
26792
|
+
var import_os49 = __toESM(require("os"));
|
|
26297
26793
|
init_scan_summary();
|
|
26298
26794
|
init_litellm();
|
|
26299
26795
|
init_cost_gemini();
|
|
@@ -26314,10 +26810,10 @@ function encodeProjectPath(projectPath) {
|
|
|
26314
26810
|
}
|
|
26315
26811
|
function sessionJsonlPath(projectPath, sessionId) {
|
|
26316
26812
|
const encoded = encodeProjectPath(projectPath);
|
|
26317
|
-
return
|
|
26813
|
+
return import_path56.default.join(import_os49.default.homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
26318
26814
|
}
|
|
26319
26815
|
function projectLabel(projectPath) {
|
|
26320
|
-
return projectPath.replace(
|
|
26816
|
+
return projectPath.replace(import_os49.default.homedir(), "~");
|
|
26321
26817
|
}
|
|
26322
26818
|
function parseHistoryLines(lines) {
|
|
26323
26819
|
const entries = [];
|
|
@@ -26386,10 +26882,10 @@ function parseSessionLines(lines) {
|
|
|
26386
26882
|
return { toolCalls, costUSD, hasSnapshot, modifiedFiles };
|
|
26387
26883
|
}
|
|
26388
26884
|
function loadAuditEntries(auditPath) {
|
|
26389
|
-
const aPath = auditPath ??
|
|
26885
|
+
const aPath = auditPath ?? import_path56.default.join(import_os49.default.homedir(), ".node9", "audit.log");
|
|
26390
26886
|
let raw;
|
|
26391
26887
|
try {
|
|
26392
|
-
raw =
|
|
26888
|
+
raw = import_fs59.default.readFileSync(aPath, "utf-8");
|
|
26393
26889
|
} catch {
|
|
26394
26890
|
return [];
|
|
26395
26891
|
}
|
|
@@ -26425,8 +26921,8 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
26425
26921
|
return result;
|
|
26426
26922
|
}
|
|
26427
26923
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
26428
|
-
const tmpDir =
|
|
26429
|
-
if (!
|
|
26924
|
+
const tmpDir = import_path56.default.join(import_os49.default.homedir(), ".gemini", "tmp");
|
|
26925
|
+
if (!import_fs59.default.existsSync(tmpDir)) return [];
|
|
26430
26926
|
const cutoff = days !== null ? (() => {
|
|
26431
26927
|
const d = /* @__PURE__ */ new Date();
|
|
26432
26928
|
d.setDate(d.getDate() - days);
|
|
@@ -26435,35 +26931,35 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
26435
26931
|
})() : null;
|
|
26436
26932
|
let slugDirs;
|
|
26437
26933
|
try {
|
|
26438
|
-
slugDirs =
|
|
26934
|
+
slugDirs = import_fs59.default.readdirSync(tmpDir);
|
|
26439
26935
|
} catch {
|
|
26440
26936
|
return [];
|
|
26441
26937
|
}
|
|
26442
26938
|
const summaries = [];
|
|
26443
|
-
for (const
|
|
26444
|
-
const slugPath =
|
|
26939
|
+
for (const slug2 of slugDirs) {
|
|
26940
|
+
const slugPath = import_path56.default.join(tmpDir, slug2);
|
|
26445
26941
|
try {
|
|
26446
|
-
if (!
|
|
26942
|
+
if (!import_fs59.default.statSync(slugPath).isDirectory()) continue;
|
|
26447
26943
|
} catch {
|
|
26448
26944
|
continue;
|
|
26449
26945
|
}
|
|
26450
|
-
let projectRoot =
|
|
26946
|
+
let projectRoot = import_path56.default.join(import_os49.default.homedir(), slug2);
|
|
26451
26947
|
try {
|
|
26452
|
-
projectRoot =
|
|
26948
|
+
projectRoot = import_fs59.default.readFileSync(import_path56.default.join(slugPath, ".project_root"), "utf-8").trim();
|
|
26453
26949
|
} catch {
|
|
26454
26950
|
}
|
|
26455
|
-
const chatsDir =
|
|
26456
|
-
if (!
|
|
26951
|
+
const chatsDir = import_path56.default.join(slugPath, "chats");
|
|
26952
|
+
if (!import_fs59.default.existsSync(chatsDir)) continue;
|
|
26457
26953
|
let chatFiles;
|
|
26458
26954
|
try {
|
|
26459
|
-
chatFiles =
|
|
26955
|
+
chatFiles = import_fs59.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
26460
26956
|
} catch {
|
|
26461
26957
|
continue;
|
|
26462
26958
|
}
|
|
26463
26959
|
for (const chatFile of chatFiles) {
|
|
26464
26960
|
let raw;
|
|
26465
26961
|
try {
|
|
26466
|
-
raw =
|
|
26962
|
+
raw = import_fs59.default.readFileSync(import_path56.default.join(chatsDir, chatFile), "utf-8");
|
|
26467
26963
|
} catch {
|
|
26468
26964
|
continue;
|
|
26469
26965
|
}
|
|
@@ -26543,8 +27039,8 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
26543
27039
|
return summaries;
|
|
26544
27040
|
}
|
|
26545
27041
|
function buildCodexSessions(days, allAuditEntries) {
|
|
26546
|
-
const sessionsBase =
|
|
26547
|
-
if (!
|
|
27042
|
+
const sessionsBase = import_path56.default.join(import_os49.default.homedir(), ".codex", "sessions");
|
|
27043
|
+
if (!import_fs59.default.existsSync(sessionsBase)) return [];
|
|
26548
27044
|
const cutoff = days !== null ? (() => {
|
|
26549
27045
|
const d = /* @__PURE__ */ new Date();
|
|
26550
27046
|
d.setDate(d.getDate() - days);
|
|
@@ -26553,29 +27049,29 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
26553
27049
|
})() : null;
|
|
26554
27050
|
const jsonlFiles = [];
|
|
26555
27051
|
try {
|
|
26556
|
-
for (const year of
|
|
26557
|
-
const yearPath =
|
|
27052
|
+
for (const year of import_fs59.default.readdirSync(sessionsBase)) {
|
|
27053
|
+
const yearPath = import_path56.default.join(sessionsBase, year);
|
|
26558
27054
|
try {
|
|
26559
|
-
if (!
|
|
27055
|
+
if (!import_fs59.default.statSync(yearPath).isDirectory()) continue;
|
|
26560
27056
|
} catch {
|
|
26561
27057
|
continue;
|
|
26562
27058
|
}
|
|
26563
|
-
for (const month of
|
|
26564
|
-
const monthPath =
|
|
27059
|
+
for (const month of import_fs59.default.readdirSync(yearPath)) {
|
|
27060
|
+
const monthPath = import_path56.default.join(yearPath, month);
|
|
26565
27061
|
try {
|
|
26566
|
-
if (!
|
|
27062
|
+
if (!import_fs59.default.statSync(monthPath).isDirectory()) continue;
|
|
26567
27063
|
} catch {
|
|
26568
27064
|
continue;
|
|
26569
27065
|
}
|
|
26570
|
-
for (const day of
|
|
26571
|
-
const dayPath =
|
|
27066
|
+
for (const day of import_fs59.default.readdirSync(monthPath)) {
|
|
27067
|
+
const dayPath = import_path56.default.join(monthPath, day);
|
|
26572
27068
|
try {
|
|
26573
|
-
if (!
|
|
27069
|
+
if (!import_fs59.default.statSync(dayPath).isDirectory()) continue;
|
|
26574
27070
|
} catch {
|
|
26575
27071
|
continue;
|
|
26576
27072
|
}
|
|
26577
|
-
for (const file of
|
|
26578
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
27073
|
+
for (const file of import_fs59.default.readdirSync(dayPath)) {
|
|
27074
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path56.default.join(dayPath, file));
|
|
26579
27075
|
}
|
|
26580
27076
|
}
|
|
26581
27077
|
}
|
|
@@ -26587,7 +27083,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
26587
27083
|
for (const filePath of jsonlFiles) {
|
|
26588
27084
|
let lines;
|
|
26589
27085
|
try {
|
|
26590
|
-
lines =
|
|
27086
|
+
lines = import_fs59.default.readFileSync(filePath, "utf-8").split("\n");
|
|
26591
27087
|
} catch {
|
|
26592
27088
|
continue;
|
|
26593
27089
|
}
|
|
@@ -26673,10 +27169,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
26673
27169
|
return summaries;
|
|
26674
27170
|
}
|
|
26675
27171
|
function buildSessions(days, historyPath) {
|
|
26676
|
-
const hPath = historyPath ??
|
|
27172
|
+
const hPath = historyPath ?? import_path56.default.join(import_os49.default.homedir(), ".claude", "history.jsonl");
|
|
26677
27173
|
let historyRaw = "";
|
|
26678
27174
|
try {
|
|
26679
|
-
historyRaw =
|
|
27175
|
+
historyRaw = import_fs59.default.readFileSync(hPath, "utf-8");
|
|
26680
27176
|
} catch {
|
|
26681
27177
|
}
|
|
26682
27178
|
const cutoff = days !== null ? (() => {
|
|
@@ -26700,7 +27196,7 @@ function buildSessions(days, historyPath) {
|
|
|
26700
27196
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
26701
27197
|
let sessionLines = [];
|
|
26702
27198
|
try {
|
|
26703
|
-
sessionLines =
|
|
27199
|
+
sessionLines = import_fs59.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
26704
27200
|
} catch {
|
|
26705
27201
|
}
|
|
26706
27202
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -26786,11 +27282,11 @@ function toolInputSummary(tool, input) {
|
|
|
26786
27282
|
}
|
|
26787
27283
|
function toolColor(tool) {
|
|
26788
27284
|
const t = tool.toLowerCase();
|
|
26789
|
-
if (t === "bash" || t === "execute_bash") return
|
|
26790
|
-
if (t === "write") return
|
|
26791
|
-
if (t === "edit" || t === "notebookedit") return
|
|
26792
|
-
if (t === "read") return
|
|
26793
|
-
return
|
|
27285
|
+
if (t === "bash" || t === "execute_bash") return import_chalk29.default.red;
|
|
27286
|
+
if (t === "write") return import_chalk29.default.green;
|
|
27287
|
+
if (t === "edit" || t === "notebookedit") return import_chalk29.default.yellow;
|
|
27288
|
+
if (t === "read") return import_chalk29.default.cyan;
|
|
27289
|
+
return import_chalk29.default.gray;
|
|
26794
27290
|
}
|
|
26795
27291
|
function barStr2(value, max, width) {
|
|
26796
27292
|
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
@@ -26800,7 +27296,7 @@ function barStr2(value, max, width) {
|
|
|
26800
27296
|
function colorBar2(value, max, width) {
|
|
26801
27297
|
const s = barStr2(value, max, width);
|
|
26802
27298
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
26803
|
-
return
|
|
27299
|
+
return import_chalk29.default.cyan(s.slice(0, filled)) + import_chalk29.default.dim(s.slice(filled));
|
|
26804
27300
|
}
|
|
26805
27301
|
function renderSummary(summaries) {
|
|
26806
27302
|
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
@@ -26830,45 +27326,45 @@ function renderSummary(summaries) {
|
|
|
26830
27326
|
}
|
|
26831
27327
|
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
26832
27328
|
const W = 20;
|
|
26833
|
-
console.log(
|
|
27329
|
+
console.log(import_chalk29.default.dim(" " + "\u2500".repeat(70)));
|
|
26834
27330
|
console.log(
|
|
26835
|
-
" " +
|
|
27331
|
+
" " + import_chalk29.default.bold.white(String(summaries.length).padEnd(4)) + import_chalk29.default.dim("sessions ") + import_chalk29.default.bold.yellow(fmtCost3(totalCost).padEnd(10)) + import_chalk29.default.dim("total ") + import_chalk29.default.bold.white(String(totalTools).padEnd(6)) + import_chalk29.default.dim("tool calls ") + import_chalk29.default.bold.white(String(totalFiles)) + import_chalk29.default.dim(" files modified") + (totalBlocked > 0 ? import_chalk29.default.dim(" ") + import_chalk29.default.red.bold(String(totalBlocked)) + import_chalk29.default.dim(" blocked by node9") : "")
|
|
26836
27332
|
);
|
|
26837
27333
|
console.log(
|
|
26838
|
-
" " +
|
|
27334
|
+
" " + import_chalk29.default.dim("avg ") + import_chalk29.default.white(fmtCost3(avgCost).padEnd(10)) + import_chalk29.default.dim("/session ") + import_chalk29.default.green(String(snapshots)) + import_chalk29.default.dim(` of ${summaries.length} sessions had snapshots`)
|
|
26839
27335
|
);
|
|
26840
27336
|
console.log("");
|
|
26841
|
-
console.log(" " +
|
|
27337
|
+
console.log(" " + import_chalk29.default.dim("Tool breakdown:"));
|
|
26842
27338
|
const maxGroup = Math.max(...Object.values(groups));
|
|
26843
27339
|
for (const [label2, count] of Object.entries(groups)) {
|
|
26844
27340
|
if (count === 0) continue;
|
|
26845
27341
|
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
26846
27342
|
console.log(
|
|
26847
|
-
" " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " +
|
|
27343
|
+
" " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk29.default.white(String(count).padStart(4)) + import_chalk29.default.dim(` (${String(pct)}%)`)
|
|
26848
27344
|
);
|
|
26849
27345
|
}
|
|
26850
27346
|
console.log("");
|
|
26851
27347
|
if (topProjects.length > 1) {
|
|
26852
|
-
console.log(" " +
|
|
27348
|
+
console.log(" " + import_chalk29.default.dim("Cost by project:"));
|
|
26853
27349
|
const maxProjCost = topProjects[0][1];
|
|
26854
27350
|
for (const [proj, cost] of topProjects) {
|
|
26855
27351
|
console.log(
|
|
26856
|
-
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " +
|
|
27352
|
+
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + import_chalk29.default.yellow(fmtCost3(cost))
|
|
26857
27353
|
);
|
|
26858
27354
|
}
|
|
26859
27355
|
console.log("");
|
|
26860
27356
|
}
|
|
26861
|
-
console.log(
|
|
27357
|
+
console.log(import_chalk29.default.dim(" " + "\u2500".repeat(70)));
|
|
26862
27358
|
console.log("");
|
|
26863
27359
|
}
|
|
26864
27360
|
function renderList(summaries, totalCost) {
|
|
26865
27361
|
if (summaries.length === 0) {
|
|
26866
|
-
console.log(
|
|
27362
|
+
console.log(import_chalk29.default.yellow(" No sessions found in the requested range.\n"));
|
|
26867
27363
|
return;
|
|
26868
27364
|
}
|
|
26869
|
-
const totalLabel = totalCost > 0 ?
|
|
27365
|
+
const totalLabel = totalCost > 0 ? import_chalk29.default.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
26870
27366
|
console.log(
|
|
26871
|
-
" " +
|
|
27367
|
+
" " + import_chalk29.default.white(String(summaries.length)) + import_chalk29.default.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
26872
27368
|
);
|
|
26873
27369
|
console.log("");
|
|
26874
27370
|
let lastGroup = "";
|
|
@@ -26876,51 +27372,51 @@ function renderList(summaries, totalCost) {
|
|
|
26876
27372
|
const activeDate = fmtDate2(s.lastActiveTime);
|
|
26877
27373
|
const group = activeDate + " " + s.projectLabel;
|
|
26878
27374
|
if (group !== lastGroup) {
|
|
26879
|
-
console.log(
|
|
27375
|
+
console.log(import_chalk29.default.dim(" \u2500\u2500\u2500 ") + import_chalk29.default.bold(activeDate) + import_chalk29.default.dim(" " + s.projectLabel));
|
|
26880
27376
|
lastGroup = group;
|
|
26881
27377
|
}
|
|
26882
27378
|
const startDate = fmtDate2(s.startTime);
|
|
26883
|
-
const dateRange = startDate !== activeDate ?
|
|
26884
|
-
const timeStr =
|
|
26885
|
-
const prompt =
|
|
26886
|
-
const tools = s.toolCalls.length > 0 ?
|
|
26887
|
-
const cost = s.costUSD > 0 ?
|
|
26888
|
-
const blocked = s.blockedCalls.length > 0 ?
|
|
26889
|
-
const snap = s.hasSnapshot ?
|
|
26890
|
-
const agentBadge =
|
|
27379
|
+
const dateRange = startDate !== activeDate ? import_chalk29.default.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
|
|
27380
|
+
const timeStr = import_chalk29.default.dim(fmtTime(s.startTime));
|
|
27381
|
+
const prompt = import_chalk29.default.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
|
|
27382
|
+
const tools = s.toolCalls.length > 0 ? import_chalk29.default.dim(String(s.toolCalls.length).padStart(3) + " tools") : import_chalk29.default.dim(" 0 tools");
|
|
27383
|
+
const cost = s.costUSD > 0 ? import_chalk29.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
27384
|
+
const blocked = s.blockedCalls.length > 0 ? import_chalk29.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
27385
|
+
const snap = s.hasSnapshot ? import_chalk29.default.green(" \u{1F4F8}") : "";
|
|
27386
|
+
const agentBadge = import_chalk29.default[agentColorName(s.agent ?? "claude")](
|
|
26891
27387
|
" " + agentBadgeText(s.agent ?? "claude", 0)
|
|
26892
27388
|
);
|
|
26893
|
-
const sid =
|
|
27389
|
+
const sid = import_chalk29.default.dim(" " + s.sessionId.slice(0, 8));
|
|
26894
27390
|
console.log(
|
|
26895
27391
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
26896
27392
|
);
|
|
26897
27393
|
}
|
|
26898
27394
|
console.log("");
|
|
26899
27395
|
console.log(
|
|
26900
|
-
|
|
27396
|
+
import_chalk29.default.dim(" Run") + " " + import_chalk29.default.cyan("node9 sessions --detail <session-id>") + import_chalk29.default.dim(" for full tool trace.")
|
|
26901
27397
|
);
|
|
26902
27398
|
console.log("");
|
|
26903
27399
|
}
|
|
26904
27400
|
function renderDetail(s) {
|
|
26905
27401
|
console.log("");
|
|
26906
|
-
console.log(
|
|
27402
|
+
console.log(import_chalk29.default.bold(" Session ") + import_chalk29.default.dim(s.sessionId));
|
|
26907
27403
|
console.log(
|
|
26908
|
-
|
|
27404
|
+
import_chalk29.default.bold(" Prompt ") + import_chalk29.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
26909
27405
|
);
|
|
26910
|
-
console.log(
|
|
27406
|
+
console.log(import_chalk29.default.bold(" Project ") + import_chalk29.default.white(s.projectLabel));
|
|
26911
27407
|
if (s.agent) {
|
|
26912
|
-
const agentLabel2 =
|
|
26913
|
-
console.log(
|
|
27408
|
+
const agentLabel2 = import_chalk29.default[agentColorName(s.agent)](agentDisplayName(s.agent));
|
|
27409
|
+
console.log(import_chalk29.default.bold(" Agent ") + agentLabel2);
|
|
26914
27410
|
}
|
|
26915
|
-
console.log(
|
|
27411
|
+
console.log(import_chalk29.default.bold(" When ") + import_chalk29.default.white(fmtDateTime(s.startTime)));
|
|
26916
27412
|
if (s.costUSD > 0)
|
|
26917
|
-
console.log(
|
|
27413
|
+
console.log(import_chalk29.default.bold(" Cost ") + import_chalk29.default.yellow("~" + fmtCost3(s.costUSD)));
|
|
26918
27414
|
console.log(
|
|
26919
|
-
|
|
27415
|
+
import_chalk29.default.bold(" Snapshot ") + (s.hasSnapshot ? import_chalk29.default.green("\u2713 taken") : import_chalk29.default.dim("none"))
|
|
26920
27416
|
);
|
|
26921
27417
|
console.log("");
|
|
26922
27418
|
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
26923
|
-
console.log(
|
|
27419
|
+
console.log(import_chalk29.default.dim(" No tool calls recorded.\n"));
|
|
26924
27420
|
return;
|
|
26925
27421
|
}
|
|
26926
27422
|
const timeline = [
|
|
@@ -26933,32 +27429,32 @@ function renderDetail(s) {
|
|
|
26933
27429
|
});
|
|
26934
27430
|
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
26935
27431
|
if (s.blockedCalls.length > 0)
|
|
26936
|
-
headerParts.push(
|
|
26937
|
-
console.log(
|
|
27432
|
+
headerParts.push(import_chalk29.default.red(`${s.blockedCalls.length} blocked by node9`));
|
|
27433
|
+
console.log(import_chalk29.default.bold(" " + headerParts.join(" \xB7 ")));
|
|
26938
27434
|
console.log("");
|
|
26939
27435
|
for (const entry of timeline) {
|
|
26940
27436
|
if (entry.kind === "tool") {
|
|
26941
27437
|
const tc = entry.tc;
|
|
26942
27438
|
const colorFn = toolColor(tc.tool);
|
|
26943
27439
|
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
26944
|
-
const detail =
|
|
26945
|
-
const ts = tc.timestamp ?
|
|
27440
|
+
const detail = import_chalk29.default.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
|
|
27441
|
+
const ts = tc.timestamp ? import_chalk29.default.dim(fmtTime(tc.timestamp) + " ") : " ";
|
|
26946
27442
|
console.log(` ${ts}${toolPad} ${detail}`);
|
|
26947
27443
|
} else {
|
|
26948
27444
|
const bc = entry.bc;
|
|
26949
|
-
const ts = bc.timestamp ?
|
|
26950
|
-
const label2 =
|
|
26951
|
-
const toolName =
|
|
26952
|
-
const argsSummary = bc.args ?
|
|
26953
|
-
const reason = bc.checkedBy ?
|
|
27445
|
+
const ts = bc.timestamp ? import_chalk29.default.dim(fmtTime(bc.timestamp) + " ") : " ";
|
|
27446
|
+
const label2 = import_chalk29.default.red("\u{1F6D1} BLOCKED".padEnd(16));
|
|
27447
|
+
const toolName = import_chalk29.default.red(bc.tool.padEnd(10));
|
|
27448
|
+
const argsSummary = bc.args ? import_chalk29.default.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : import_chalk29.default.dim("[args not logged]");
|
|
27449
|
+
const reason = bc.checkedBy ? import_chalk29.default.dim(" \u2190 " + bc.checkedBy) : "";
|
|
26954
27450
|
console.log(` ${ts}${label2} ${toolName} ${argsSummary}${reason}`);
|
|
26955
27451
|
}
|
|
26956
27452
|
}
|
|
26957
27453
|
console.log("");
|
|
26958
27454
|
if (s.modifiedFiles.length > 0) {
|
|
26959
|
-
console.log(
|
|
27455
|
+
console.log(import_chalk29.default.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
26960
27456
|
for (const f of s.modifiedFiles) {
|
|
26961
|
-
console.log(" " +
|
|
27457
|
+
console.log(" " + import_chalk29.default.yellow(f));
|
|
26962
27458
|
}
|
|
26963
27459
|
console.log("");
|
|
26964
27460
|
}
|
|
@@ -26966,13 +27462,13 @@ function renderDetail(s) {
|
|
|
26966
27462
|
function registerSessionsCommand(program2) {
|
|
26967
27463
|
program2.command("sessions").description("Show what your AI agent did \u2014 sessions, tool calls, cost, and file changes").option("--all", "Show all sessions (default: last 7 days)").option("--days <n>", "Show last N days of sessions", "7").option("--detail <sessionId>", "Show full tool trace for a session").action((options) => {
|
|
26968
27464
|
console.log("");
|
|
26969
|
-
console.log(
|
|
27465
|
+
console.log(import_chalk29.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk29.default.dim(" \u2014 what your AI agent did"));
|
|
26970
27466
|
console.log("");
|
|
26971
27467
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
26972
27468
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
26973
|
-
console.log(
|
|
27469
|
+
console.log(import_chalk29.default.dim(" " + rangeLabel));
|
|
26974
27470
|
console.log("");
|
|
26975
|
-
process.stdout.write(
|
|
27471
|
+
process.stdout.write(import_chalk29.default.dim(" Loading\u2026"));
|
|
26976
27472
|
const summaries = buildSessions(days);
|
|
26977
27473
|
if (process.stdout.isTTY) {
|
|
26978
27474
|
process.stdout.clearLine(0);
|
|
@@ -26985,8 +27481,8 @@ function registerSessionsCommand(program2) {
|
|
|
26985
27481
|
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
26986
27482
|
);
|
|
26987
27483
|
if (!target) {
|
|
26988
|
-
console.log(
|
|
26989
|
-
console.log(
|
|
27484
|
+
console.log(import_chalk29.default.red(` Session not found: ${options.detail}`));
|
|
27485
|
+
console.log(import_chalk29.default.dim(" Run `node9 sessions` to list recent sessions.\n"));
|
|
26990
27486
|
return;
|
|
26991
27487
|
}
|
|
26992
27488
|
renderDetail(target);
|
|
@@ -26999,7 +27495,7 @@ function registerSessionsCommand(program2) {
|
|
|
26999
27495
|
}
|
|
27000
27496
|
|
|
27001
27497
|
// src/cli/commands/session-taint.ts
|
|
27002
|
-
var
|
|
27498
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
27003
27499
|
init_daemon();
|
|
27004
27500
|
function resolveSessionId(records, query) {
|
|
27005
27501
|
const exact = records.find((r) => r.sessionId === query);
|
|
@@ -27026,22 +27522,22 @@ function registerSessionTaintCommand(program2) {
|
|
|
27026
27522
|
const records = await listSessionTaints();
|
|
27027
27523
|
console.log("");
|
|
27028
27524
|
if (records.length === 0) {
|
|
27029
|
-
console.log(
|
|
27030
|
-
console.log(
|
|
27525
|
+
console.log(import_chalk30.default.dim(" No tainted sessions."));
|
|
27526
|
+
console.log(import_chalk30.default.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
|
|
27031
27527
|
return;
|
|
27032
27528
|
}
|
|
27033
27529
|
console.log(
|
|
27034
|
-
" " +
|
|
27530
|
+
" " + import_chalk30.default.bold(String(records.length)) + import_chalk30.default.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
|
|
27035
27531
|
);
|
|
27036
27532
|
console.log("");
|
|
27037
27533
|
for (const r of records) {
|
|
27038
27534
|
console.log(
|
|
27039
|
-
" " +
|
|
27535
|
+
" " + import_chalk30.default.yellow(r.sessionId.slice(0, 8).padEnd(10)) + import_chalk30.default.red(r.source) + sourceGap(r.source) + import_chalk30.default.dim("clears in " + fmtRemaining(r.expiresAt))
|
|
27040
27536
|
);
|
|
27041
27537
|
}
|
|
27042
27538
|
console.log("");
|
|
27043
27539
|
console.log(
|
|
27044
|
-
|
|
27540
|
+
import_chalk30.default.dim(" Run ") + import_chalk30.default.cyan("node9 session-taint clear <id>") + import_chalk30.default.dim(" to release one, or ") + import_chalk30.default.cyan("--all") + import_chalk30.default.dim(" for every session.") + "\n"
|
|
27045
27541
|
);
|
|
27046
27542
|
});
|
|
27047
27543
|
cmd.command("clear").description("Clear a session's taint so its next network/write action isn't held for review").argument("[sessionId]", "Session id to clear (the 8-char prefix from `list` is accepted)").option("--all", "Clear every session taint").action(async (sessionId, opts) => {
|
|
@@ -27049,32 +27545,32 @@ function registerSessionTaintCommand(program2) {
|
|
|
27049
27545
|
if (opts.all) {
|
|
27050
27546
|
const res2 = await clearSessionTaint({ all: true });
|
|
27051
27547
|
if (res2.daemonUnavailable) {
|
|
27052
|
-
console.log(
|
|
27548
|
+
console.log(import_chalk30.default.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
|
|
27053
27549
|
return;
|
|
27054
27550
|
}
|
|
27055
27551
|
console.log(
|
|
27056
|
-
|
|
27552
|
+
import_chalk30.default.green(" \u2713 ") + `Cleared ${import_chalk30.default.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
|
|
27057
27553
|
`
|
|
27058
27554
|
);
|
|
27059
27555
|
return;
|
|
27060
27556
|
}
|
|
27061
27557
|
if (!sessionId) {
|
|
27062
|
-
console.log(
|
|
27063
|
-
console.log(
|
|
27558
|
+
console.log(import_chalk30.default.red(" Provide a session id or --all."));
|
|
27559
|
+
console.log(import_chalk30.default.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
|
|
27064
27560
|
return;
|
|
27065
27561
|
}
|
|
27066
27562
|
const records = await listSessionTaints();
|
|
27067
27563
|
if (records.length === 0) {
|
|
27068
|
-
console.log(
|
|
27564
|
+
console.log(import_chalk30.default.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
|
|
27069
27565
|
return;
|
|
27070
27566
|
}
|
|
27071
27567
|
const resolved = resolveSessionId(records, sessionId);
|
|
27072
27568
|
if ("error" in resolved) {
|
|
27073
27569
|
if (resolved.error === "not-found") {
|
|
27074
|
-
console.log(
|
|
27570
|
+
console.log(import_chalk30.default.red(` No tainted session matches "${sessionId}".`));
|
|
27075
27571
|
} else {
|
|
27076
|
-
console.log(
|
|
27077
|
-
for (const m of resolved.matches) console.log(
|
|
27572
|
+
console.log(import_chalk30.default.red(` "${sessionId}" is ambiguous \u2014 matches:`));
|
|
27573
|
+
for (const m of resolved.matches) console.log(import_chalk30.default.dim(" " + m));
|
|
27078
27574
|
}
|
|
27079
27575
|
console.log("");
|
|
27080
27576
|
return;
|
|
@@ -27082,24 +27578,24 @@ function registerSessionTaintCommand(program2) {
|
|
|
27082
27578
|
const res = await clearSessionTaint({ sessionId: resolved.record.sessionId });
|
|
27083
27579
|
if (res.cleared > 0) {
|
|
27084
27580
|
console.log(
|
|
27085
|
-
|
|
27581
|
+
import_chalk30.default.green(" \u2713 ") + `Cleared taint for ${import_chalk30.default.yellow(resolved.record.sessionId.slice(0, 8))} ` + import_chalk30.default.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
|
|
27086
27582
|
);
|
|
27087
27583
|
} else {
|
|
27088
27584
|
console.log(
|
|
27089
|
-
|
|
27585
|
+
import_chalk30.default.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
|
|
27090
27586
|
);
|
|
27091
27587
|
}
|
|
27092
27588
|
});
|
|
27093
27589
|
}
|
|
27094
27590
|
|
|
27095
27591
|
// src/cli/commands/skill-pin.ts
|
|
27096
|
-
var
|
|
27097
|
-
var
|
|
27098
|
-
var
|
|
27099
|
-
var
|
|
27592
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
27593
|
+
var import_fs60 = __toESM(require("fs"));
|
|
27594
|
+
var import_os50 = __toESM(require("os"));
|
|
27595
|
+
var import_path57 = __toESM(require("path"));
|
|
27100
27596
|
function wipeSkillSessions() {
|
|
27101
27597
|
try {
|
|
27102
|
-
|
|
27598
|
+
import_fs60.default.rmSync(import_path57.default.join(import_os50.default.homedir(), ".node9", "skill-sessions"), {
|
|
27103
27599
|
recursive: true,
|
|
27104
27600
|
force: true
|
|
27105
27601
|
});
|
|
@@ -27113,29 +27609,29 @@ function registerSkillPinCommand(program2) {
|
|
|
27113
27609
|
const result = readSkillPinsSafe();
|
|
27114
27610
|
if (!result.ok) {
|
|
27115
27611
|
if (result.reason === "missing") {
|
|
27116
|
-
console.log(
|
|
27612
|
+
console.log(import_chalk31.default.gray("\nNo skill roots are pinned yet."));
|
|
27117
27613
|
console.log(
|
|
27118
|
-
|
|
27614
|
+
import_chalk31.default.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
27119
27615
|
);
|
|
27120
27616
|
return;
|
|
27121
27617
|
}
|
|
27122
|
-
console.error(
|
|
27618
|
+
console.error(import_chalk31.default.red(`
|
|
27123
27619
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
27124
|
-
console.error(
|
|
27620
|
+
console.error(import_chalk31.default.yellow(" Run: node9 skill pin reset\n"));
|
|
27125
27621
|
process.exit(1);
|
|
27126
27622
|
}
|
|
27127
27623
|
const entries = Object.entries(result.pins.roots);
|
|
27128
27624
|
if (entries.length === 0) {
|
|
27129
|
-
console.log(
|
|
27625
|
+
console.log(import_chalk31.default.gray("\nNo skill roots are pinned yet.\n"));
|
|
27130
27626
|
return;
|
|
27131
27627
|
}
|
|
27132
|
-
console.log(
|
|
27628
|
+
console.log(import_chalk31.default.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
27133
27629
|
for (const [key, entry] of entries) {
|
|
27134
|
-
const missing = entry.exists ? "" :
|
|
27135
|
-
console.log(` ${
|
|
27630
|
+
const missing = entry.exists ? "" : import_chalk31.default.yellow(" (not present at pin time)");
|
|
27631
|
+
console.log(` ${import_chalk31.default.cyan(key)} ${import_chalk31.default.gray(entry.rootPath)}${missing}`);
|
|
27136
27632
|
console.log(` Files (${entry.fileCount})`);
|
|
27137
|
-
console.log(` Hash: ${
|
|
27138
|
-
console.log(` Pinned: ${
|
|
27633
|
+
console.log(` Hash: ${import_chalk31.default.gray(entry.contentHash.slice(0, 16))}...`);
|
|
27634
|
+
console.log(` Pinned: ${import_chalk31.default.gray(entry.pinnedAt)}
|
|
27139
27635
|
`);
|
|
27140
27636
|
}
|
|
27141
27637
|
});
|
|
@@ -27144,52 +27640,52 @@ function registerSkillPinCommand(program2) {
|
|
|
27144
27640
|
try {
|
|
27145
27641
|
pins = readSkillPins();
|
|
27146
27642
|
} catch {
|
|
27147
|
-
console.error(
|
|
27148
|
-
console.error(
|
|
27643
|
+
console.error(import_chalk31.default.red("\n\u274C Pin file is corrupt."));
|
|
27644
|
+
console.error(import_chalk31.default.yellow(" Run: node9 skill pin reset\n"));
|
|
27149
27645
|
process.exit(1);
|
|
27150
27646
|
}
|
|
27151
27647
|
if (!pins.roots[rootKey]) {
|
|
27152
|
-
console.error(
|
|
27648
|
+
console.error(import_chalk31.default.red(`
|
|
27153
27649
|
\u274C No pin found for root key "${rootKey}"
|
|
27154
27650
|
`));
|
|
27155
|
-
console.error(`Run ${
|
|
27651
|
+
console.error(`Run ${import_chalk31.default.cyan("node9 skill pin list")} to see pinned roots.
|
|
27156
27652
|
`);
|
|
27157
27653
|
process.exit(1);
|
|
27158
27654
|
}
|
|
27159
27655
|
const rootPath = pins.roots[rootKey].rootPath;
|
|
27160
27656
|
removePin2(rootKey);
|
|
27161
27657
|
wipeSkillSessions();
|
|
27162
|
-
console.log(
|
|
27163
|
-
\u{1F513} Pin removed for ${
|
|
27164
|
-
console.log(
|
|
27165
|
-
console.log(
|
|
27658
|
+
console.log(import_chalk31.default.green(`
|
|
27659
|
+
\u{1F513} Pin removed for ${import_chalk31.default.cyan(rootKey)}`));
|
|
27660
|
+
console.log(import_chalk31.default.gray(` ${rootPath}`));
|
|
27661
|
+
console.log(import_chalk31.default.gray(" Next session will re-pin with current state.\n"));
|
|
27166
27662
|
});
|
|
27167
27663
|
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
27168
27664
|
const result = readSkillPinsSafe();
|
|
27169
27665
|
if (!result.ok && result.reason === "missing") {
|
|
27170
27666
|
wipeSkillSessions();
|
|
27171
|
-
console.log(
|
|
27667
|
+
console.log(import_chalk31.default.gray("\nNo pins to clear.\n"));
|
|
27172
27668
|
return;
|
|
27173
27669
|
}
|
|
27174
27670
|
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
27175
27671
|
clearAllPins2();
|
|
27176
27672
|
wipeSkillSessions();
|
|
27177
|
-
console.log(
|
|
27673
|
+
console.log(import_chalk31.default.green(`
|
|
27178
27674
|
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
27179
|
-
console.log(
|
|
27675
|
+
console.log(import_chalk31.default.gray(" Next session will re-pin with current state.\n"));
|
|
27180
27676
|
});
|
|
27181
27677
|
}
|
|
27182
27678
|
|
|
27183
27679
|
// src/cli/commands/decisions.ts
|
|
27184
|
-
var
|
|
27185
|
-
var
|
|
27186
|
-
var
|
|
27187
|
-
var
|
|
27188
|
-
var DECISIONS_FILE2 =
|
|
27680
|
+
var import_fs61 = __toESM(require("fs"));
|
|
27681
|
+
var import_os51 = __toESM(require("os"));
|
|
27682
|
+
var import_path58 = __toESM(require("path"));
|
|
27683
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
27684
|
+
var DECISIONS_FILE2 = import_path58.default.join(import_os51.default.homedir(), ".node9", "decisions.json");
|
|
27189
27685
|
function readDecisions() {
|
|
27190
27686
|
try {
|
|
27191
|
-
if (!
|
|
27192
|
-
const raw =
|
|
27687
|
+
if (!import_fs61.default.existsSync(DECISIONS_FILE2)) return {};
|
|
27688
|
+
const raw = import_fs61.default.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
27193
27689
|
const parsed = JSON.parse(raw);
|
|
27194
27690
|
const out = {};
|
|
27195
27691
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -27201,11 +27697,11 @@ function readDecisions() {
|
|
|
27201
27697
|
}
|
|
27202
27698
|
}
|
|
27203
27699
|
function writeDecisions(d) {
|
|
27204
|
-
const dir =
|
|
27205
|
-
if (!
|
|
27700
|
+
const dir = import_path58.default.dirname(DECISIONS_FILE2);
|
|
27701
|
+
if (!import_fs61.default.existsSync(dir)) import_fs61.default.mkdirSync(dir, { recursive: true });
|
|
27206
27702
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
27207
|
-
|
|
27208
|
-
|
|
27703
|
+
import_fs61.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
27704
|
+
import_fs61.default.renameSync(tmp, DECISIONS_FILE2);
|
|
27209
27705
|
}
|
|
27210
27706
|
function registerDecisionsCommand(program2) {
|
|
27211
27707
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -27213,67 +27709,67 @@ function registerDecisionsCommand(program2) {
|
|
|
27213
27709
|
const decisions = readDecisions();
|
|
27214
27710
|
const entries = Object.entries(decisions);
|
|
27215
27711
|
if (entries.length === 0) {
|
|
27216
|
-
console.log(
|
|
27712
|
+
console.log(import_chalk32.default.gray(" No persistent decisions stored."));
|
|
27217
27713
|
console.log(
|
|
27218
|
-
|
|
27219
|
-
`) +
|
|
27714
|
+
import_chalk32.default.gray(` File: ${DECISIONS_FILE2}
|
|
27715
|
+
`) + import_chalk32.default.gray(' Decisions are written when you click "Always Allow" or')
|
|
27220
27716
|
);
|
|
27221
|
-
console.log(
|
|
27717
|
+
console.log(import_chalk32.default.gray(' "Always Deny" in node9 tail or the native popup.'));
|
|
27222
27718
|
return;
|
|
27223
27719
|
}
|
|
27224
|
-
console.log(
|
|
27720
|
+
console.log(import_chalk32.default.bold(`
|
|
27225
27721
|
Persistent decisions (${entries.length})
|
|
27226
27722
|
`));
|
|
27227
27723
|
const w = Math.max(...entries.map(([k]) => k.length));
|
|
27228
27724
|
for (const [tool, verdict] of entries.sort()) {
|
|
27229
|
-
const colored = verdict === "allow" ?
|
|
27725
|
+
const colored = verdict === "allow" ? import_chalk32.default.green(verdict) : import_chalk32.default.red(verdict);
|
|
27230
27726
|
console.log(` ${tool.padEnd(w)} ${colored}`);
|
|
27231
27727
|
}
|
|
27232
27728
|
console.log(
|
|
27233
|
-
|
|
27729
|
+
import_chalk32.default.gray(`
|
|
27234
27730
|
Stored in ${DECISIONS_FILE2}
|
|
27235
|
-
`) +
|
|
27731
|
+
`) + import_chalk32.default.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
|
|
27236
27732
|
);
|
|
27237
27733
|
});
|
|
27238
27734
|
cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
|
|
27239
27735
|
const decisions = readDecisions();
|
|
27240
27736
|
if (!(toolName in decisions)) {
|
|
27241
|
-
console.log(
|
|
27737
|
+
console.log(import_chalk32.default.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
|
|
27242
27738
|
process.exitCode = 1;
|
|
27243
27739
|
return;
|
|
27244
27740
|
}
|
|
27245
27741
|
delete decisions[toolName];
|
|
27246
27742
|
writeDecisions(decisions);
|
|
27247
|
-
console.log(
|
|
27743
|
+
console.log(import_chalk32.default.green(` \u2713 Cleared persistent decision for "${toolName}".`));
|
|
27248
27744
|
});
|
|
27249
27745
|
cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
|
|
27250
27746
|
const decisions = readDecisions();
|
|
27251
27747
|
const count = Object.keys(decisions).length;
|
|
27252
27748
|
if (count === 0) {
|
|
27253
|
-
console.log(
|
|
27749
|
+
console.log(import_chalk32.default.gray(" Nothing to clear \u2014 no persistent decisions stored."));
|
|
27254
27750
|
return;
|
|
27255
27751
|
}
|
|
27256
27752
|
writeDecisions({});
|
|
27257
27753
|
console.log(
|
|
27258
|
-
|
|
27754
|
+
import_chalk32.default.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
|
|
27259
27755
|
);
|
|
27260
27756
|
});
|
|
27261
27757
|
}
|
|
27262
27758
|
|
|
27263
27759
|
// src/cli/commands/dlp.ts
|
|
27264
|
-
var
|
|
27265
|
-
var
|
|
27266
|
-
var
|
|
27267
|
-
var
|
|
27268
|
-
var AUDIT_LOG =
|
|
27269
|
-
var RESOLVED_FILE =
|
|
27760
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
27761
|
+
var import_fs62 = __toESM(require("fs"));
|
|
27762
|
+
var import_path59 = __toESM(require("path"));
|
|
27763
|
+
var import_os52 = __toESM(require("os"));
|
|
27764
|
+
var AUDIT_LOG = import_path59.default.join(import_os52.default.homedir(), ".node9", "audit.log");
|
|
27765
|
+
var RESOLVED_FILE = import_path59.default.join(import_os52.default.homedir(), ".node9", "dlp-resolved.json");
|
|
27270
27766
|
var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
|
|
27271
27767
|
function stripAnsi(s) {
|
|
27272
27768
|
return s.replace(ANSI_RE, "");
|
|
27273
27769
|
}
|
|
27274
27770
|
function loadResolved() {
|
|
27275
27771
|
try {
|
|
27276
|
-
const raw = JSON.parse(
|
|
27772
|
+
const raw = JSON.parse(import_fs62.default.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
27277
27773
|
return new Set(raw);
|
|
27278
27774
|
} catch {
|
|
27279
27775
|
return /* @__PURE__ */ new Set();
|
|
@@ -27281,13 +27777,13 @@ function loadResolved() {
|
|
|
27281
27777
|
}
|
|
27282
27778
|
function saveResolved(resolved) {
|
|
27283
27779
|
try {
|
|
27284
|
-
|
|
27780
|
+
import_fs62.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
27285
27781
|
} catch {
|
|
27286
27782
|
}
|
|
27287
27783
|
}
|
|
27288
27784
|
function loadDlpFindings() {
|
|
27289
|
-
if (!
|
|
27290
|
-
return
|
|
27785
|
+
if (!import_fs62.default.existsSync(AUDIT_LOG)) return [];
|
|
27786
|
+
return import_fs62.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
27291
27787
|
if (!line.trim()) return [];
|
|
27292
27788
|
try {
|
|
27293
27789
|
const e = JSON.parse(line);
|
|
@@ -27316,14 +27812,14 @@ function registerDlpCommand(program2) {
|
|
|
27316
27812
|
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
27317
27813
|
const findings = loadDlpFindings();
|
|
27318
27814
|
if (findings.length === 0) {
|
|
27319
|
-
console.log(
|
|
27815
|
+
console.log(import_chalk33.default.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
27320
27816
|
return;
|
|
27321
27817
|
}
|
|
27322
27818
|
const resolved = loadResolved();
|
|
27323
27819
|
for (const e of findings) resolved.add(entryKey2(e));
|
|
27324
27820
|
saveResolved(resolved);
|
|
27325
27821
|
console.log(
|
|
27326
|
-
|
|
27822
|
+
import_chalk33.default.green(
|
|
27327
27823
|
`
|
|
27328
27824
|
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
27329
27825
|
`
|
|
@@ -27337,63 +27833,63 @@ function registerDlpCommand(program2) {
|
|
|
27337
27833
|
const resolvedCount = findings.length - open.length;
|
|
27338
27834
|
console.log("");
|
|
27339
27835
|
console.log(
|
|
27340
|
-
|
|
27836
|
+
import_chalk33.default.bold.cyan("\u{1F510} node9 dlp") + import_chalk33.default.dim(" \u2014 secrets found in Claude response text")
|
|
27341
27837
|
);
|
|
27342
27838
|
console.log("");
|
|
27343
27839
|
if (open.length === 0) {
|
|
27344
27840
|
if (resolvedCount > 0) {
|
|
27345
|
-
console.log(
|
|
27841
|
+
console.log(import_chalk33.default.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
27346
27842
|
} else {
|
|
27347
27843
|
console.log(
|
|
27348
|
-
|
|
27844
|
+
import_chalk33.default.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
27349
27845
|
);
|
|
27350
27846
|
}
|
|
27351
27847
|
console.log("");
|
|
27352
27848
|
return;
|
|
27353
27849
|
}
|
|
27354
27850
|
console.log(
|
|
27355
|
-
|
|
27851
|
+
import_chalk33.default.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + import_chalk33.default.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
27356
27852
|
);
|
|
27357
27853
|
console.log("");
|
|
27358
27854
|
console.log(
|
|
27359
|
-
|
|
27855
|
+
import_chalk33.default.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
27360
27856
|
);
|
|
27361
|
-
console.log(
|
|
27857
|
+
console.log(import_chalk33.default.dim(" Rotate each affected key immediately.\n"));
|
|
27362
27858
|
for (const e of open) {
|
|
27363
27859
|
console.log(
|
|
27364
|
-
" " +
|
|
27860
|
+
" " + import_chalk33.default.red("\u25CF") + " " + import_chalk33.default.white(e.dlpPattern ?? "Secret") + import_chalk33.default.dim(" " + fmtDate3(e.ts))
|
|
27365
27861
|
);
|
|
27366
27862
|
if (e.dlpSample) {
|
|
27367
|
-
console.log(" " +
|
|
27863
|
+
console.log(" " + import_chalk33.default.dim("Sample: ") + import_chalk33.default.yellow(stripAnsi(e.dlpSample)));
|
|
27368
27864
|
}
|
|
27369
27865
|
if (e.project) {
|
|
27370
|
-
console.log(" " +
|
|
27866
|
+
console.log(" " + import_chalk33.default.dim("Project: ") + import_chalk33.default.dim(stripAnsi(e.project)));
|
|
27371
27867
|
}
|
|
27372
27868
|
console.log("");
|
|
27373
27869
|
}
|
|
27374
|
-
console.log(" " +
|
|
27375
|
-
console.log(" " +
|
|
27870
|
+
console.log(" " + import_chalk33.default.bold("Next steps:"));
|
|
27871
|
+
console.log(" " + import_chalk33.default.cyan("1.") + " Rotate any exposed keys shown above");
|
|
27376
27872
|
console.log(
|
|
27377
|
-
" " +
|
|
27873
|
+
" " + import_chalk33.default.cyan("2.") + " Run " + import_chalk33.default.white("node9 dlp resolve") + " to acknowledge"
|
|
27378
27874
|
);
|
|
27379
27875
|
console.log(
|
|
27380
|
-
" " +
|
|
27876
|
+
" " + import_chalk33.default.cyan("3.") + " Run " + import_chalk33.default.white("node9 report") + " for full audit history"
|
|
27381
27877
|
);
|
|
27382
27878
|
console.log("");
|
|
27383
27879
|
});
|
|
27384
27880
|
}
|
|
27385
27881
|
|
|
27386
27882
|
// src/cli/commands/mask.ts
|
|
27387
|
-
var
|
|
27388
|
-
var
|
|
27389
|
-
var
|
|
27390
|
-
var
|
|
27883
|
+
var import_chalk34 = __toESM(require("chalk"));
|
|
27884
|
+
var import_fs63 = __toESM(require("fs"));
|
|
27885
|
+
var import_path60 = __toESM(require("path"));
|
|
27886
|
+
var import_os53 = __toESM(require("os"));
|
|
27391
27887
|
init_dlp();
|
|
27392
27888
|
function findJsonlFiles(dir) {
|
|
27393
27889
|
const results = [];
|
|
27394
|
-
if (!
|
|
27395
|
-
for (const entry of
|
|
27396
|
-
const full =
|
|
27890
|
+
if (!import_fs63.default.existsSync(dir)) return results;
|
|
27891
|
+
for (const entry of import_fs63.default.readdirSync(dir, { withFileTypes: true })) {
|
|
27892
|
+
const full = import_path60.default.join(dir, entry.name);
|
|
27397
27893
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
27398
27894
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
27399
27895
|
}
|
|
@@ -27436,7 +27932,7 @@ function redactJson(obj) {
|
|
|
27436
27932
|
function processFile(filePath, dryRun) {
|
|
27437
27933
|
let raw;
|
|
27438
27934
|
try {
|
|
27439
|
-
raw =
|
|
27935
|
+
raw = import_fs63.default.readFileSync(filePath, "utf-8");
|
|
27440
27936
|
} catch {
|
|
27441
27937
|
return { redactedLines: 0, patterns: [] };
|
|
27442
27938
|
}
|
|
@@ -27468,14 +27964,14 @@ function processFile(filePath, dryRun) {
|
|
|
27468
27964
|
}
|
|
27469
27965
|
}
|
|
27470
27966
|
if (!dryRun && redactedLines > 0) {
|
|
27471
|
-
|
|
27967
|
+
import_fs63.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
27472
27968
|
}
|
|
27473
27969
|
return { redactedLines, patterns };
|
|
27474
27970
|
}
|
|
27475
27971
|
function processJsonFile(filePath, dryRun) {
|
|
27476
27972
|
let raw;
|
|
27477
27973
|
try {
|
|
27478
|
-
raw =
|
|
27974
|
+
raw = import_fs63.default.readFileSync(filePath, "utf-8");
|
|
27479
27975
|
} catch {
|
|
27480
27976
|
return { redactedLines: 0, patterns: [] };
|
|
27481
27977
|
}
|
|
@@ -27488,15 +27984,15 @@ function processJsonFile(filePath, dryRun) {
|
|
|
27488
27984
|
const { value, modified, found } = redactJson(parsed);
|
|
27489
27985
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
27490
27986
|
if (!dryRun) {
|
|
27491
|
-
|
|
27987
|
+
import_fs63.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
27492
27988
|
}
|
|
27493
27989
|
return { redactedLines: 1, patterns: found };
|
|
27494
27990
|
}
|
|
27495
27991
|
function findJsonFiles(dir) {
|
|
27496
27992
|
const results = [];
|
|
27497
|
-
if (!
|
|
27498
|
-
for (const entry of
|
|
27499
|
-
const full =
|
|
27993
|
+
if (!import_fs63.default.existsSync(dir)) return results;
|
|
27994
|
+
for (const entry of import_fs63.default.readdirSync(dir, { withFileTypes: true })) {
|
|
27995
|
+
const full = import_path60.default.join(dir, entry.name);
|
|
27500
27996
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
27501
27997
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
27502
27998
|
}
|
|
@@ -27505,9 +28001,9 @@ function findJsonFiles(dir) {
|
|
|
27505
28001
|
function registerMaskCommand(program2) {
|
|
27506
28002
|
program2.command("mask").description("Redact plaintext secrets from local AI session history files").option("--dry-run", "show what would be redacted without making changes").option("--all", "scan all history (default: last 30 days)").action(async (options) => {
|
|
27507
28003
|
const dryRun = !!options.dryRun;
|
|
27508
|
-
const home =
|
|
27509
|
-
const claudeDir =
|
|
27510
|
-
const geminiDir =
|
|
28004
|
+
const home = import_os53.default.homedir();
|
|
28005
|
+
const claudeDir = import_path60.default.join(home, ".claude", "projects");
|
|
28006
|
+
const geminiDir = import_path60.default.join(home, ".gemini", "tmp");
|
|
27511
28007
|
const allFiles = [
|
|
27512
28008
|
...findJsonlFiles(claudeDir).map((p) => ({ path: p, type: "jsonl" })),
|
|
27513
28009
|
...findJsonFiles(geminiDir).map((p) => ({ path: p, type: "json" }))
|
|
@@ -27515,18 +28011,18 @@ function registerMaskCommand(program2) {
|
|
|
27515
28011
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
27516
28012
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
27517
28013
|
try {
|
|
27518
|
-
return
|
|
28014
|
+
return import_fs63.default.statSync(f.path).mtime >= cutoff;
|
|
27519
28015
|
} catch {
|
|
27520
28016
|
return false;
|
|
27521
28017
|
}
|
|
27522
28018
|
}) : allFiles;
|
|
27523
28019
|
if (filtered.length === 0) {
|
|
27524
|
-
console.log(
|
|
28020
|
+
console.log(import_chalk34.default.yellow(" No session files found."));
|
|
27525
28021
|
return;
|
|
27526
28022
|
}
|
|
27527
28023
|
console.log("");
|
|
27528
28024
|
if (dryRun) {
|
|
27529
|
-
console.log(
|
|
28025
|
+
console.log(import_chalk34.default.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
27530
28026
|
}
|
|
27531
28027
|
let totalFiles = 0;
|
|
27532
28028
|
let totalLines = 0;
|
|
@@ -27542,23 +28038,23 @@ function registerMaskCommand(program2) {
|
|
|
27542
28038
|
});
|
|
27543
28039
|
const verb = dryRun ? "Would redact" : "Redacted";
|
|
27544
28040
|
console.log(
|
|
27545
|
-
" " +
|
|
28041
|
+
" " + import_chalk34.default.dim(shortPath.slice(0, 60).padEnd(62)) + import_chalk34.default.red(`${verb}: `) + import_chalk34.default.yellow(patterns.join(", ")) + import_chalk34.default.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
27546
28042
|
);
|
|
27547
28043
|
}
|
|
27548
28044
|
}
|
|
27549
28045
|
console.log("");
|
|
27550
28046
|
if (totalFiles === 0) {
|
|
27551
|
-
console.log(
|
|
28047
|
+
console.log(import_chalk34.default.green(" No secrets found in session history."));
|
|
27552
28048
|
} else {
|
|
27553
28049
|
const verb = dryRun ? "would be modified" : "modified";
|
|
27554
28050
|
console.log(
|
|
27555
|
-
|
|
28051
|
+
import_chalk34.default.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + import_chalk34.default.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
27556
28052
|
);
|
|
27557
|
-
console.log(" Patterns: " +
|
|
28053
|
+
console.log(" Patterns: " + import_chalk34.default.yellow(totalPatterns.join(", ")));
|
|
27558
28054
|
if (!dryRun) {
|
|
27559
28055
|
console.log("");
|
|
27560
28056
|
console.log(
|
|
27561
|
-
|
|
28057
|
+
import_chalk34.default.dim(
|
|
27562
28058
|
" Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
|
|
27563
28059
|
)
|
|
27564
28060
|
);
|
|
@@ -27571,20 +28067,20 @@ function registerMaskCommand(program2) {
|
|
|
27571
28067
|
// src/cli.ts
|
|
27572
28068
|
init_blast();
|
|
27573
28069
|
var { version } = JSON.parse(
|
|
27574
|
-
|
|
28070
|
+
import_fs66.default.readFileSync(import_path63.default.join(__dirname, "../package.json"), "utf-8")
|
|
27575
28071
|
);
|
|
27576
28072
|
var program = new import_commander.Command();
|
|
27577
28073
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
27578
28074
|
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) => {
|
|
27579
28075
|
const DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept";
|
|
27580
|
-
const credPath =
|
|
27581
|
-
if (!
|
|
27582
|
-
|
|
28076
|
+
const credPath = import_path63.default.join(import_os56.default.homedir(), ".node9", "credentials.json");
|
|
28077
|
+
if (!import_fs66.default.existsSync(import_path63.default.dirname(credPath)))
|
|
28078
|
+
import_fs66.default.mkdirSync(import_path63.default.dirname(credPath), { recursive: true });
|
|
27583
28079
|
const profileName = options.profile || "default";
|
|
27584
28080
|
let existingCreds = {};
|
|
27585
28081
|
try {
|
|
27586
|
-
if (
|
|
27587
|
-
const raw = JSON.parse(
|
|
28082
|
+
if (import_fs66.default.existsSync(credPath)) {
|
|
28083
|
+
const raw = JSON.parse(import_fs66.default.readFileSync(credPath, "utf-8"));
|
|
27588
28084
|
if (raw.apiKey) {
|
|
27589
28085
|
existingCreds = {
|
|
27590
28086
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL2 }
|
|
@@ -27596,14 +28092,14 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
27596
28092
|
} catch {
|
|
27597
28093
|
}
|
|
27598
28094
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL2 };
|
|
27599
|
-
|
|
28095
|
+
import_fs66.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
27600
28096
|
let effectiveCloud = null;
|
|
27601
28097
|
if (profileName === "default") {
|
|
27602
|
-
const
|
|
28098
|
+
const configPath = import_path63.default.join(import_os56.default.homedir(), ".node9", "config.json");
|
|
27603
28099
|
let config = {};
|
|
27604
28100
|
try {
|
|
27605
|
-
if (
|
|
27606
|
-
config = JSON.parse(
|
|
28101
|
+
if (import_fs66.default.existsSync(configPath))
|
|
28102
|
+
config = JSON.parse(import_fs66.default.readFileSync(configPath, "utf-8"));
|
|
27607
28103
|
} catch {
|
|
27608
28104
|
}
|
|
27609
28105
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -27618,35 +28114,35 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
27618
28114
|
approvers.cloud = false;
|
|
27619
28115
|
}
|
|
27620
28116
|
s.approvers = approvers;
|
|
27621
|
-
if (!
|
|
27622
|
-
|
|
27623
|
-
|
|
28117
|
+
if (!import_fs66.default.existsSync(import_path63.default.dirname(configPath)))
|
|
28118
|
+
import_fs66.default.mkdirSync(import_path63.default.dirname(configPath), { recursive: true });
|
|
28119
|
+
import_fs66.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
27624
28120
|
effectiveCloud = approvers.cloud === true;
|
|
27625
28121
|
}
|
|
27626
28122
|
if (options.profile && profileName !== "default") {
|
|
27627
|
-
console.log(
|
|
27628
|
-
console.log(
|
|
28123
|
+
console.log(import_chalk36.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
28124
|
+
console.log(import_chalk36.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
27629
28125
|
} else if (options.local || effectiveCloud === false) {
|
|
27630
|
-
console.log(
|
|
27631
|
-
console.log(
|
|
28126
|
+
console.log(import_chalk36.default.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
|
|
28127
|
+
console.log(import_chalk36.default.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
|
|
27632
28128
|
if (!options.local) {
|
|
27633
28129
|
console.log(
|
|
27634
|
-
|
|
28130
|
+
import_chalk36.default.yellow(` Your config has cloud approvals OFF (settings.approvers.cloud).`)
|
|
27635
28131
|
);
|
|
27636
28132
|
console.log(
|
|
27637
|
-
|
|
28133
|
+
import_chalk36.default.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
|
|
27638
28134
|
);
|
|
27639
28135
|
}
|
|
27640
28136
|
} else {
|
|
27641
|
-
console.log(
|
|
27642
|
-
console.log(
|
|
28137
|
+
console.log(import_chalk36.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
28138
|
+
console.log(import_chalk36.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
27643
28139
|
}
|
|
27644
28140
|
});
|
|
27645
28141
|
program.command("signup").description("Create your node9 account / open the dashboard in your browser").option("--login", "Open the login page instead of signup").action((options) => {
|
|
27646
28142
|
const route = options.login ? "auth/login" : "auth/signup";
|
|
27647
28143
|
const url = `https://node9.ai/${route}?ref=cli_cmd`;
|
|
27648
28144
|
console.log("");
|
|
27649
|
-
console.log(" " +
|
|
28145
|
+
console.log(" " + import_chalk36.default.dim("Opening ") + import_chalk36.default.cyan.underline(url));
|
|
27650
28146
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
27651
28147
|
try {
|
|
27652
28148
|
const child = (0, import_child_process15.spawn)(opener, [url], {
|
|
@@ -27679,7 +28175,7 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
|
|
|
27679
28175
|
if (target === "hermes") return setupHermes();
|
|
27680
28176
|
if (target === "hud") return setupHud();
|
|
27681
28177
|
console.error(
|
|
27682
|
-
|
|
28178
|
+
import_chalk36.default.red(
|
|
27683
28179
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
27684
28180
|
)
|
|
27685
28181
|
);
|
|
@@ -27693,20 +28189,20 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
27693
28189
|
"The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
|
|
27694
28190
|
).action(async (target) => {
|
|
27695
28191
|
if (!target) {
|
|
27696
|
-
console.log(
|
|
27697
|
-
console.log(" Usage: " +
|
|
28192
|
+
console.log(import_chalk36.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
28193
|
+
console.log(" Usage: " + import_chalk36.default.white("node9 setup <target>") + "\n");
|
|
27698
28194
|
console.log(" Targets:");
|
|
27699
|
-
console.log(" " +
|
|
27700
|
-
console.log(" " +
|
|
27701
|
-
console.log(" " +
|
|
27702
|
-
console.log(" " +
|
|
27703
|
-
console.log(" " +
|
|
27704
|
-
console.log(" " +
|
|
27705
|
-
console.log(" " +
|
|
27706
|
-
console.log(" " +
|
|
27707
|
-
console.log(" " +
|
|
28195
|
+
console.log(" " + import_chalk36.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
28196
|
+
console.log(" " + import_chalk36.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
28197
|
+
console.log(" " + import_chalk36.default.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
|
|
28198
|
+
console.log(" " + import_chalk36.default.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
|
|
28199
|
+
console.log(" " + import_chalk36.default.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
28200
|
+
console.log(" " + import_chalk36.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
28201
|
+
console.log(" " + import_chalk36.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
28202
|
+
console.log(" " + import_chalk36.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
28203
|
+
console.log(" " + import_chalk36.default.green("hermes") + " \u2014 Hermes Agent (hook mode)");
|
|
27708
28204
|
process.stdout.write(
|
|
27709
|
-
" " +
|
|
28205
|
+
" " + import_chalk36.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
27710
28206
|
);
|
|
27711
28207
|
console.log("");
|
|
27712
28208
|
return;
|
|
@@ -27723,7 +28219,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
27723
28219
|
if (t === "hermes") return setupHermes();
|
|
27724
28220
|
if (t === "hud") return setupHud();
|
|
27725
28221
|
console.error(
|
|
27726
|
-
|
|
28222
|
+
import_chalk36.default.red(
|
|
27727
28223
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
27728
28224
|
)
|
|
27729
28225
|
);
|
|
@@ -27749,33 +28245,33 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
|
|
|
27749
28245
|
else if (target === "hud") fn = teardownHud;
|
|
27750
28246
|
else {
|
|
27751
28247
|
console.error(
|
|
27752
|
-
|
|
28248
|
+
import_chalk36.default.red(
|
|
27753
28249
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
27754
28250
|
)
|
|
27755
28251
|
);
|
|
27756
28252
|
process.exit(1);
|
|
27757
28253
|
}
|
|
27758
|
-
console.log(
|
|
28254
|
+
console.log(import_chalk36.default.cyan(`
|
|
27759
28255
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
27760
28256
|
`));
|
|
27761
28257
|
try {
|
|
27762
28258
|
fn();
|
|
27763
28259
|
} catch (err2) {
|
|
27764
|
-
console.error(
|
|
28260
|
+
console.error(import_chalk36.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
27765
28261
|
process.exit(1);
|
|
27766
28262
|
}
|
|
27767
|
-
console.log(
|
|
28263
|
+
console.log(import_chalk36.default.gray("\n Restart the agent for changes to take effect."));
|
|
27768
28264
|
});
|
|
27769
28265
|
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) => {
|
|
27770
|
-
console.log(
|
|
27771
|
-
console.log(
|
|
28266
|
+
console.log(import_chalk36.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
28267
|
+
console.log(import_chalk36.default.bold("Stopping daemon..."));
|
|
27772
28268
|
try {
|
|
27773
28269
|
stopDaemon();
|
|
27774
|
-
console.log(
|
|
28270
|
+
console.log(import_chalk36.default.green(" \u2705 Daemon stopped"));
|
|
27775
28271
|
} catch {
|
|
27776
|
-
console.log(
|
|
28272
|
+
console.log(import_chalk36.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
27777
28273
|
}
|
|
27778
|
-
console.log(
|
|
28274
|
+
console.log(import_chalk36.default.bold("\nRemoving hooks..."));
|
|
27779
28275
|
let teardownFailed = false;
|
|
27780
28276
|
for (const [label2, fn] of [
|
|
27781
28277
|
["Claude", teardownClaude],
|
|
@@ -27791,45 +28287,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
27791
28287
|
} catch (err2) {
|
|
27792
28288
|
teardownFailed = true;
|
|
27793
28289
|
console.error(
|
|
27794
|
-
|
|
28290
|
+
import_chalk36.default.red(
|
|
27795
28291
|
` \u26A0\uFE0F Failed to remove ${label2} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
27796
28292
|
)
|
|
27797
28293
|
);
|
|
27798
28294
|
}
|
|
27799
28295
|
}
|
|
27800
28296
|
if (options.purge) {
|
|
27801
|
-
const node9Dir =
|
|
27802
|
-
if (
|
|
28297
|
+
const node9Dir = import_path63.default.join(import_os56.default.homedir(), ".node9");
|
|
28298
|
+
if (import_fs66.default.existsSync(node9Dir)) {
|
|
27803
28299
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
27804
28300
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
27805
28301
|
default: false
|
|
27806
28302
|
});
|
|
27807
28303
|
if (confirmed) {
|
|
27808
|
-
|
|
27809
|
-
if (
|
|
28304
|
+
import_fs66.default.rmSync(node9Dir, { recursive: true });
|
|
28305
|
+
if (import_fs66.default.existsSync(node9Dir)) {
|
|
27810
28306
|
console.error(
|
|
27811
|
-
|
|
28307
|
+
import_chalk36.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
27812
28308
|
);
|
|
27813
28309
|
} else {
|
|
27814
|
-
console.log(
|
|
28310
|
+
console.log(import_chalk36.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
27815
28311
|
}
|
|
27816
28312
|
} else {
|
|
27817
|
-
console.log(
|
|
28313
|
+
console.log(import_chalk36.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
27818
28314
|
}
|
|
27819
28315
|
} else {
|
|
27820
|
-
console.log(
|
|
28316
|
+
console.log(import_chalk36.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
27821
28317
|
}
|
|
27822
28318
|
} else {
|
|
27823
28319
|
console.log(
|
|
27824
|
-
|
|
28320
|
+
import_chalk36.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
27825
28321
|
);
|
|
27826
28322
|
}
|
|
27827
28323
|
if (teardownFailed) {
|
|
27828
|
-
console.error(
|
|
28324
|
+
console.error(import_chalk36.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
27829
28325
|
process.exit(1);
|
|
27830
28326
|
}
|
|
27831
|
-
console.log(
|
|
27832
|
-
console.log(
|
|
28327
|
+
console.log(import_chalk36.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
|
|
28328
|
+
console.log(import_chalk36.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
27833
28329
|
});
|
|
27834
28330
|
registerDoctorCommand(program, version);
|
|
27835
28331
|
program.command("explain").description(
|
|
@@ -27842,7 +28338,7 @@ program.command("explain").description(
|
|
|
27842
28338
|
try {
|
|
27843
28339
|
args = JSON.parse(trimmed);
|
|
27844
28340
|
} catch {
|
|
27845
|
-
console.error(
|
|
28341
|
+
console.error(import_chalk36.default.red(`
|
|
27846
28342
|
\u274C Invalid JSON: ${trimmed}
|
|
27847
28343
|
`));
|
|
27848
28344
|
process.exit(1);
|
|
@@ -27853,54 +28349,62 @@ program.command("explain").description(
|
|
|
27853
28349
|
}
|
|
27854
28350
|
const result = await explainPolicy(tool, args);
|
|
27855
28351
|
console.log("");
|
|
27856
|
-
console.log(
|
|
28352
|
+
console.log(import_chalk36.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
27857
28353
|
console.log("");
|
|
27858
|
-
console.log(` ${
|
|
28354
|
+
console.log(` ${import_chalk36.default.bold("Tool:")} ${import_chalk36.default.white(result.tool)}`);
|
|
27859
28355
|
if (argsRaw) {
|
|
27860
28356
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
27861
|
-
console.log(` ${
|
|
28357
|
+
console.log(` ${import_chalk36.default.bold("Input:")} ${import_chalk36.default.gray(preview2)}`);
|
|
27862
28358
|
}
|
|
27863
28359
|
console.log("");
|
|
27864
|
-
console.log(
|
|
28360
|
+
console.log(import_chalk36.default.bold("Config Sources (Waterfall):"));
|
|
27865
28361
|
for (const tier of result.waterfall) {
|
|
27866
|
-
const num3 =
|
|
28362
|
+
const num3 = import_chalk36.default.gray(` ${tier.tier}.`);
|
|
27867
28363
|
const label2 = tier.label.padEnd(16);
|
|
27868
28364
|
let statusStr;
|
|
27869
28365
|
if (tier.tier === 1) {
|
|
27870
|
-
statusStr =
|
|
28366
|
+
statusStr = import_chalk36.default.gray(tier.note ?? "");
|
|
27871
28367
|
} else if (tier.status === "active") {
|
|
27872
|
-
const loc = tier.path ?
|
|
27873
|
-
const note = tier.note ?
|
|
27874
|
-
statusStr =
|
|
28368
|
+
const loc = tier.path ? import_chalk36.default.gray(tier.path) : "";
|
|
28369
|
+
const note = tier.note ? import_chalk36.default.gray(`(${tier.note})`) : "";
|
|
28370
|
+
statusStr = import_chalk36.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
27875
28371
|
} else {
|
|
27876
|
-
statusStr =
|
|
28372
|
+
statusStr = import_chalk36.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
27877
28373
|
}
|
|
27878
|
-
console.log(`${num3} ${
|
|
28374
|
+
console.log(`${num3} ${import_chalk36.default.white(label2)} ${statusStr}`);
|
|
27879
28375
|
}
|
|
27880
28376
|
console.log("");
|
|
27881
|
-
console.log(
|
|
28377
|
+
console.log(import_chalk36.default.bold("Policy Evaluation:"));
|
|
27882
28378
|
for (const step of result.steps) {
|
|
27883
28379
|
const isFinal = step.isFinal;
|
|
27884
28380
|
let icon;
|
|
27885
|
-
if (step.outcome === "allow") icon =
|
|
27886
|
-
else if (step.outcome === "
|
|
27887
|
-
else if (step.outcome === "
|
|
27888
|
-
else icon =
|
|
28381
|
+
if (step.outcome === "allow") icon = import_chalk36.default.green(" \u2705");
|
|
28382
|
+
else if (step.outcome === "block") icon = import_chalk36.default.red(" \u{1F6D1}");
|
|
28383
|
+
else if (step.outcome === "review") icon = import_chalk36.default.red(" \u{1F534}");
|
|
28384
|
+
else if (step.outcome === "skip") icon = import_chalk36.default.gray(" \u2500 ");
|
|
28385
|
+
else icon = import_chalk36.default.gray(" \u25CB ");
|
|
27889
28386
|
const name = step.name.padEnd(18);
|
|
27890
|
-
const nameStr = isFinal ?
|
|
27891
|
-
const detail = isFinal ?
|
|
27892
|
-
const arrow = isFinal ?
|
|
28387
|
+
const nameStr = isFinal ? import_chalk36.default.white.bold(name) : import_chalk36.default.white(name);
|
|
28388
|
+
const detail = isFinal ? import_chalk36.default.white(step.detail) : import_chalk36.default.gray(step.detail);
|
|
28389
|
+
const arrow = isFinal ? import_chalk36.default.yellow(" \u2190 STOP") : "";
|
|
27893
28390
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
27894
28391
|
}
|
|
27895
28392
|
console.log("");
|
|
27896
28393
|
if (result.decision === "allow") {
|
|
27897
|
-
console.log(
|
|
28394
|
+
console.log(import_chalk36.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk36.default.gray(" \u2014 no approval needed"));
|
|
28395
|
+
} else if (result.decision === "block") {
|
|
28396
|
+
console.log(
|
|
28397
|
+
import_chalk36.default.red.bold(" Decision: \u{1F6D1} BLOCK") + import_chalk36.default.gray(" \u2014 this action is blocked")
|
|
28398
|
+
);
|
|
28399
|
+
if (result.blockedByLabel) {
|
|
28400
|
+
console.log(import_chalk36.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
28401
|
+
}
|
|
27898
28402
|
} else {
|
|
27899
28403
|
console.log(
|
|
27900
|
-
|
|
28404
|
+
import_chalk36.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk36.default.gray(" \u2014 human approval required")
|
|
27901
28405
|
);
|
|
27902
28406
|
if (result.blockedByLabel) {
|
|
27903
|
-
console.log(
|
|
28407
|
+
console.log(import_chalk36.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
27904
28408
|
}
|
|
27905
28409
|
}
|
|
27906
28410
|
console.log("");
|
|
@@ -27915,18 +28419,18 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
27915
28419
|
try {
|
|
27916
28420
|
await startTail2(options);
|
|
27917
28421
|
} catch (err2) {
|
|
27918
|
-
console.error(
|
|
28422
|
+
console.error(import_chalk36.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
27919
28423
|
process.exit(1);
|
|
27920
28424
|
}
|
|
27921
28425
|
});
|
|
27922
28426
|
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
27923
28427
|
try {
|
|
27924
|
-
const dashboardPath =
|
|
28428
|
+
const dashboardPath = import_path63.default.join(__dirname, "dashboard.mjs");
|
|
27925
28429
|
const dynamicImport = new Function("id", "return import(id)");
|
|
27926
28430
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
27927
28431
|
await mod.startMonitor();
|
|
27928
28432
|
} catch (err2) {
|
|
27929
|
-
console.error(
|
|
28433
|
+
console.error(import_chalk36.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
27930
28434
|
process.exit(1);
|
|
27931
28435
|
}
|
|
27932
28436
|
});
|
|
@@ -27959,14 +28463,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
27959
28463
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
27960
28464
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
27961
28465
|
if (subcommand === "debug") {
|
|
27962
|
-
const flagFile =
|
|
28466
|
+
const flagFile = import_path63.default.join(import_os56.default.homedir(), ".node9", "hud-debug");
|
|
27963
28467
|
if (state === "on") {
|
|
27964
|
-
|
|
27965
|
-
|
|
28468
|
+
import_fs66.default.mkdirSync(import_path63.default.dirname(flagFile), { recursive: true });
|
|
28469
|
+
import_fs66.default.writeFileSync(flagFile, "");
|
|
27966
28470
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
27967
28471
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
27968
28472
|
} else if (state === "off") {
|
|
27969
|
-
if (
|
|
28473
|
+
if (import_fs66.default.existsSync(flagFile)) import_fs66.default.unlinkSync(flagFile);
|
|
27970
28474
|
console.log("HUD debug logging disabled.");
|
|
27971
28475
|
} else {
|
|
27972
28476
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -27981,7 +28485,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
27981
28485
|
const ms = parseDuration(options.duration);
|
|
27982
28486
|
if (ms === null) {
|
|
27983
28487
|
console.error(
|
|
27984
|
-
|
|
28488
|
+
import_chalk36.default.red(`
|
|
27985
28489
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
27986
28490
|
`)
|
|
27987
28491
|
);
|
|
@@ -27989,20 +28493,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
27989
28493
|
}
|
|
27990
28494
|
pauseNode9(ms, options.duration);
|
|
27991
28495
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
27992
|
-
console.log(
|
|
28496
|
+
console.log(import_chalk36.default.yellow(`
|
|
27993
28497
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
27994
|
-
console.log(
|
|
27995
|
-
console.log(
|
|
28498
|
+
console.log(import_chalk36.default.gray(` All tool calls will be allowed without review.`));
|
|
28499
|
+
console.log(import_chalk36.default.gray(` Run "node9 resume" to re-enable early.
|
|
27996
28500
|
`));
|
|
27997
28501
|
});
|
|
27998
28502
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
27999
28503
|
const { paused } = checkPause();
|
|
28000
28504
|
if (!paused) {
|
|
28001
|
-
console.log(
|
|
28505
|
+
console.log(import_chalk36.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
28002
28506
|
return;
|
|
28003
28507
|
}
|
|
28004
28508
|
resumeNode9();
|
|
28005
|
-
console.log(
|
|
28509
|
+
console.log(import_chalk36.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
28006
28510
|
});
|
|
28007
28511
|
var HOOK_BASED_AGENTS = {
|
|
28008
28512
|
claude: "claude",
|
|
@@ -28018,15 +28522,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
28018
28522
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
28019
28523
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
28020
28524
|
console.error(
|
|
28021
|
-
|
|
28525
|
+
import_chalk36.default.yellow(`
|
|
28022
28526
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
28023
28527
|
);
|
|
28024
|
-
console.error(
|
|
28528
|
+
console.error(import_chalk36.default.white(`
|
|
28025
28529
|
"${target}" uses its own hook system. Use:`));
|
|
28026
28530
|
console.error(
|
|
28027
|
-
|
|
28531
|
+
import_chalk36.default.green(` node9 addto ${target} `) + import_chalk36.default.gray("# one-time setup")
|
|
28028
28532
|
);
|
|
28029
|
-
console.error(
|
|
28533
|
+
console.error(import_chalk36.default.green(` ${target} `) + import_chalk36.default.gray("# run normally"));
|
|
28030
28534
|
process.exit(1);
|
|
28031
28535
|
}
|
|
28032
28536
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -28043,7 +28547,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
28043
28547
|
}
|
|
28044
28548
|
);
|
|
28045
28549
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
28046
|
-
console.error(
|
|
28550
|
+
console.error(import_chalk36.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
28047
28551
|
const daemonReady = await autoStartDaemonAndWait();
|
|
28048
28552
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
28049
28553
|
}
|
|
@@ -28056,12 +28560,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
28056
28560
|
}
|
|
28057
28561
|
if (!result.approved) {
|
|
28058
28562
|
console.error(
|
|
28059
|
-
|
|
28563
|
+
import_chalk36.default.red(`
|
|
28060
28564
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
28061
28565
|
);
|
|
28062
28566
|
process.exit(1);
|
|
28063
28567
|
}
|
|
28064
|
-
console.error(
|
|
28568
|
+
console.error(import_chalk36.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
28065
28569
|
await runProxy(fullCommand);
|
|
28066
28570
|
} else {
|
|
28067
28571
|
program.help();
|
|
@@ -28076,6 +28580,7 @@ registerAgentsCommand(program);
|
|
|
28076
28580
|
registerScanCommand(program);
|
|
28077
28581
|
registerPostureCommand(program);
|
|
28078
28582
|
registerEgressCommand(program);
|
|
28583
|
+
registerJailCommand(program);
|
|
28079
28584
|
registerSandboxCommand(program, version);
|
|
28080
28585
|
registerSessionsCommand(program);
|
|
28081
28586
|
registerSessionTaintCommand(program);
|
|
@@ -28087,9 +28592,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
28087
28592
|
const isCheckHook = process.argv[2] === "check";
|
|
28088
28593
|
if (isCheckHook) {
|
|
28089
28594
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
28090
|
-
const logPath =
|
|
28595
|
+
const logPath = import_path63.default.join(import_os56.default.homedir(), ".node9", "hook-debug.log");
|
|
28091
28596
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
28092
|
-
|
|
28597
|
+
import_fs66.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
28093
28598
|
`);
|
|
28094
28599
|
}
|
|
28095
28600
|
process.exit(0);
|