@node9/proxy 1.8.2 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -718
- package/dist/cli.js +193 -68
- package/dist/cli.mjs +185 -60
- package/dist/index.js +1 -3
- package/dist/index.mjs +1 -3
- package/dist/shields/builtin/docker.json +120 -0
- package/dist/shields/builtin/k8s.json +92 -0
- package/dist/shields/builtin/mongodb.json +78 -0
- package/dist/shields/builtin/redis.json +78 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2852,9 +2852,7 @@ end run`;
|
|
|
2852
2852
|
"--text",
|
|
2853
2853
|
pangoMessage,
|
|
2854
2854
|
"--ok-label",
|
|
2855
|
-
locked ? "Waiting..." : "Allow \u21B5"
|
|
2856
|
-
"--timeout",
|
|
2857
|
-
"300"
|
|
2855
|
+
locked ? "Waiting..." : "Allow \u21B5"
|
|
2858
2856
|
];
|
|
2859
2857
|
if (!locked) {
|
|
2860
2858
|
argsList.push("--cancel-label", "Block \u238B");
|
|
@@ -5674,6 +5672,12 @@ function estimateToolCost(tool, args) {
|
|
|
5674
5672
|
const newStr = a.new_string ?? "";
|
|
5675
5673
|
return String(newStr).length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
|
|
5676
5674
|
}
|
|
5675
|
+
if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal_execute") {
|
|
5676
|
+
const command = String(a.command ?? a.cmd ?? a.input ?? "");
|
|
5677
|
+
if (command.length > 0) {
|
|
5678
|
+
return command.length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5677
5681
|
return void 0;
|
|
5678
5682
|
}
|
|
5679
5683
|
function broadcast(event, data) {
|
|
@@ -6748,7 +6752,7 @@ import fs25 from "fs";
|
|
|
6748
6752
|
import os21 from "os";
|
|
6749
6753
|
import path28 from "path";
|
|
6750
6754
|
import readline5 from "readline";
|
|
6751
|
-
import { spawn as
|
|
6755
|
+
import { spawn as spawn10, execSync as execSync3 } from "child_process";
|
|
6752
6756
|
function getIcon(tool) {
|
|
6753
6757
|
const t = tool.toLowerCase();
|
|
6754
6758
|
for (const [k, v] of Object.entries(ICONS)) {
|
|
@@ -6805,7 +6809,7 @@ async function ensureDaemon() {
|
|
|
6805
6809
|
} catch {
|
|
6806
6810
|
}
|
|
6807
6811
|
console.log(chalk17.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6808
|
-
const child =
|
|
6812
|
+
const child = spawn10(process.execPath, [process.argv[1], "daemon"], {
|
|
6809
6813
|
detached: true,
|
|
6810
6814
|
stdio: "ignore",
|
|
6811
6815
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -6979,6 +6983,7 @@ async function startTail(options = {}) {
|
|
|
6979
6983
|
return;
|
|
6980
6984
|
}
|
|
6981
6985
|
const connectionTime = Date.now();
|
|
6986
|
+
let initialReplayDone = false;
|
|
6982
6987
|
const activityPending = /* @__PURE__ */ new Map();
|
|
6983
6988
|
const orphanedResults = /* @__PURE__ */ new Map();
|
|
6984
6989
|
let csrfToken = "";
|
|
@@ -7310,11 +7315,17 @@ async function startTail(options = {}) {
|
|
|
7310
7315
|
return;
|
|
7311
7316
|
}
|
|
7312
7317
|
if (event === "activity") {
|
|
7318
|
+
const isReplayEvent = data.status && data.status !== "pending";
|
|
7319
|
+
if (isReplayEvent && !initialReplayDone) {
|
|
7320
|
+
renderResult(data, data);
|
|
7321
|
+
return;
|
|
7322
|
+
}
|
|
7313
7323
|
if (!options.history && data.ts > 0 && data.ts < connectionTime) return;
|
|
7314
|
-
if (
|
|
7324
|
+
if (isReplayEvent) {
|
|
7315
7325
|
renderResult(data, data);
|
|
7316
7326
|
return;
|
|
7317
7327
|
}
|
|
7328
|
+
if (!initialReplayDone) initialReplayDone = true;
|
|
7318
7329
|
const orphaned = orphanedResults.get(data.id);
|
|
7319
7330
|
if (orphaned) {
|
|
7320
7331
|
orphanedResults.delete(data.id);
|
|
@@ -7695,6 +7706,24 @@ function renderContextLine(stdin) {
|
|
|
7695
7706
|
async function main() {
|
|
7696
7707
|
try {
|
|
7697
7708
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7709
|
+
if (fs26.existsSync(path29.join(os22.homedir(), ".node9", "hud-debug"))) {
|
|
7710
|
+
try {
|
|
7711
|
+
const logPath = path29.join(os22.homedir(), ".node9", "hud-debug.log");
|
|
7712
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7713
|
+
let size = 0;
|
|
7714
|
+
try {
|
|
7715
|
+
size = fs26.statSync(logPath).size;
|
|
7716
|
+
} catch {
|
|
7717
|
+
}
|
|
7718
|
+
if (size < MAX_LOG_SIZE) {
|
|
7719
|
+
fs26.appendFileSync(
|
|
7720
|
+
logPath,
|
|
7721
|
+
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7722
|
+
);
|
|
7723
|
+
}
|
|
7724
|
+
} catch {
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7698
7727
|
if (!daemonStatus2) {
|
|
7699
7728
|
renderOffline();
|
|
7700
7729
|
return;
|
|
@@ -7807,7 +7836,7 @@ function isNode9Hook(cmd) {
|
|
|
7807
7836
|
function teardownClaude() {
|
|
7808
7837
|
const homeDir2 = os10.homedir();
|
|
7809
7838
|
const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
|
|
7810
|
-
const mcpPath = path14.join(homeDir2, ".claude.json");
|
|
7839
|
+
const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
|
|
7811
7840
|
let changed = false;
|
|
7812
7841
|
const settings = readJson(hooksPath);
|
|
7813
7842
|
if (settings?.hooks) {
|
|
@@ -7833,11 +7862,12 @@ function teardownClaude() {
|
|
|
7833
7862
|
let mcpChanged = false;
|
|
7834
7863
|
if (removeNode9McpServer(claudeConfig.mcpServers)) {
|
|
7835
7864
|
mcpChanged = true;
|
|
7836
|
-
console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.claude.json"));
|
|
7865
|
+
console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.claude/.mcp.json"));
|
|
7837
7866
|
}
|
|
7838
7867
|
for (const [name, server] of Object.entries(claudeConfig.mcpServers)) {
|
|
7839
|
-
|
|
7840
|
-
|
|
7868
|
+
const args = server.args;
|
|
7869
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7870
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
7841
7871
|
claudeConfig.mcpServers[name] = {
|
|
7842
7872
|
...server,
|
|
7843
7873
|
command: originalCmd,
|
|
@@ -7845,16 +7875,11 @@ function teardownClaude() {
|
|
|
7845
7875
|
};
|
|
7846
7876
|
mcpChanged = true;
|
|
7847
7877
|
} else if (server.command === "node9") {
|
|
7848
|
-
console.warn(
|
|
7849
|
-
chalk.yellow(
|
|
7850
|
-
` \u26A0\uFE0F Cannot unwrap MCP server "${name}" in ~/.claude.json \u2014 args is empty. Remove it manually.`
|
|
7851
|
-
)
|
|
7852
|
-
);
|
|
7853
7878
|
}
|
|
7854
7879
|
}
|
|
7855
7880
|
if (mcpChanged) {
|
|
7856
7881
|
writeJson(mcpPath, claudeConfig);
|
|
7857
|
-
console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.claude.json"));
|
|
7882
|
+
console.log(chalk.green(" \u2705 Unwrapped MCP servers in ~/.claude/.mcp.json"));
|
|
7858
7883
|
}
|
|
7859
7884
|
}
|
|
7860
7885
|
}
|
|
@@ -7883,8 +7908,9 @@ function teardownGemini() {
|
|
|
7883
7908
|
console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.gemini/settings.json"));
|
|
7884
7909
|
}
|
|
7885
7910
|
for (const [name, server] of Object.entries(settings.mcpServers)) {
|
|
7886
|
-
|
|
7887
|
-
|
|
7911
|
+
const args = server.args;
|
|
7912
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7913
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
7888
7914
|
settings.mcpServers[name] = {
|
|
7889
7915
|
...server,
|
|
7890
7916
|
command: originalCmd,
|
|
@@ -7915,8 +7941,9 @@ function teardownCursor() {
|
|
|
7915
7941
|
console.log(chalk.green(" \u2705 Removed node9 MCP server entry from ~/.cursor/mcp.json"));
|
|
7916
7942
|
}
|
|
7917
7943
|
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
7918
|
-
|
|
7919
|
-
|
|
7944
|
+
const args = server.args;
|
|
7945
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7946
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
7920
7947
|
mcpConfig.mcpServers[name] = {
|
|
7921
7948
|
...server,
|
|
7922
7949
|
command: originalCmd,
|
|
@@ -7934,7 +7961,7 @@ function teardownCursor() {
|
|
|
7934
7961
|
}
|
|
7935
7962
|
async function setupClaude() {
|
|
7936
7963
|
const homeDir2 = os10.homedir();
|
|
7937
|
-
const mcpPath = path14.join(homeDir2, ".claude.json");
|
|
7964
|
+
const mcpPath = path14.join(homeDir2, ".claude", ".mcp.json");
|
|
7938
7965
|
const hooksPath = path14.join(homeDir2, ".claude", "settings.json");
|
|
7939
7966
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
7940
7967
|
const settings = readJson(hooksPath) ?? {};
|
|
@@ -7949,7 +7976,7 @@ async function setupClaude() {
|
|
|
7949
7976
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
7950
7977
|
settings.hooks.PreToolUse.push({
|
|
7951
7978
|
matcher: ".*",
|
|
7952
|
-
hooks: [{ type: "command", command: fullPathCommand("check"), timeout:
|
|
7979
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
7953
7980
|
});
|
|
7954
7981
|
console.log(chalk.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
|
|
7955
7982
|
hooksChanged = true;
|
|
@@ -7975,6 +8002,15 @@ async function setupClaude() {
|
|
|
7975
8002
|
console.log(chalk.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
7976
8003
|
anythingChanged = true;
|
|
7977
8004
|
}
|
|
8005
|
+
const hudCommand = fullPathCommand("hud");
|
|
8006
|
+
const statusLineObj = { type: "command", command: hudCommand };
|
|
8007
|
+
const existingStatusLine = settings.statusLine;
|
|
8008
|
+
const existingStatusCommand = typeof existingStatusLine === "object" ? existingStatusLine?.command : existingStatusLine;
|
|
8009
|
+
if (existingStatusCommand !== hudCommand) {
|
|
8010
|
+
settings.statusLine = statusLineObj;
|
|
8011
|
+
hooksChanged = true;
|
|
8012
|
+
anythingChanged = true;
|
|
8013
|
+
}
|
|
7978
8014
|
if (hooksChanged) {
|
|
7979
8015
|
writeJson(hooksPath, settings);
|
|
7980
8016
|
console.log("");
|
|
@@ -7982,20 +8018,24 @@ async function setupClaude() {
|
|
|
7982
8018
|
const serversToWrap = [];
|
|
7983
8019
|
for (const [name, server] of Object.entries(servers)) {
|
|
7984
8020
|
if (!server.command || server.command === "node9") continue;
|
|
7985
|
-
const
|
|
7986
|
-
serversToWrap.push({ name,
|
|
8021
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8022
|
+
serversToWrap.push({ name, upstream });
|
|
7987
8023
|
}
|
|
7988
8024
|
if (serversToWrap.length > 0) {
|
|
7989
8025
|
console.log(chalk.bold("The following existing entries will be modified:\n"));
|
|
7990
8026
|
console.log(chalk.white(` ${mcpPath}`));
|
|
7991
|
-
for (const { name,
|
|
7992
|
-
console.log(chalk.gray(` \u2022 ${name}: "${
|
|
8027
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8028
|
+
console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
7993
8029
|
}
|
|
7994
8030
|
console.log("");
|
|
7995
8031
|
const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
|
|
7996
8032
|
if (proceed) {
|
|
7997
|
-
for (const { name,
|
|
7998
|
-
servers[name] = {
|
|
8033
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8034
|
+
servers[name] = {
|
|
8035
|
+
...servers[name],
|
|
8036
|
+
command: "node9",
|
|
8037
|
+
args: ["mcp", "--upstream", upstream]
|
|
8038
|
+
};
|
|
7999
8039
|
}
|
|
8000
8040
|
claudeConfig.mcpServers = servers;
|
|
8001
8041
|
writeJson(mcpPath, claudeConfig);
|
|
@@ -8075,20 +8115,24 @@ async function setupGemini() {
|
|
|
8075
8115
|
const serversToWrap = [];
|
|
8076
8116
|
for (const [name, server] of Object.entries(servers)) {
|
|
8077
8117
|
if (!server.command || server.command === "node9") continue;
|
|
8078
|
-
const
|
|
8079
|
-
serversToWrap.push({ name,
|
|
8118
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8119
|
+
serversToWrap.push({ name, upstream });
|
|
8080
8120
|
}
|
|
8081
8121
|
if (serversToWrap.length > 0) {
|
|
8082
8122
|
console.log(chalk.bold("The following existing entries will be modified:\n"));
|
|
8083
8123
|
console.log(chalk.white(` ${settingsPath} (mcpServers)`));
|
|
8084
|
-
for (const { name,
|
|
8085
|
-
console.log(chalk.gray(` \u2022 ${name}: "${
|
|
8124
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8125
|
+
console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8086
8126
|
}
|
|
8087
8127
|
console.log("");
|
|
8088
8128
|
const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
|
|
8089
8129
|
if (proceed) {
|
|
8090
|
-
for (const { name,
|
|
8091
|
-
servers[name] = {
|
|
8130
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8131
|
+
servers[name] = {
|
|
8132
|
+
...servers[name],
|
|
8133
|
+
command: "node9",
|
|
8134
|
+
args: ["mcp", "--upstream", upstream]
|
|
8135
|
+
};
|
|
8092
8136
|
}
|
|
8093
8137
|
settings.mcpServers = servers;
|
|
8094
8138
|
writeJson(settingsPath, settings);
|
|
@@ -8147,20 +8191,24 @@ async function setupCursor() {
|
|
|
8147
8191
|
const serversToWrap = [];
|
|
8148
8192
|
for (const [name, server] of Object.entries(servers)) {
|
|
8149
8193
|
if (!server.command || server.command === "node9") continue;
|
|
8150
|
-
const
|
|
8151
|
-
serversToWrap.push({ name,
|
|
8194
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8195
|
+
serversToWrap.push({ name, upstream });
|
|
8152
8196
|
}
|
|
8153
8197
|
if (serversToWrap.length > 0) {
|
|
8154
8198
|
console.log(chalk.bold("The following existing entries will be modified:\n"));
|
|
8155
8199
|
console.log(chalk.white(` ${mcpPath}`));
|
|
8156
|
-
for (const { name,
|
|
8157
|
-
console.log(chalk.gray(` \u2022 ${name}: "${
|
|
8200
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8201
|
+
console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8158
8202
|
}
|
|
8159
8203
|
console.log("");
|
|
8160
8204
|
const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
|
|
8161
8205
|
if (proceed) {
|
|
8162
|
-
for (const { name,
|
|
8163
|
-
servers[name] = {
|
|
8206
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8207
|
+
servers[name] = {
|
|
8208
|
+
...servers[name],
|
|
8209
|
+
command: "node9",
|
|
8210
|
+
args: ["mcp", "--upstream", upstream]
|
|
8211
|
+
};
|
|
8164
8212
|
}
|
|
8165
8213
|
mcpConfig.mcpServers = servers;
|
|
8166
8214
|
writeJson(mcpPath, mcpConfig);
|
|
@@ -8223,20 +8271,24 @@ async function setupCodex() {
|
|
|
8223
8271
|
const serversToWrap = [];
|
|
8224
8272
|
for (const [name, server] of Object.entries(servers)) {
|
|
8225
8273
|
if (!server.command || server.command === "node9") continue;
|
|
8226
|
-
const
|
|
8227
|
-
serversToWrap.push({ name,
|
|
8274
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8275
|
+
serversToWrap.push({ name, upstream });
|
|
8228
8276
|
}
|
|
8229
8277
|
if (serversToWrap.length > 0) {
|
|
8230
8278
|
console.log(chalk.bold("The following existing entries will be modified:\n"));
|
|
8231
8279
|
console.log(chalk.white(` ${configPath}`));
|
|
8232
|
-
for (const { name,
|
|
8233
|
-
console.log(chalk.gray(` \u2022 ${name}: "${
|
|
8280
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8281
|
+
console.log(chalk.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8234
8282
|
}
|
|
8235
8283
|
console.log("");
|
|
8236
8284
|
const proceed = await confirm({ message: "Wrap these MCP servers?", default: true });
|
|
8237
8285
|
if (proceed) {
|
|
8238
|
-
for (const { name,
|
|
8239
|
-
servers[name] = {
|
|
8286
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8287
|
+
servers[name] = {
|
|
8288
|
+
...servers[name],
|
|
8289
|
+
command: "node9",
|
|
8290
|
+
args: ["mcp", "--upstream", upstream]
|
|
8291
|
+
};
|
|
8240
8292
|
}
|
|
8241
8293
|
config.mcp_servers = servers;
|
|
8242
8294
|
writeToml(configPath, config);
|
|
@@ -8425,18 +8477,20 @@ async function runProxy(targetCommand) {
|
|
|
8425
8477
|
const cmd = commandParts[0];
|
|
8426
8478
|
const args = commandParts.slice(1);
|
|
8427
8479
|
let executable = cmd;
|
|
8480
|
+
let useShell = false;
|
|
8428
8481
|
try {
|
|
8429
8482
|
const { stdout } = await execa("which", [cmd]);
|
|
8430
8483
|
if (stdout) executable = stdout.trim();
|
|
8431
8484
|
} catch {
|
|
8485
|
+
useShell = true;
|
|
8432
8486
|
}
|
|
8433
8487
|
console.error(chalk4.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
|
|
8434
|
-
const
|
|
8488
|
+
const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
|
|
8489
|
+
const child = useShell ? spawn3("/bin/bash", ["-c", targetCommand], {
|
|
8435
8490
|
stdio: ["pipe", "pipe", "inherit"],
|
|
8436
|
-
// We control STDIN and STDOUT
|
|
8437
8491
|
shell: false,
|
|
8438
|
-
env:
|
|
8439
|
-
});
|
|
8492
|
+
env: spawnEnv
|
|
8493
|
+
}) : spawn3(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
|
|
8440
8494
|
const agentIn = readline.createInterface({ input: process.stdin, terminal: false });
|
|
8441
8495
|
agentIn.on("line", async (line) => {
|
|
8442
8496
|
let message;
|
|
@@ -8549,6 +8603,7 @@ init_config();
|
|
|
8549
8603
|
init_policy();
|
|
8550
8604
|
import chalk5 from "chalk";
|
|
8551
8605
|
import fs18 from "fs";
|
|
8606
|
+
import { spawn as spawn6 } from "child_process";
|
|
8552
8607
|
import path20 from "path";
|
|
8553
8608
|
import os14 from "os";
|
|
8554
8609
|
|
|
@@ -8928,6 +8983,37 @@ RAW: ${raw}
|
|
|
8928
8983
|
process.exit(0);
|
|
8929
8984
|
}
|
|
8930
8985
|
const config = getConfig(payload.cwd || void 0);
|
|
8986
|
+
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
8987
|
+
try {
|
|
8988
|
+
const scriptPath = process.argv[1];
|
|
8989
|
+
if (typeof scriptPath !== "string" || !path20.isAbsolute(scriptPath))
|
|
8990
|
+
throw new Error("node9: argv[1] is not an absolute path");
|
|
8991
|
+
const resolvedScript = fs18.realpathSync(scriptPath);
|
|
8992
|
+
const expectedCli = fs18.realpathSync(path20.resolve(__dirname, "../../cli.js"));
|
|
8993
|
+
if (resolvedScript !== expectedCli)
|
|
8994
|
+
throw new Error(
|
|
8995
|
+
"node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
|
|
8996
|
+
);
|
|
8997
|
+
const safeEnv = { ...process.env };
|
|
8998
|
+
for (const key of [
|
|
8999
|
+
"NODE_OPTIONS",
|
|
9000
|
+
"LD_PRELOAD",
|
|
9001
|
+
"LD_LIBRARY_PATH",
|
|
9002
|
+
"DYLD_INSERT_LIBRARIES",
|
|
9003
|
+
"NODE_PATH",
|
|
9004
|
+
"ELECTRON_RUN_AS_NODE"
|
|
9005
|
+
]) {
|
|
9006
|
+
delete safeEnv[key];
|
|
9007
|
+
}
|
|
9008
|
+
const d = spawn6(process.execPath, [scriptPath, "daemon"], {
|
|
9009
|
+
detached: true,
|
|
9010
|
+
stdio: "ignore",
|
|
9011
|
+
env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
9012
|
+
});
|
|
9013
|
+
d.unref();
|
|
9014
|
+
} catch {
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
8931
9017
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
8932
9018
|
const logPath = path20.join(os14.homedir(), ".node9", "hook-debug.log");
|
|
8933
9019
|
if (!fs18.existsSync(path20.dirname(logPath)))
|
|
@@ -9839,7 +9925,7 @@ function registerAuditCommand(program2) {
|
|
|
9839
9925
|
init_daemon2();
|
|
9840
9926
|
init_daemon();
|
|
9841
9927
|
import chalk9 from "chalk";
|
|
9842
|
-
import { spawn as
|
|
9928
|
+
import { spawn as spawn7 } from "child_process";
|
|
9843
9929
|
function registerDaemonCommand(program2) {
|
|
9844
9930
|
program2.command("daemon").description("Run the local approval server").argument("[action]", "start | stop | status (default: start)").option("-b, --background", "Start the daemon in the background (detached)").option("-o, --openui", "Start in background and open browser").option(
|
|
9845
9931
|
"-w, --watch",
|
|
@@ -9870,7 +9956,7 @@ function registerDaemonCommand(program2) {
|
|
|
9870
9956
|
console.log(chalk9.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
9871
9957
|
process.exit(0);
|
|
9872
9958
|
}
|
|
9873
|
-
const child =
|
|
9959
|
+
const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
|
|
9874
9960
|
detached: true,
|
|
9875
9961
|
stdio: "ignore"
|
|
9876
9962
|
});
|
|
@@ -9885,7 +9971,7 @@ function registerDaemonCommand(program2) {
|
|
|
9885
9971
|
process.exit(0);
|
|
9886
9972
|
}
|
|
9887
9973
|
if (options.background) {
|
|
9888
|
-
const child =
|
|
9974
|
+
const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
|
|
9889
9975
|
detached: true,
|
|
9890
9976
|
stdio: "ignore"
|
|
9891
9977
|
});
|
|
@@ -10474,7 +10560,7 @@ function registerUndoCommand(program2) {
|
|
|
10474
10560
|
// src/cli/commands/watch.ts
|
|
10475
10561
|
init_daemon();
|
|
10476
10562
|
import chalk14 from "chalk";
|
|
10477
|
-
import { spawn as
|
|
10563
|
+
import { spawn as spawn8, spawnSync as spawnSync5 } from "child_process";
|
|
10478
10564
|
function registerWatchCommand(program2) {
|
|
10479
10565
|
program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
|
|
10480
10566
|
let port = DAEMON_PORT;
|
|
@@ -10490,7 +10576,7 @@ function registerWatchCommand(program2) {
|
|
|
10490
10576
|
}
|
|
10491
10577
|
} catch {
|
|
10492
10578
|
console.error(chalk14.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
10493
|
-
const child =
|
|
10579
|
+
const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
|
|
10494
10580
|
detached: true,
|
|
10495
10581
|
stdio: "ignore",
|
|
10496
10582
|
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
@@ -10536,7 +10622,7 @@ function registerWatchCommand(program2) {
|
|
|
10536
10622
|
init_orchestrator();
|
|
10537
10623
|
import readline3 from "readline";
|
|
10538
10624
|
import chalk15 from "chalk";
|
|
10539
|
-
import { spawn as
|
|
10625
|
+
import { spawn as spawn9 } from "child_process";
|
|
10540
10626
|
import { execa as execa2 } from "execa";
|
|
10541
10627
|
init_provenance();
|
|
10542
10628
|
function sanitize4(value) {
|
|
@@ -10623,7 +10709,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10623
10709
|
const safeEnv = Object.fromEntries(
|
|
10624
10710
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
10625
10711
|
);
|
|
10626
|
-
const child =
|
|
10712
|
+
const child = spawn9(executable, cmdArgs, {
|
|
10627
10713
|
stdio: ["pipe", "pipe", "inherit"],
|
|
10628
10714
|
// control stdin/stdout; inherit stderr
|
|
10629
10715
|
shell: false,
|
|
@@ -10706,8 +10792,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10706
10792
|
return;
|
|
10707
10793
|
} finally {
|
|
10708
10794
|
authPending = false;
|
|
10709
|
-
|
|
10710
|
-
|
|
10795
|
+
if (deferredStdinEnd) {
|
|
10796
|
+
child.stdin.end();
|
|
10797
|
+
} else {
|
|
10798
|
+
agentIn.resume();
|
|
10799
|
+
}
|
|
10711
10800
|
if (deferredExitCode !== null) process.exit(deferredExitCode);
|
|
10712
10801
|
}
|
|
10713
10802
|
return;
|
|
@@ -11442,7 +11531,43 @@ registerMcpGatewayCommand(program);
|
|
|
11442
11531
|
registerMcpServerCommand(program);
|
|
11443
11532
|
registerCheckCommand(program);
|
|
11444
11533
|
registerLogCommand(program);
|
|
11445
|
-
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").
|
|
11534
|
+
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
11535
|
+
"after",
|
|
11536
|
+
`
|
|
11537
|
+
Outputs up to 3 lines to stdout, then exits:
|
|
11538
|
+
|
|
11539
|
+
Line 1 \u2014 Security state (always shown):
|
|
11540
|
+
\u{1F6E1} node9 | <mode> [shields] | \u2705 allowed \u{1F6D1} blocked \u{1F6A8} dlp ~$cost
|
|
11541
|
+
Shows "offline" if the node9 daemon is not running.
|
|
11542
|
+
|
|
11543
|
+
Line 2 \u2014 Claude context & rate limits (shown when available):
|
|
11544
|
+
<model> \u2502 ctx \u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591 61% \u2502 5h \u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591 40% (2h 10m left)
|
|
11545
|
+
Only appears when Claude Code passes context_window / rate_limits data via stdin.
|
|
11546
|
+
|
|
11547
|
+
Line 3 \u2014 Environment counts (shown when non-zero):
|
|
11548
|
+
2 CLAUDE.md | 5 rules | 4 MCPs | 3 hooks
|
|
11549
|
+
Counts CLAUDE.md files, rules/, MCP servers, and hook entries across user + project scope.
|
|
11550
|
+
Disable with: { "settings": { "hud": { "showEnvironmentCounts": false } } } in node9.config.json
|
|
11551
|
+
|
|
11552
|
+
Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
11553
|
+
Run "node9 addto claude" to register it as the statusLine.`
|
|
11554
|
+
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
11555
|
+
if (subcommand === "debug") {
|
|
11556
|
+
const flagFile = path30.join(os23.homedir(), ".node9", "hud-debug");
|
|
11557
|
+
if (state === "on") {
|
|
11558
|
+
fs27.mkdirSync(path30.dirname(flagFile), { recursive: true });
|
|
11559
|
+
fs27.writeFileSync(flagFile, "");
|
|
11560
|
+
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
11561
|
+
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
11562
|
+
} else if (state === "off") {
|
|
11563
|
+
if (fs27.existsSync(flagFile)) fs27.unlinkSync(flagFile);
|
|
11564
|
+
console.log("HUD debug logging disabled.");
|
|
11565
|
+
} else {
|
|
11566
|
+
console.error("Usage: node9 hud debug on|off");
|
|
11567
|
+
process.exit(1);
|
|
11568
|
+
}
|
|
11569
|
+
return;
|
|
11570
|
+
}
|
|
11446
11571
|
const { main: main2 } = await Promise.resolve().then(() => (init_hud(), hud_exports));
|
|
11447
11572
|
await main2();
|
|
11448
11573
|
});
|
package/dist/index.js
CHANGED
|
@@ -2382,9 +2382,7 @@ end run`;
|
|
|
2382
2382
|
"--text",
|
|
2383
2383
|
pangoMessage,
|
|
2384
2384
|
"--ok-label",
|
|
2385
|
-
locked ? "Waiting..." : "Allow \u21B5"
|
|
2386
|
-
"--timeout",
|
|
2387
|
-
"300"
|
|
2385
|
+
locked ? "Waiting..." : "Allow \u21B5"
|
|
2388
2386
|
];
|
|
2389
2387
|
if (!locked) {
|
|
2390
2388
|
argsList.push("--cancel-label", "Block \u238B");
|
package/dist/index.mjs
CHANGED
|
@@ -2352,9 +2352,7 @@ end run`;
|
|
|
2352
2352
|
"--text",
|
|
2353
2353
|
pangoMessage,
|
|
2354
2354
|
"--ok-label",
|
|
2355
|
-
locked ? "Waiting..." : "Allow \u21B5"
|
|
2356
|
-
"--timeout",
|
|
2357
|
-
"300"
|
|
2355
|
+
locked ? "Waiting..." : "Allow \u21B5"
|
|
2358
2356
|
];
|
|
2359
2357
|
if (!locked) {
|
|
2360
2358
|
argsList.push("--cancel-label", "Block \u238B");
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docker",
|
|
3
|
+
"description": "Protects Docker environments from destructive AI operations",
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"smartRules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "shield:docker:block-system-prune",
|
|
8
|
+
"tool": "*",
|
|
9
|
+
"conditions": [
|
|
10
|
+
{
|
|
11
|
+
"field": "command",
|
|
12
|
+
"op": "matches",
|
|
13
|
+
"value": "docker\\s+system\\s+prune",
|
|
14
|
+
"flags": "i"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"verdict": "block",
|
|
18
|
+
"reason": "docker system prune removes all unused containers, images, and volumes — blocked by Docker shield"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "shield:docker:block-volume-prune",
|
|
22
|
+
"tool": "*",
|
|
23
|
+
"conditions": [
|
|
24
|
+
{
|
|
25
|
+
"field": "command",
|
|
26
|
+
"op": "matches",
|
|
27
|
+
"value": "docker\\s+volume\\s+prune",
|
|
28
|
+
"flags": "i"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"verdict": "block",
|
|
32
|
+
"reason": "docker volume prune destroys all unused volumes and their data — blocked by Docker shield"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "shield:docker:block-rm-force",
|
|
36
|
+
"tool": "*",
|
|
37
|
+
"conditionMode": "all",
|
|
38
|
+
"conditions": [
|
|
39
|
+
{
|
|
40
|
+
"field": "command",
|
|
41
|
+
"op": "matches",
|
|
42
|
+
"value": "docker\\s+rm\\b",
|
|
43
|
+
"flags": "i"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"field": "command",
|
|
47
|
+
"op": "matches",
|
|
48
|
+
"value": "(^|\\s)(-f|--force)(\\s|$)",
|
|
49
|
+
"flags": "i"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"verdict": "block",
|
|
53
|
+
"reason": "Force-removing running containers is destructive — blocked by Docker shield"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "shield:docker:review-volume-rm",
|
|
57
|
+
"tool": "*",
|
|
58
|
+
"conditions": [
|
|
59
|
+
{
|
|
60
|
+
"field": "command",
|
|
61
|
+
"op": "matches",
|
|
62
|
+
"value": "docker\\s+volume\\s+rm\\s+",
|
|
63
|
+
"flags": "i"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"verdict": "review",
|
|
67
|
+
"reason": "Volume removal deletes persistent data and requires human approval (Docker shield)"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "shield:docker:review-stop-kill",
|
|
71
|
+
"tool": "*",
|
|
72
|
+
"conditions": [
|
|
73
|
+
{
|
|
74
|
+
"field": "command",
|
|
75
|
+
"op": "matches",
|
|
76
|
+
"value": "docker\\s+(stop|kill)\\s+",
|
|
77
|
+
"flags": "i"
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"verdict": "review",
|
|
81
|
+
"reason": "Stopping or killing containers requires human approval (Docker shield)"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "shield:docker:review-image-rm",
|
|
85
|
+
"tool": "*",
|
|
86
|
+
"conditions": [
|
|
87
|
+
{
|
|
88
|
+
"field": "command",
|
|
89
|
+
"op": "matches",
|
|
90
|
+
"value": "docker\\s+image\\s+rm\\b",
|
|
91
|
+
"flags": "i"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"verdict": "review",
|
|
95
|
+
"reason": "Image removal requires human approval (Docker shield)"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "shield:docker:review-rmi-force",
|
|
99
|
+
"tool": "*",
|
|
100
|
+
"conditionMode": "all",
|
|
101
|
+
"conditions": [
|
|
102
|
+
{
|
|
103
|
+
"field": "command",
|
|
104
|
+
"op": "matches",
|
|
105
|
+
"value": "docker\\s+rmi\\b",
|
|
106
|
+
"flags": "i"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"field": "command",
|
|
110
|
+
"op": "matches",
|
|
111
|
+
"value": "(^|\\s)(-f|--force)(\\s|$)",
|
|
112
|
+
"flags": "i"
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"verdict": "review",
|
|
116
|
+
"reason": "Force image removal requires human approval (Docker shield)"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"dangerousWords": []
|
|
120
|
+
}
|