@node9/proxy 1.7.1 → 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 -657
- package/dist/cli.js +404 -333
- package/dist/cli.mjs +396 -325
- package/dist/index.js +90 -260
- package/dist/index.mjs +90 -260
- package/dist/shields/builtin/aws.json +59 -0
- package/dist/shields/builtin/bash-safe.json +78 -0
- package/dist/shields/builtin/docker.json +120 -0
- package/dist/shields/builtin/filesystem.json +30 -0
- package/dist/shields/builtin/github.json +26 -0
- package/dist/shields/builtin/k8s.json +92 -0
- package/dist/shields/builtin/mongodb.json +78 -0
- package/dist/shields/builtin/postgres.json +42 -0
- package/dist/shields/builtin/redis.json +78 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -268,6 +268,70 @@ var init_config_schema = __esm({
|
|
|
268
268
|
});
|
|
269
269
|
|
|
270
270
|
// src/shields.ts
|
|
271
|
+
function validateShieldDefinition(raw, filePath) {
|
|
272
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
273
|
+
process.stderr.write(`[node9] Shield file is not an object: ${filePath}
|
|
274
|
+
`);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
const r = raw;
|
|
278
|
+
if (typeof r.name !== "string" || !r.name) {
|
|
279
|
+
process.stderr.write(`[node9] Shield file missing 'name': ${filePath}
|
|
280
|
+
`);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
if (typeof r.description !== "string") {
|
|
284
|
+
process.stderr.write(`[node9] Shield file missing 'description': ${filePath}
|
|
285
|
+
`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
if (!Array.isArray(r.aliases)) {
|
|
289
|
+
process.stderr.write(`[node9] Shield file missing 'aliases' array: ${filePath}
|
|
290
|
+
`);
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (!Array.isArray(r.smartRules)) {
|
|
294
|
+
process.stderr.write(`[node9] Shield file missing 'smartRules' array: ${filePath}
|
|
295
|
+
`);
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
if (!Array.isArray(r.dangerousWords)) {
|
|
299
|
+
process.stderr.write(`[node9] Shield file missing 'dangerousWords' array: ${filePath}
|
|
300
|
+
`);
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
return r;
|
|
304
|
+
}
|
|
305
|
+
function loadShieldsFromDir(dir, label) {
|
|
306
|
+
const result = {};
|
|
307
|
+
let entries;
|
|
308
|
+
try {
|
|
309
|
+
entries = import_fs2.default.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
310
|
+
} catch (err2) {
|
|
311
|
+
if (err2.code !== "ENOENT") {
|
|
312
|
+
process.stderr.write(`[node9] Could not read ${label} shields dir ${dir}: ${String(err2)}
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
for (const file of entries) {
|
|
318
|
+
const filePath = import_path2.default.join(dir, file);
|
|
319
|
+
try {
|
|
320
|
+
const raw = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
|
|
321
|
+
const shield = validateShieldDefinition(raw, filePath);
|
|
322
|
+
if (shield) result[shield.name] = shield;
|
|
323
|
+
} catch (err2) {
|
|
324
|
+
process.stderr.write(`[node9] Failed to load ${label} shield ${file}: ${String(err2)}
|
|
325
|
+
`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
function buildSHIELDS() {
|
|
331
|
+
const builtins = loadShieldsFromDir(BUILTIN_DIR, "builtin");
|
|
332
|
+
const userShields = loadShieldsFromDir(USER_SHIELDS_DIR, "user");
|
|
333
|
+
return { ...builtins, ...userShields };
|
|
334
|
+
}
|
|
271
335
|
function resolveShieldName(input) {
|
|
272
336
|
const lower = input.toLowerCase();
|
|
273
337
|
if (SHIELDS[lower]) return lower;
|
|
@@ -374,7 +438,24 @@ function resolveShieldRule(shieldName, identifier) {
|
|
|
374
438
|
}
|
|
375
439
|
return null;
|
|
376
440
|
}
|
|
377
|
-
|
|
441
|
+
function installShield(name, shieldJson) {
|
|
442
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`Invalid shield name '${name}': only alphanumeric characters, hyphens, and underscores are allowed`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const shield = validateShieldDefinition(shieldJson, `<downloaded:${name}>`);
|
|
448
|
+
if (!shield) throw new Error(`Downloaded shield '${name}' failed validation`);
|
|
449
|
+
if (shield.name !== name) {
|
|
450
|
+
throw new Error(`Shield name mismatch: file declares '${shield.name}' but expected '${name}'`);
|
|
451
|
+
}
|
|
452
|
+
import_fs2.default.mkdirSync(USER_SHIELDS_DIR, { recursive: true });
|
|
453
|
+
const filePath = import_path2.default.join(USER_SHIELDS_DIR, `${name}.json`);
|
|
454
|
+
const tmp = `${filePath}.${import_crypto2.default.randomBytes(6).toString("hex")}.tmp`;
|
|
455
|
+
import_fs2.default.writeFileSync(tmp, JSON.stringify(shieldJson, null, 2), { mode: 384 });
|
|
456
|
+
import_fs2.default.renameSync(tmp, filePath);
|
|
457
|
+
}
|
|
458
|
+
var import_fs2, import_path2, import_os2, import_crypto2, BUILTIN_DIR, USER_SHIELDS_DIR, SHIELDS, SHIELDS_STATE_FILE;
|
|
378
459
|
var init_shields = __esm({
|
|
379
460
|
"src/shields.ts"() {
|
|
380
461
|
"use strict";
|
|
@@ -382,251 +463,9 @@ var init_shields = __esm({
|
|
|
382
463
|
import_path2 = __toESM(require("path"));
|
|
383
464
|
import_os2 = __toESM(require("os"));
|
|
384
465
|
import_crypto2 = __toESM(require("crypto"));
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
description: "Protects PostgreSQL databases from destructive AI operations",
|
|
389
|
-
aliases: ["pg", "postgresql"],
|
|
390
|
-
smartRules: [
|
|
391
|
-
{
|
|
392
|
-
name: "shield:postgres:block-drop-table",
|
|
393
|
-
tool: "*",
|
|
394
|
-
conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
|
|
395
|
-
verdict: "block",
|
|
396
|
-
reason: "DROP TABLE is irreversible \u2014 blocked by Postgres shield"
|
|
397
|
-
},
|
|
398
|
-
{
|
|
399
|
-
name: "shield:postgres:block-truncate",
|
|
400
|
-
tool: "*",
|
|
401
|
-
conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
|
|
402
|
-
verdict: "block",
|
|
403
|
-
reason: "TRUNCATE is irreversible \u2014 blocked by Postgres shield"
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
name: "shield:postgres:block-drop-column",
|
|
407
|
-
tool: "*",
|
|
408
|
-
conditions: [
|
|
409
|
-
{ field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
|
|
410
|
-
],
|
|
411
|
-
verdict: "block",
|
|
412
|
-
reason: "DROP COLUMN is irreversible \u2014 blocked by Postgres shield"
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
name: "shield:postgres:review-grant-revoke",
|
|
416
|
-
tool: "*",
|
|
417
|
-
conditions: [{ field: "sql", op: "matches", value: "\\b(GRANT|REVOKE)\\b", flags: "i" }],
|
|
418
|
-
verdict: "review",
|
|
419
|
-
reason: "Permission changes require human approval (Postgres shield)"
|
|
420
|
-
}
|
|
421
|
-
],
|
|
422
|
-
dangerousWords: ["dropdb", "pg_dropcluster"]
|
|
423
|
-
},
|
|
424
|
-
github: {
|
|
425
|
-
name: "github",
|
|
426
|
-
description: "Protects GitHub repositories from destructive AI operations",
|
|
427
|
-
aliases: ["git"],
|
|
428
|
-
smartRules: [
|
|
429
|
-
{
|
|
430
|
-
// Note: git branch -d/-D is already caught by the built-in review-git-destructive rule.
|
|
431
|
-
// This rule adds coverage for `git push --delete` which the built-in does not match.
|
|
432
|
-
name: "shield:github:review-delete-branch-remote",
|
|
433
|
-
tool: "bash",
|
|
434
|
-
conditions: [
|
|
435
|
-
{
|
|
436
|
-
field: "command",
|
|
437
|
-
op: "matches",
|
|
438
|
-
value: "git\\s+push\\s+.*--delete",
|
|
439
|
-
flags: "i"
|
|
440
|
-
}
|
|
441
|
-
],
|
|
442
|
-
verdict: "review",
|
|
443
|
-
reason: "Remote branch deletion requires human approval (GitHub shield)"
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
name: "shield:github:block-delete-repo",
|
|
447
|
-
tool: "*",
|
|
448
|
-
conditions: [
|
|
449
|
-
{ field: "command", op: "matches", value: "gh\\s+repo\\s+delete", flags: "i" }
|
|
450
|
-
],
|
|
451
|
-
verdict: "block",
|
|
452
|
-
reason: "Repository deletion is irreversible \u2014 blocked by GitHub shield"
|
|
453
|
-
}
|
|
454
|
-
],
|
|
455
|
-
dangerousWords: []
|
|
456
|
-
},
|
|
457
|
-
aws: {
|
|
458
|
-
name: "aws",
|
|
459
|
-
description: "Protects AWS infrastructure from destructive AI operations",
|
|
460
|
-
aliases: ["amazon"],
|
|
461
|
-
smartRules: [
|
|
462
|
-
{
|
|
463
|
-
name: "shield:aws:block-delete-s3-bucket",
|
|
464
|
-
tool: "*",
|
|
465
|
-
conditions: [
|
|
466
|
-
{
|
|
467
|
-
field: "command",
|
|
468
|
-
op: "matches",
|
|
469
|
-
value: "aws\\s+s3.*rb\\s|aws\\s+s3api\\s+delete-bucket",
|
|
470
|
-
flags: "i"
|
|
471
|
-
}
|
|
472
|
-
],
|
|
473
|
-
verdict: "block",
|
|
474
|
-
reason: "S3 bucket deletion is irreversible \u2014 blocked by AWS shield"
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
name: "shield:aws:review-iam-changes",
|
|
478
|
-
tool: "*",
|
|
479
|
-
conditions: [
|
|
480
|
-
{
|
|
481
|
-
field: "command",
|
|
482
|
-
op: "matches",
|
|
483
|
-
value: "aws\\s+iam\\s+(create|delete|attach|detach|put|remove)",
|
|
484
|
-
flags: "i"
|
|
485
|
-
}
|
|
486
|
-
],
|
|
487
|
-
verdict: "review",
|
|
488
|
-
reason: "IAM changes require human approval (AWS shield)"
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
name: "shield:aws:block-ec2-terminate",
|
|
492
|
-
tool: "*",
|
|
493
|
-
conditions: [
|
|
494
|
-
{
|
|
495
|
-
field: "command",
|
|
496
|
-
op: "matches",
|
|
497
|
-
value: "aws\\s+ec2\\s+terminate-instances",
|
|
498
|
-
flags: "i"
|
|
499
|
-
}
|
|
500
|
-
],
|
|
501
|
-
verdict: "block",
|
|
502
|
-
reason: "EC2 instance termination is irreversible \u2014 blocked by AWS shield"
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
name: "shield:aws:review-rds-delete",
|
|
506
|
-
tool: "*",
|
|
507
|
-
conditions: [
|
|
508
|
-
{ field: "command", op: "matches", value: "aws\\s+rds\\s+delete-", flags: "i" }
|
|
509
|
-
],
|
|
510
|
-
verdict: "review",
|
|
511
|
-
reason: "RDS deletion requires human approval (AWS shield)"
|
|
512
|
-
}
|
|
513
|
-
],
|
|
514
|
-
dangerousWords: []
|
|
515
|
-
},
|
|
516
|
-
"bash-safe": {
|
|
517
|
-
name: "bash-safe",
|
|
518
|
-
description: "Blocks high-risk bash patterns: pipe-to-shell, rm -rf /, disk overwrites, eval",
|
|
519
|
-
aliases: ["bash", "shell"],
|
|
520
|
-
smartRules: [
|
|
521
|
-
{
|
|
522
|
-
name: "shield:bash-safe:block-pipe-to-shell",
|
|
523
|
-
tool: "bash",
|
|
524
|
-
conditions: [
|
|
525
|
-
{
|
|
526
|
-
field: "command",
|
|
527
|
-
op: "matches",
|
|
528
|
-
value: "(curl|wget)\\s+[^|]*\\|\\s*(bash|sh|zsh|fish|python3?|ruby|perl|node)",
|
|
529
|
-
flags: "i"
|
|
530
|
-
}
|
|
531
|
-
],
|
|
532
|
-
verdict: "block",
|
|
533
|
-
reason: "Pipe-to-shell is a common supply-chain attack vector \u2014 blocked by bash-safe shield"
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
name: "shield:bash-safe:block-obfuscated-exec",
|
|
537
|
-
tool: "bash",
|
|
538
|
-
conditions: [
|
|
539
|
-
{
|
|
540
|
-
field: "command",
|
|
541
|
-
op: "matches",
|
|
542
|
-
value: "base64\\s+(-d|--decode).*\\|\\s*(bash|sh|zsh)",
|
|
543
|
-
flags: "i"
|
|
544
|
-
}
|
|
545
|
-
],
|
|
546
|
-
verdict: "block",
|
|
547
|
-
reason: "Obfuscated execution via base64 decode \u2014 blocked by bash-safe shield"
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
name: "shield:bash-safe:block-rm-root",
|
|
551
|
-
tool: "bash",
|
|
552
|
-
conditions: [
|
|
553
|
-
{
|
|
554
|
-
field: "command",
|
|
555
|
-
op: "matches",
|
|
556
|
-
value: "rm\\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)[a-zA-Z]*\\s+(\\/|~|\\$HOME|\\$\\{HOME\\})\\s*$",
|
|
557
|
-
flags: "i"
|
|
558
|
-
}
|
|
559
|
-
],
|
|
560
|
-
verdict: "block",
|
|
561
|
-
reason: "rm -rf of root or home directory is catastrophic \u2014 blocked by bash-safe shield"
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: "shield:bash-safe:block-disk-overwrite",
|
|
565
|
-
tool: "bash",
|
|
566
|
-
conditions: [
|
|
567
|
-
{
|
|
568
|
-
field: "command",
|
|
569
|
-
op: "matches",
|
|
570
|
-
value: "dd\\s+.*of=\\/dev\\/(sd|nvme|hd|vd|xvd)",
|
|
571
|
-
flags: "i"
|
|
572
|
-
}
|
|
573
|
-
],
|
|
574
|
-
verdict: "block",
|
|
575
|
-
reason: "Writing directly to a block device is irreversible \u2014 blocked by bash-safe shield"
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
name: "shield:bash-safe:review-eval",
|
|
579
|
-
tool: "bash",
|
|
580
|
-
conditions: [
|
|
581
|
-
{
|
|
582
|
-
field: "command",
|
|
583
|
-
op: "matches",
|
|
584
|
-
value: '\\beval\\s+[\\$`("]',
|
|
585
|
-
flags: "i"
|
|
586
|
-
}
|
|
587
|
-
],
|
|
588
|
-
verdict: "review",
|
|
589
|
-
reason: "eval of dynamic content requires human approval (bash-safe shield)"
|
|
590
|
-
}
|
|
591
|
-
],
|
|
592
|
-
dangerousWords: []
|
|
593
|
-
},
|
|
594
|
-
filesystem: {
|
|
595
|
-
name: "filesystem",
|
|
596
|
-
description: "Protects the local filesystem from dangerous AI operations",
|
|
597
|
-
aliases: ["fs"],
|
|
598
|
-
smartRules: [
|
|
599
|
-
{
|
|
600
|
-
name: "shield:filesystem:review-chmod-777",
|
|
601
|
-
tool: "bash",
|
|
602
|
-
conditions: [
|
|
603
|
-
{ field: "command", op: "matches", value: "chmod\\s+(777|a\\+rwx)", flags: "i" }
|
|
604
|
-
],
|
|
605
|
-
verdict: "review",
|
|
606
|
-
reason: "chmod 777 requires human approval (filesystem shield)"
|
|
607
|
-
},
|
|
608
|
-
{
|
|
609
|
-
name: "shield:filesystem:review-write-etc",
|
|
610
|
-
tool: "bash",
|
|
611
|
-
conditions: [
|
|
612
|
-
{
|
|
613
|
-
field: "command",
|
|
614
|
-
// Narrow to write-indicative operations to avoid approval fatigue on reads.
|
|
615
|
-
// Matches: tee /etc/*, cp .../etc/*, mv .../etc/*, > /etc/*, install .../etc/*
|
|
616
|
-
op: "matches",
|
|
617
|
-
value: "(tee|\\bcp\\b|\\bmv\\b|install|>+)\\s+.*\\/etc\\/"
|
|
618
|
-
}
|
|
619
|
-
],
|
|
620
|
-
verdict: "review",
|
|
621
|
-
reason: "Writing to /etc requires human approval (filesystem shield)"
|
|
622
|
-
}
|
|
623
|
-
],
|
|
624
|
-
// dd removed: too common as a legitimate tool (disk imaging, file ops).
|
|
625
|
-
// mkfs removed: already in the built-in DANGEROUS_WORDS baseline.
|
|
626
|
-
// wipefs retained: rarely legitimate in an agent context and not in built-ins.
|
|
627
|
-
dangerousWords: ["wipefs"]
|
|
628
|
-
}
|
|
629
|
-
};
|
|
466
|
+
BUILTIN_DIR = import_path2.default.join(__dirname, "shields", "builtin");
|
|
467
|
+
USER_SHIELDS_DIR = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields");
|
|
468
|
+
SHIELDS = buildSHIELDS();
|
|
630
469
|
SHIELDS_STATE_FILE = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields.json");
|
|
631
470
|
}
|
|
632
471
|
});
|
|
@@ -2598,7 +2437,7 @@ function isDaemonRunning() {
|
|
|
2598
2437
|
return false;
|
|
2599
2438
|
}
|
|
2600
2439
|
}
|
|
2601
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly) {
|
|
2440
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched) {
|
|
2602
2441
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2603
2442
|
const ctrl = new AbortController();
|
|
2604
2443
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -2619,7 +2458,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
2619
2458
|
...cwd && { cwd },
|
|
2620
2459
|
...recoveryCommand && { recoveryCommand },
|
|
2621
2460
|
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
2622
|
-
...viewOnly && { viewOnly: true }
|
|
2461
|
+
...viewOnly && { viewOnly: true },
|
|
2462
|
+
...localSmartRuleMatched && { localSmartRuleMatched: true }
|
|
2623
2463
|
}),
|
|
2624
2464
|
signal: ctrl.signal
|
|
2625
2465
|
});
|
|
@@ -3032,9 +2872,7 @@ end run`;
|
|
|
3032
2872
|
"--text",
|
|
3033
2873
|
pangoMessage,
|
|
3034
2874
|
"--ok-label",
|
|
3035
|
-
locked ? "Waiting..." : "Allow \u21B5"
|
|
3036
|
-
"--timeout",
|
|
3037
|
-
"300"
|
|
2875
|
+
locked ? "Waiting..." : "Allow \u21B5"
|
|
3038
2876
|
];
|
|
3039
2877
|
if (!locked) {
|
|
3040
2878
|
argsList.push("--cancel-label", "Block \u238B");
|
|
@@ -3314,6 +3152,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3314
3152
|
let policyMatchedWord;
|
|
3315
3153
|
let riskMetadata;
|
|
3316
3154
|
let statefulRecoveryCommand;
|
|
3155
|
+
let localSmartRuleMatched = false;
|
|
3317
3156
|
let taintWarning = null;
|
|
3318
3157
|
if (isNetworkTool(toolName, args)) {
|
|
3319
3158
|
const filePaths = extractFilePaths(toolName, args);
|
|
@@ -3457,6 +3296,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3457
3296
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
3458
3297
|
policyMatchedField = policyResult.matchedField;
|
|
3459
3298
|
policyMatchedWord = policyResult.matchedWord;
|
|
3299
|
+
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
3460
3300
|
riskMetadata = computeRiskMetadata(
|
|
3461
3301
|
args,
|
|
3462
3302
|
policyResult.tier ?? 6,
|
|
@@ -3499,22 +3339,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3499
3339
|
}
|
|
3500
3340
|
let cloudRequestId = null;
|
|
3501
3341
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
3502
|
-
if (cloudEnforced) {
|
|
3342
|
+
if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
3503
3343
|
try {
|
|
3504
3344
|
const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
|
|
3505
3345
|
if (!initResult.pending) {
|
|
3506
3346
|
if (initResult.shadowMode) {
|
|
3507
3347
|
return { approved: true, checkedBy: "cloud" };
|
|
3508
3348
|
}
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3349
|
+
if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
3350
|
+
return {
|
|
3351
|
+
approved: !!initResult.approved,
|
|
3352
|
+
reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
|
|
3353
|
+
checkedBy: initResult.approved ? "cloud" : void 0,
|
|
3354
|
+
blockedBy: initResult.approved ? void 0 : "team-policy",
|
|
3355
|
+
blockedByLabel: "Organization Policy (SaaS)"
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
3360
|
+
cloudRequestId = initResult.requestId || null;
|
|
3516
3361
|
}
|
|
3517
|
-
cloudRequestId = initResult.requestId || null;
|
|
3518
3362
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
3519
3363
|
} catch {
|
|
3520
3364
|
}
|
|
@@ -3560,7 +3404,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3560
3404
|
riskMetadata,
|
|
3561
3405
|
options?.activityId,
|
|
3562
3406
|
options?.cwd,
|
|
3563
|
-
statefulRecoveryCommand
|
|
3407
|
+
statefulRecoveryCommand,
|
|
3408
|
+
void 0,
|
|
3409
|
+
void 0,
|
|
3410
|
+
localSmartRuleMatched || options?.localSmartRuleMatched
|
|
3564
3411
|
);
|
|
3565
3412
|
daemonEntryId = entry.id;
|
|
3566
3413
|
daemonAllowCount = entry.allowCount;
|
|
@@ -3568,7 +3415,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3568
3415
|
}
|
|
3569
3416
|
}
|
|
3570
3417
|
}
|
|
3571
|
-
if (cloudEnforced && cloudRequestId) {
|
|
3418
|
+
if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
3572
3419
|
racePromises.push(
|
|
3573
3420
|
(async () => {
|
|
3574
3421
|
try {
|
|
@@ -5842,6 +5689,12 @@ function estimateToolCost(tool, args) {
|
|
|
5842
5689
|
const newStr = a.new_string ?? "";
|
|
5843
5690
|
return String(newStr).length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
|
|
5844
5691
|
}
|
|
5692
|
+
if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal_execute") {
|
|
5693
|
+
const command = String(a.command ?? a.cmd ?? a.input ?? "");
|
|
5694
|
+
if (command.length > 0) {
|
|
5695
|
+
return command.length / BYTES_PER_TOKEN / 1e6 * OUTPUT_PRICE_PER_1M;
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5845
5698
|
return void 0;
|
|
5846
5699
|
}
|
|
5847
5700
|
function broadcast(event, data) {
|
|
@@ -6230,7 +6083,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
6230
6083
|
viewOnly = false,
|
|
6231
6084
|
fromCLI = false,
|
|
6232
6085
|
activityId,
|
|
6233
|
-
cwd
|
|
6086
|
+
cwd,
|
|
6087
|
+
localSmartRuleMatched = false
|
|
6234
6088
|
} = JSON.parse(body);
|
|
6235
6089
|
const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto6.randomUUID)();
|
|
6236
6090
|
const entry = {
|
|
@@ -6310,7 +6164,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
6310
6164
|
agent: typeof agent === "string" ? agent : void 0,
|
|
6311
6165
|
mcpServer: typeof mcpServer === "string" ? mcpServer : void 0
|
|
6312
6166
|
},
|
|
6313
|
-
{ calledFromDaemon: true }
|
|
6167
|
+
{ calledFromDaemon: true, localSmartRuleMatched: !!localSmartRuleMatched }
|
|
6314
6168
|
).then((result) => {
|
|
6315
6169
|
const e = pending.get(id);
|
|
6316
6170
|
if (!e) return;
|
|
@@ -6973,7 +6827,7 @@ async function ensureDaemon() {
|
|
|
6973
6827
|
} catch {
|
|
6974
6828
|
}
|
|
6975
6829
|
console.log(import_chalk17.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6976
|
-
const child = (0,
|
|
6830
|
+
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6977
6831
|
detached: true,
|
|
6978
6832
|
stdio: "ignore",
|
|
6979
6833
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -7147,6 +7001,7 @@ async function startTail(options = {}) {
|
|
|
7147
7001
|
return;
|
|
7148
7002
|
}
|
|
7149
7003
|
const connectionTime = Date.now();
|
|
7004
|
+
let initialReplayDone = false;
|
|
7150
7005
|
const activityPending = /* @__PURE__ */ new Map();
|
|
7151
7006
|
const orphanedResults = /* @__PURE__ */ new Map();
|
|
7152
7007
|
let csrfToken = "";
|
|
@@ -7342,10 +7197,10 @@ async function startTail(options = {}) {
|
|
|
7342
7197
|
try {
|
|
7343
7198
|
const browserEnabled = getConfig().settings.approvers?.browser !== false;
|
|
7344
7199
|
if (browserEnabled) {
|
|
7345
|
-
if (process.platform === "darwin") (0,
|
|
7200
|
+
if (process.platform === "darwin") (0, import_child_process14.execSync)(`open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
7346
7201
|
else if (process.platform === "win32")
|
|
7347
|
-
(0,
|
|
7348
|
-
else (0,
|
|
7202
|
+
(0, import_child_process14.execSync)(`cmd /c start "" "${dashboardUrl}"`, { stdio: "ignore" });
|
|
7203
|
+
else (0, import_child_process14.execSync)(`xdg-open "${dashboardUrl}"`, { stdio: "ignore" });
|
|
7349
7204
|
const intToken = getInternalToken();
|
|
7350
7205
|
fetch(`http://127.0.0.1:${port}/browser-opened`, {
|
|
7351
7206
|
method: "POST",
|
|
@@ -7478,11 +7333,17 @@ async function startTail(options = {}) {
|
|
|
7478
7333
|
return;
|
|
7479
7334
|
}
|
|
7480
7335
|
if (event === "activity") {
|
|
7336
|
+
const isReplayEvent = data.status && data.status !== "pending";
|
|
7337
|
+
if (isReplayEvent && !initialReplayDone) {
|
|
7338
|
+
renderResult(data, data);
|
|
7339
|
+
return;
|
|
7340
|
+
}
|
|
7481
7341
|
if (!options.history && data.ts > 0 && data.ts < connectionTime) return;
|
|
7482
|
-
if (
|
|
7342
|
+
if (isReplayEvent) {
|
|
7483
7343
|
renderResult(data, data);
|
|
7484
7344
|
return;
|
|
7485
7345
|
}
|
|
7346
|
+
if (!initialReplayDone) initialReplayDone = true;
|
|
7486
7347
|
const orphaned = orphanedResults.get(data.id);
|
|
7487
7348
|
if (orphaned) {
|
|
7488
7349
|
orphanedResults.delete(data.id);
|
|
@@ -7521,7 +7382,7 @@ async function startTail(options = {}) {
|
|
|
7521
7382
|
process.exit(1);
|
|
7522
7383
|
});
|
|
7523
7384
|
}
|
|
7524
|
-
var import_http2, import_chalk17, import_fs25, import_os21, import_path28, import_readline5,
|
|
7385
|
+
var import_http2, import_chalk17, import_fs25, import_os21, import_path28, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
|
|
7525
7386
|
var init_tail = __esm({
|
|
7526
7387
|
"src/tui/tail.ts"() {
|
|
7527
7388
|
"use strict";
|
|
@@ -7531,7 +7392,7 @@ var init_tail = __esm({
|
|
|
7531
7392
|
import_os21 = __toESM(require("os"));
|
|
7532
7393
|
import_path28 = __toESM(require("path"));
|
|
7533
7394
|
import_readline5 = __toESM(require("readline"));
|
|
7534
|
-
|
|
7395
|
+
import_child_process14 = require("child_process");
|
|
7535
7396
|
init_daemon2();
|
|
7536
7397
|
init_daemon();
|
|
7537
7398
|
init_core();
|
|
@@ -7866,6 +7727,24 @@ function renderContextLine(stdin) {
|
|
|
7866
7727
|
async function main() {
|
|
7867
7728
|
try {
|
|
7868
7729
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7730
|
+
if (import_fs26.default.existsSync(import_path29.default.join(import_os22.default.homedir(), ".node9", "hud-debug"))) {
|
|
7731
|
+
try {
|
|
7732
|
+
const logPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "hud-debug.log");
|
|
7733
|
+
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7734
|
+
let size = 0;
|
|
7735
|
+
try {
|
|
7736
|
+
size = import_fs26.default.statSync(logPath).size;
|
|
7737
|
+
} catch {
|
|
7738
|
+
}
|
|
7739
|
+
if (size < MAX_LOG_SIZE) {
|
|
7740
|
+
import_fs26.default.appendFileSync(
|
|
7741
|
+
logPath,
|
|
7742
|
+
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7743
|
+
);
|
|
7744
|
+
}
|
|
7745
|
+
} catch {
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7869
7748
|
if (!daemonStatus2) {
|
|
7870
7749
|
renderOffline();
|
|
7871
7750
|
return;
|
|
@@ -7982,7 +7861,7 @@ function isNode9Hook(cmd) {
|
|
|
7982
7861
|
function teardownClaude() {
|
|
7983
7862
|
const homeDir2 = import_os10.default.homedir();
|
|
7984
7863
|
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
7985
|
-
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
7864
|
+
const mcpPath = import_path14.default.join(homeDir2, ".claude", ".mcp.json");
|
|
7986
7865
|
let changed = false;
|
|
7987
7866
|
const settings = readJson(hooksPath);
|
|
7988
7867
|
if (settings?.hooks) {
|
|
@@ -8008,11 +7887,12 @@ function teardownClaude() {
|
|
|
8008
7887
|
let mcpChanged = false;
|
|
8009
7888
|
if (removeNode9McpServer(claudeConfig.mcpServers)) {
|
|
8010
7889
|
mcpChanged = true;
|
|
8011
|
-
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.claude.json"));
|
|
7890
|
+
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.claude/.mcp.json"));
|
|
8012
7891
|
}
|
|
8013
7892
|
for (const [name, server] of Object.entries(claudeConfig.mcpServers)) {
|
|
8014
|
-
|
|
8015
|
-
|
|
7893
|
+
const args = server.args;
|
|
7894
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7895
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
8016
7896
|
claudeConfig.mcpServers[name] = {
|
|
8017
7897
|
...server,
|
|
8018
7898
|
command: originalCmd,
|
|
@@ -8020,16 +7900,11 @@ function teardownClaude() {
|
|
|
8020
7900
|
};
|
|
8021
7901
|
mcpChanged = true;
|
|
8022
7902
|
} else if (server.command === "node9") {
|
|
8023
|
-
console.warn(
|
|
8024
|
-
import_chalk.default.yellow(
|
|
8025
|
-
` \u26A0\uFE0F Cannot unwrap MCP server "${name}" in ~/.claude.json \u2014 args is empty. Remove it manually.`
|
|
8026
|
-
)
|
|
8027
|
-
);
|
|
8028
7903
|
}
|
|
8029
7904
|
}
|
|
8030
7905
|
if (mcpChanged) {
|
|
8031
7906
|
writeJson(mcpPath, claudeConfig);
|
|
8032
|
-
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.claude.json"));
|
|
7907
|
+
console.log(import_chalk.default.green(" \u2705 Unwrapped MCP servers in ~/.claude/.mcp.json"));
|
|
8033
7908
|
}
|
|
8034
7909
|
}
|
|
8035
7910
|
}
|
|
@@ -8058,8 +7933,9 @@ function teardownGemini() {
|
|
|
8058
7933
|
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.gemini/settings.json"));
|
|
8059
7934
|
}
|
|
8060
7935
|
for (const [name, server] of Object.entries(settings.mcpServers)) {
|
|
8061
|
-
|
|
8062
|
-
|
|
7936
|
+
const args = server.args;
|
|
7937
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7938
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
8063
7939
|
settings.mcpServers[name] = {
|
|
8064
7940
|
...server,
|
|
8065
7941
|
command: originalCmd,
|
|
@@ -8090,8 +7966,9 @@ function teardownCursor() {
|
|
|
8090
7966
|
console.log(import_chalk.default.green(" \u2705 Removed node9 MCP server entry from ~/.cursor/mcp.json"));
|
|
8091
7967
|
}
|
|
8092
7968
|
for (const [name, server] of Object.entries(mcpConfig.mcpServers)) {
|
|
8093
|
-
|
|
8094
|
-
|
|
7969
|
+
const args = server.args;
|
|
7970
|
+
if (server.command === "node9" && Array.isArray(args) && args[0] === "mcp" && args[1] === "--upstream" && typeof args[2] === "string") {
|
|
7971
|
+
const [originalCmd, ...originalArgs] = args[2].split(" ");
|
|
8095
7972
|
mcpConfig.mcpServers[name] = {
|
|
8096
7973
|
...server,
|
|
8097
7974
|
command: originalCmd,
|
|
@@ -8109,7 +7986,7 @@ function teardownCursor() {
|
|
|
8109
7986
|
}
|
|
8110
7987
|
async function setupClaude() {
|
|
8111
7988
|
const homeDir2 = import_os10.default.homedir();
|
|
8112
|
-
const mcpPath = import_path14.default.join(homeDir2, ".claude.json");
|
|
7989
|
+
const mcpPath = import_path14.default.join(homeDir2, ".claude", ".mcp.json");
|
|
8113
7990
|
const hooksPath = import_path14.default.join(homeDir2, ".claude", "settings.json");
|
|
8114
7991
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
8115
7992
|
const settings = readJson(hooksPath) ?? {};
|
|
@@ -8124,7 +8001,7 @@ async function setupClaude() {
|
|
|
8124
8001
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
8125
8002
|
settings.hooks.PreToolUse.push({
|
|
8126
8003
|
matcher: ".*",
|
|
8127
|
-
hooks: [{ type: "command", command: fullPathCommand("check"), timeout:
|
|
8004
|
+
hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 600 }]
|
|
8128
8005
|
});
|
|
8129
8006
|
console.log(import_chalk.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
|
|
8130
8007
|
hooksChanged = true;
|
|
@@ -8150,6 +8027,15 @@ async function setupClaude() {
|
|
|
8150
8027
|
console.log(import_chalk.default.green(" \u2705 node9 MCP server added \u2192 node9 mcp-server"));
|
|
8151
8028
|
anythingChanged = true;
|
|
8152
8029
|
}
|
|
8030
|
+
const hudCommand = fullPathCommand("hud");
|
|
8031
|
+
const statusLineObj = { type: "command", command: hudCommand };
|
|
8032
|
+
const existingStatusLine = settings.statusLine;
|
|
8033
|
+
const existingStatusCommand = typeof existingStatusLine === "object" ? existingStatusLine?.command : existingStatusLine;
|
|
8034
|
+
if (existingStatusCommand !== hudCommand) {
|
|
8035
|
+
settings.statusLine = statusLineObj;
|
|
8036
|
+
hooksChanged = true;
|
|
8037
|
+
anythingChanged = true;
|
|
8038
|
+
}
|
|
8153
8039
|
if (hooksChanged) {
|
|
8154
8040
|
writeJson(hooksPath, settings);
|
|
8155
8041
|
console.log("");
|
|
@@ -8157,20 +8043,24 @@ async function setupClaude() {
|
|
|
8157
8043
|
const serversToWrap = [];
|
|
8158
8044
|
for (const [name, server] of Object.entries(servers)) {
|
|
8159
8045
|
if (!server.command || server.command === "node9") continue;
|
|
8160
|
-
const
|
|
8161
|
-
serversToWrap.push({ name,
|
|
8046
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8047
|
+
serversToWrap.push({ name, upstream });
|
|
8162
8048
|
}
|
|
8163
8049
|
if (serversToWrap.length > 0) {
|
|
8164
8050
|
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
8165
8051
|
console.log(import_chalk.default.white(` ${mcpPath}`));
|
|
8166
|
-
for (const { name,
|
|
8167
|
-
console.log(import_chalk.default.gray(` \u2022 ${name}: "${
|
|
8052
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8053
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8168
8054
|
}
|
|
8169
8055
|
console.log("");
|
|
8170
8056
|
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
8171
8057
|
if (proceed) {
|
|
8172
|
-
for (const { name,
|
|
8173
|
-
servers[name] = {
|
|
8058
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8059
|
+
servers[name] = {
|
|
8060
|
+
...servers[name],
|
|
8061
|
+
command: "node9",
|
|
8062
|
+
args: ["mcp", "--upstream", upstream]
|
|
8063
|
+
};
|
|
8174
8064
|
}
|
|
8175
8065
|
claudeConfig.mcpServers = servers;
|
|
8176
8066
|
writeJson(mcpPath, claudeConfig);
|
|
@@ -8250,20 +8140,24 @@ async function setupGemini() {
|
|
|
8250
8140
|
const serversToWrap = [];
|
|
8251
8141
|
for (const [name, server] of Object.entries(servers)) {
|
|
8252
8142
|
if (!server.command || server.command === "node9") continue;
|
|
8253
|
-
const
|
|
8254
|
-
serversToWrap.push({ name,
|
|
8143
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8144
|
+
serversToWrap.push({ name, upstream });
|
|
8255
8145
|
}
|
|
8256
8146
|
if (serversToWrap.length > 0) {
|
|
8257
8147
|
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
8258
8148
|
console.log(import_chalk.default.white(` ${settingsPath} (mcpServers)`));
|
|
8259
|
-
for (const { name,
|
|
8260
|
-
console.log(import_chalk.default.gray(` \u2022 ${name}: "${
|
|
8149
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8150
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8261
8151
|
}
|
|
8262
8152
|
console.log("");
|
|
8263
8153
|
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
8264
8154
|
if (proceed) {
|
|
8265
|
-
for (const { name,
|
|
8266
|
-
servers[name] = {
|
|
8155
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8156
|
+
servers[name] = {
|
|
8157
|
+
...servers[name],
|
|
8158
|
+
command: "node9",
|
|
8159
|
+
args: ["mcp", "--upstream", upstream]
|
|
8160
|
+
};
|
|
8267
8161
|
}
|
|
8268
8162
|
settings.mcpServers = servers;
|
|
8269
8163
|
writeJson(settingsPath, settings);
|
|
@@ -8322,20 +8216,24 @@ async function setupCursor() {
|
|
|
8322
8216
|
const serversToWrap = [];
|
|
8323
8217
|
for (const [name, server] of Object.entries(servers)) {
|
|
8324
8218
|
if (!server.command || server.command === "node9") continue;
|
|
8325
|
-
const
|
|
8326
|
-
serversToWrap.push({ name,
|
|
8219
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8220
|
+
serversToWrap.push({ name, upstream });
|
|
8327
8221
|
}
|
|
8328
8222
|
if (serversToWrap.length > 0) {
|
|
8329
8223
|
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
8330
8224
|
console.log(import_chalk.default.white(` ${mcpPath}`));
|
|
8331
|
-
for (const { name,
|
|
8332
|
-
console.log(import_chalk.default.gray(` \u2022 ${name}: "${
|
|
8225
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8226
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8333
8227
|
}
|
|
8334
8228
|
console.log("");
|
|
8335
8229
|
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
8336
8230
|
if (proceed) {
|
|
8337
|
-
for (const { name,
|
|
8338
|
-
servers[name] = {
|
|
8231
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8232
|
+
servers[name] = {
|
|
8233
|
+
...servers[name],
|
|
8234
|
+
command: "node9",
|
|
8235
|
+
args: ["mcp", "--upstream", upstream]
|
|
8236
|
+
};
|
|
8339
8237
|
}
|
|
8340
8238
|
mcpConfig.mcpServers = servers;
|
|
8341
8239
|
writeJson(mcpPath, mcpConfig);
|
|
@@ -8398,20 +8296,24 @@ async function setupCodex() {
|
|
|
8398
8296
|
const serversToWrap = [];
|
|
8399
8297
|
for (const [name, server] of Object.entries(servers)) {
|
|
8400
8298
|
if (!server.command || server.command === "node9") continue;
|
|
8401
|
-
const
|
|
8402
|
-
serversToWrap.push({ name,
|
|
8299
|
+
const upstream = [server.command, ...server.args ?? []].join(" ");
|
|
8300
|
+
serversToWrap.push({ name, upstream });
|
|
8403
8301
|
}
|
|
8404
8302
|
if (serversToWrap.length > 0) {
|
|
8405
8303
|
console.log(import_chalk.default.bold("The following existing entries will be modified:\n"));
|
|
8406
8304
|
console.log(import_chalk.default.white(` ${configPath}`));
|
|
8407
|
-
for (const { name,
|
|
8408
|
-
console.log(import_chalk.default.gray(` \u2022 ${name}: "${
|
|
8305
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8306
|
+
console.log(import_chalk.default.gray(` \u2022 ${name}: "${upstream}" \u2192 node9 mcp --upstream "${upstream}"`));
|
|
8409
8307
|
}
|
|
8410
8308
|
console.log("");
|
|
8411
8309
|
const proceed = await (0, import_prompts.confirm)({ message: "Wrap these MCP servers?", default: true });
|
|
8412
8310
|
if (proceed) {
|
|
8413
|
-
for (const { name,
|
|
8414
|
-
servers[name] = {
|
|
8311
|
+
for (const { name, upstream } of serversToWrap) {
|
|
8312
|
+
servers[name] = {
|
|
8313
|
+
...servers[name],
|
|
8314
|
+
command: "node9",
|
|
8315
|
+
args: ["mcp", "--upstream", upstream]
|
|
8316
|
+
};
|
|
8415
8317
|
}
|
|
8416
8318
|
config.mcp_servers = servers;
|
|
8417
8319
|
writeToml(configPath, config);
|
|
@@ -8600,18 +8502,20 @@ async function runProxy(targetCommand) {
|
|
|
8600
8502
|
const cmd = commandParts[0];
|
|
8601
8503
|
const args = commandParts.slice(1);
|
|
8602
8504
|
let executable = cmd;
|
|
8505
|
+
let useShell = false;
|
|
8603
8506
|
try {
|
|
8604
8507
|
const { stdout } = await (0, import_execa.execa)("which", [cmd]);
|
|
8605
8508
|
if (stdout) executable = stdout.trim();
|
|
8606
8509
|
} catch {
|
|
8510
|
+
useShell = true;
|
|
8607
8511
|
}
|
|
8608
8512
|
console.error(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
|
|
8609
|
-
const
|
|
8513
|
+
const spawnEnv = { ...process.env, FORCE_COLOR: "1" };
|
|
8514
|
+
const child = useShell ? (0, import_child_process6.spawn)("/bin/bash", ["-c", targetCommand], {
|
|
8610
8515
|
stdio: ["pipe", "pipe", "inherit"],
|
|
8611
|
-
// We control STDIN and STDOUT
|
|
8612
8516
|
shell: false,
|
|
8613
|
-
env:
|
|
8614
|
-
});
|
|
8517
|
+
env: spawnEnv
|
|
8518
|
+
}) : (0, import_child_process6.spawn)(executable, args, { stdio: ["pipe", "pipe", "inherit"], shell: false, env: spawnEnv });
|
|
8615
8519
|
const agentIn = import_readline.default.createInterface({ input: process.stdin, terminal: false });
|
|
8616
8520
|
agentIn.on("line", async (line) => {
|
|
8617
8521
|
let message;
|
|
@@ -8720,6 +8624,7 @@ async function autoStartDaemonAndWait() {
|
|
|
8720
8624
|
// src/cli/commands/check.ts
|
|
8721
8625
|
var import_chalk5 = __toESM(require("chalk"));
|
|
8722
8626
|
var import_fs18 = __toESM(require("fs"));
|
|
8627
|
+
var import_child_process9 = require("child_process");
|
|
8723
8628
|
var import_path20 = __toESM(require("path"));
|
|
8724
8629
|
var import_os14 = __toESM(require("os"));
|
|
8725
8630
|
init_orchestrator();
|
|
@@ -9103,6 +9008,37 @@ RAW: ${raw}
|
|
|
9103
9008
|
process.exit(0);
|
|
9104
9009
|
}
|
|
9105
9010
|
const config = getConfig(payload.cwd || void 0);
|
|
9011
|
+
if (config.settings.autoStartDaemon && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON) {
|
|
9012
|
+
try {
|
|
9013
|
+
const scriptPath = process.argv[1];
|
|
9014
|
+
if (typeof scriptPath !== "string" || !import_path20.default.isAbsolute(scriptPath))
|
|
9015
|
+
throw new Error("node9: argv[1] is not an absolute path");
|
|
9016
|
+
const resolvedScript = import_fs18.default.realpathSync(scriptPath);
|
|
9017
|
+
const expectedCli = import_fs18.default.realpathSync(import_path20.default.resolve(__dirname, "../../cli.js"));
|
|
9018
|
+
if (resolvedScript !== expectedCli)
|
|
9019
|
+
throw new Error(
|
|
9020
|
+
"node9: daemon spawn aborted \u2014 argv[1] does not resolve to the node9 CLI"
|
|
9021
|
+
);
|
|
9022
|
+
const safeEnv = { ...process.env };
|
|
9023
|
+
for (const key of [
|
|
9024
|
+
"NODE_OPTIONS",
|
|
9025
|
+
"LD_PRELOAD",
|
|
9026
|
+
"LD_LIBRARY_PATH",
|
|
9027
|
+
"DYLD_INSERT_LIBRARIES",
|
|
9028
|
+
"NODE_PATH",
|
|
9029
|
+
"ELECTRON_RUN_AS_NODE"
|
|
9030
|
+
]) {
|
|
9031
|
+
delete safeEnv[key];
|
|
9032
|
+
}
|
|
9033
|
+
const d = (0, import_child_process9.spawn)(process.execPath, [scriptPath, "daemon"], {
|
|
9034
|
+
detached: true,
|
|
9035
|
+
stdio: "ignore",
|
|
9036
|
+
env: { ...safeEnv, NODE9_AUTO_STARTED: "1", NODE9_BROWSER_OPENED: "1" }
|
|
9037
|
+
});
|
|
9038
|
+
d.unref();
|
|
9039
|
+
} catch {
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9106
9042
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
9107
9043
|
const logPath = import_path20.default.join(import_os14.default.homedir(), ".node9", "hook-debug.log");
|
|
9108
9044
|
if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
|
|
@@ -9161,7 +9097,7 @@ RAW: ${raw}
|
|
|
9161
9097
|
}
|
|
9162
9098
|
}) + "\n"
|
|
9163
9099
|
);
|
|
9164
|
-
process.exit(
|
|
9100
|
+
process.exit(2);
|
|
9165
9101
|
};
|
|
9166
9102
|
if (!toolName) {
|
|
9167
9103
|
sendBlock("Node9: unrecognised hook payload \u2014 tool name missing.");
|
|
@@ -9396,6 +9332,27 @@ var import_chalk6 = __toESM(require("chalk"));
|
|
|
9396
9332
|
init_shields();
|
|
9397
9333
|
init_audit();
|
|
9398
9334
|
init_config();
|
|
9335
|
+
|
|
9336
|
+
// src/utils/https-fetch.ts
|
|
9337
|
+
var import_https = __toESM(require("https"));
|
|
9338
|
+
function httpsFetch(url) {
|
|
9339
|
+
return new Promise((resolve, reject) => {
|
|
9340
|
+
import_https.default.get(url, (res) => {
|
|
9341
|
+
if (res.statusCode !== 200) {
|
|
9342
|
+
reject(new Error(`HTTP ${String(res.statusCode)} for ${url}`));
|
|
9343
|
+
res.resume();
|
|
9344
|
+
return;
|
|
9345
|
+
}
|
|
9346
|
+
const chunks = [];
|
|
9347
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
9348
|
+
res.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
9349
|
+
res.on("error", reject);
|
|
9350
|
+
}).on("error", reject);
|
|
9351
|
+
});
|
|
9352
|
+
}
|
|
9353
|
+
|
|
9354
|
+
// src/cli/commands/shield.ts
|
|
9355
|
+
var COMMUNITY_INDEX_URL = "https://raw.githubusercontent.com/node9ai/node9-proxy/main/shields/community/index.json";
|
|
9399
9356
|
function registerShieldCommand(program2) {
|
|
9400
9357
|
const shieldCmd = program2.command("shield").description("Manage pre-packaged security shield templates");
|
|
9401
9358
|
shieldCmd.command("enable <service>").description("Enable a security shield for a specific service").action((service) => {
|
|
@@ -9455,7 +9412,32 @@ function registerShieldCommand(program2) {
|
|
|
9455
9412
|
\u{1F6E1}\uFE0F Shield "${name}" disabled.
|
|
9456
9413
|
`));
|
|
9457
9414
|
});
|
|
9458
|
-
shieldCmd.command("list").description("Show
|
|
9415
|
+
shieldCmd.command("list").description("Show available shields (add --community to browse the marketplace)").option("--community", "List shields available from the community marketplace").action((opts) => {
|
|
9416
|
+
if (opts.community) {
|
|
9417
|
+
console.log(import_chalk6.default.bold("\n\u{1F6E1}\uFE0F Community Shield Marketplace\n"));
|
|
9418
|
+
console.log(import_chalk6.default.gray(" Fetching index\u2026\n"));
|
|
9419
|
+
httpsFetch(COMMUNITY_INDEX_URL).then((body) => {
|
|
9420
|
+
const entries = JSON.parse(body);
|
|
9421
|
+
const installed = new Set(listShields().map((s) => s.name));
|
|
9422
|
+
for (const e of entries) {
|
|
9423
|
+
const tag = installed.has(e.name) ? import_chalk6.default.green("installed") : import_chalk6.default.gray("available");
|
|
9424
|
+
console.log(
|
|
9425
|
+
` ${tag} ${import_chalk6.default.cyan(e.name.padEnd(12))} ${e.description} ${import_chalk6.default.gray(`by ${e.author}`)}`
|
|
9426
|
+
);
|
|
9427
|
+
}
|
|
9428
|
+
console.log("");
|
|
9429
|
+
console.log(
|
|
9430
|
+
import_chalk6.default.gray(` Install a shield: ${import_chalk6.default.cyan("node9 shield install <name>")}
|
|
9431
|
+
`)
|
|
9432
|
+
);
|
|
9433
|
+
}).catch((err2) => {
|
|
9434
|
+
console.error(import_chalk6.default.red(`
|
|
9435
|
+
\u274C Could not fetch community index: ${String(err2)}
|
|
9436
|
+
`));
|
|
9437
|
+
process.exit(1);
|
|
9438
|
+
});
|
|
9439
|
+
return;
|
|
9440
|
+
}
|
|
9459
9441
|
const active = new Set(readActiveShields());
|
|
9460
9442
|
console.log(import_chalk6.default.bold("\n\u{1F6E1}\uFE0F Available Shields\n"));
|
|
9461
9443
|
for (const shield of listShields()) {
|
|
@@ -9465,6 +9447,10 @@ function registerShieldCommand(program2) {
|
|
|
9465
9447
|
console.log(import_chalk6.default.gray(` aliases: ${shield.aliases.join(", ")}`));
|
|
9466
9448
|
}
|
|
9467
9449
|
console.log("");
|
|
9450
|
+
console.log(
|
|
9451
|
+
import_chalk6.default.gray(` Browse community shields: ${import_chalk6.default.cyan("node9 shield list --community")}
|
|
9452
|
+
`)
|
|
9453
|
+
);
|
|
9468
9454
|
});
|
|
9469
9455
|
shieldCmd.command("status").description("Show active shields and their individual rules with verdicts").action(() => {
|
|
9470
9456
|
const active = readActiveShields();
|
|
@@ -9602,6 +9588,52 @@ function registerShieldCommand(program2) {
|
|
|
9602
9588
|
`)
|
|
9603
9589
|
);
|
|
9604
9590
|
});
|
|
9591
|
+
shieldCmd.command("install <name>").description("Install a shield from the community marketplace into ~/.node9/shields/").action((name) => {
|
|
9592
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
9593
|
+
console.error(
|
|
9594
|
+
import_chalk6.default.red(
|
|
9595
|
+
`
|
|
9596
|
+
\u274C Invalid shield name: only alphanumeric characters, hyphens, and underscores are allowed
|
|
9597
|
+
`
|
|
9598
|
+
)
|
|
9599
|
+
);
|
|
9600
|
+
process.exit(1);
|
|
9601
|
+
}
|
|
9602
|
+
console.log(import_chalk6.default.bold(`
|
|
9603
|
+
\u{1F6E1}\uFE0F Installing shield "${name}"\u2026
|
|
9604
|
+
`));
|
|
9605
|
+
httpsFetch(COMMUNITY_INDEX_URL).then((indexBody) => {
|
|
9606
|
+
const entries = JSON.parse(indexBody);
|
|
9607
|
+
const entry = entries.find((e) => e.name === name);
|
|
9608
|
+
if (!entry) {
|
|
9609
|
+
const names = entries.map((e) => import_chalk6.default.cyan(e.name)).join(", ");
|
|
9610
|
+
console.error(
|
|
9611
|
+
import_chalk6.default.red(`\u274C Shield "${name}" not found in the community marketplace.
|
|
9612
|
+
`)
|
|
9613
|
+
);
|
|
9614
|
+
console.error(` Available: ${names}
|
|
9615
|
+
`);
|
|
9616
|
+
process.exit(1);
|
|
9617
|
+
}
|
|
9618
|
+
return httpsFetch(entry.url);
|
|
9619
|
+
}).then((shieldBody) => {
|
|
9620
|
+
const shieldJson = JSON.parse(shieldBody);
|
|
9621
|
+
installShield(name, shieldJson);
|
|
9622
|
+
console.log(
|
|
9623
|
+
import_chalk6.default.green(`\u2705 Shield "${name}" installed to ~/.node9/shields/${name}.json`)
|
|
9624
|
+
);
|
|
9625
|
+
console.log(
|
|
9626
|
+
import_chalk6.default.gray(` Activate it with: ${import_chalk6.default.cyan(`node9 shield enable ${name}`)}
|
|
9627
|
+
`)
|
|
9628
|
+
);
|
|
9629
|
+
appendConfigAudit({ event: "shield-install", shield: name });
|
|
9630
|
+
}).catch((err2) => {
|
|
9631
|
+
console.error(import_chalk6.default.red(`
|
|
9632
|
+
\u274C Install failed: ${String(err2)}
|
|
9633
|
+
`));
|
|
9634
|
+
process.exit(1);
|
|
9635
|
+
});
|
|
9636
|
+
});
|
|
9605
9637
|
}
|
|
9606
9638
|
function registerConfigShowCommand(program2) {
|
|
9607
9639
|
program2.command("config show").description(
|
|
@@ -9675,7 +9707,7 @@ var import_chalk7 = __toESM(require("chalk"));
|
|
|
9675
9707
|
var import_fs20 = __toESM(require("fs"));
|
|
9676
9708
|
var import_path22 = __toESM(require("path"));
|
|
9677
9709
|
var import_os16 = __toESM(require("os"));
|
|
9678
|
-
var
|
|
9710
|
+
var import_child_process10 = require("child_process");
|
|
9679
9711
|
init_daemon();
|
|
9680
9712
|
function registerDoctorCommand(program2, version2) {
|
|
9681
9713
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(() => {
|
|
@@ -9701,7 +9733,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9701
9733
|
`));
|
|
9702
9734
|
section("Binary");
|
|
9703
9735
|
try {
|
|
9704
|
-
const which = (0,
|
|
9736
|
+
const which = (0, import_child_process10.execSync)("which node9", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
9705
9737
|
pass(`node9 found at ${which}`);
|
|
9706
9738
|
} catch {
|
|
9707
9739
|
warn(
|
|
@@ -9719,7 +9751,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
9719
9751
|
);
|
|
9720
9752
|
}
|
|
9721
9753
|
try {
|
|
9722
|
-
const gitVersion = (0,
|
|
9754
|
+
const gitVersion = (0, import_child_process10.execSync)("git --version", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
9723
9755
|
pass(gitVersion);
|
|
9724
9756
|
} catch {
|
|
9725
9757
|
warn(
|
|
@@ -9916,7 +9948,7 @@ function registerAuditCommand(program2) {
|
|
|
9916
9948
|
|
|
9917
9949
|
// src/cli/commands/daemon-cmd.ts
|
|
9918
9950
|
var import_chalk9 = __toESM(require("chalk"));
|
|
9919
|
-
var
|
|
9951
|
+
var import_child_process11 = require("child_process");
|
|
9920
9952
|
init_daemon2();
|
|
9921
9953
|
init_daemon();
|
|
9922
9954
|
function registerDaemonCommand(program2) {
|
|
@@ -9949,7 +9981,7 @@ function registerDaemonCommand(program2) {
|
|
|
9949
9981
|
console.log(import_chalk9.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST}:${DAEMON_PORT}/`));
|
|
9950
9982
|
process.exit(0);
|
|
9951
9983
|
}
|
|
9952
|
-
const child = (0,
|
|
9984
|
+
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
9953
9985
|
detached: true,
|
|
9954
9986
|
stdio: "ignore"
|
|
9955
9987
|
});
|
|
@@ -9964,7 +9996,7 @@ function registerDaemonCommand(program2) {
|
|
|
9964
9996
|
process.exit(0);
|
|
9965
9997
|
}
|
|
9966
9998
|
if (options.background) {
|
|
9967
|
-
const child = (0,
|
|
9999
|
+
const child = (0, import_child_process11.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
9968
10000
|
detached: true,
|
|
9969
10001
|
stdio: "ignore"
|
|
9970
10002
|
});
|
|
@@ -10135,7 +10167,7 @@ var import_chalk11 = __toESM(require("chalk"));
|
|
|
10135
10167
|
var import_fs23 = __toESM(require("fs"));
|
|
10136
10168
|
var import_path25 = __toESM(require("path"));
|
|
10137
10169
|
var import_os19 = __toESM(require("os"));
|
|
10138
|
-
var
|
|
10170
|
+
var import_https2 = __toESM(require("https"));
|
|
10139
10171
|
init_core();
|
|
10140
10172
|
init_shields();
|
|
10141
10173
|
var DEFAULT_SHIELDS = ["bash-safe", "filesystem", "postgres"];
|
|
@@ -10147,7 +10179,7 @@ function fireTelemetryPing(agents) {
|
|
|
10147
10179
|
os: process.platform,
|
|
10148
10180
|
node9_version: process.env.npm_package_version ?? "unknown"
|
|
10149
10181
|
});
|
|
10150
|
-
const req =
|
|
10182
|
+
const req = import_https2.default.request(
|
|
10151
10183
|
{
|
|
10152
10184
|
hostname: "api.node9.ai",
|
|
10153
10185
|
path: "/api/v1/telemetry",
|
|
@@ -10552,7 +10584,7 @@ function registerUndoCommand(program2) {
|
|
|
10552
10584
|
|
|
10553
10585
|
// src/cli/commands/watch.ts
|
|
10554
10586
|
var import_chalk14 = __toESM(require("chalk"));
|
|
10555
|
-
var
|
|
10587
|
+
var import_child_process12 = require("child_process");
|
|
10556
10588
|
init_daemon();
|
|
10557
10589
|
function registerWatchCommand(program2) {
|
|
10558
10590
|
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) => {
|
|
@@ -10569,7 +10601,7 @@ function registerWatchCommand(program2) {
|
|
|
10569
10601
|
}
|
|
10570
10602
|
} catch {
|
|
10571
10603
|
console.error(import_chalk14.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
10572
|
-
const child = (0,
|
|
10604
|
+
const child = (0, import_child_process12.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
10573
10605
|
detached: true,
|
|
10574
10606
|
stdio: "ignore",
|
|
10575
10607
|
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
@@ -10599,7 +10631,7 @@ function registerWatchCommand(program2) {
|
|
|
10599
10631
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
10600
10632
|
)
|
|
10601
10633
|
);
|
|
10602
|
-
const result = (0,
|
|
10634
|
+
const result = (0, import_child_process12.spawnSync)(cmd, args, {
|
|
10603
10635
|
stdio: "inherit",
|
|
10604
10636
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
10605
10637
|
});
|
|
@@ -10614,7 +10646,7 @@ function registerWatchCommand(program2) {
|
|
|
10614
10646
|
// src/mcp-gateway/index.ts
|
|
10615
10647
|
var import_readline3 = __toESM(require("readline"));
|
|
10616
10648
|
var import_chalk15 = __toESM(require("chalk"));
|
|
10617
|
-
var
|
|
10649
|
+
var import_child_process13 = require("child_process");
|
|
10618
10650
|
var import_execa3 = require("execa");
|
|
10619
10651
|
init_orchestrator();
|
|
10620
10652
|
init_provenance();
|
|
@@ -10702,7 +10734,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10702
10734
|
const safeEnv = Object.fromEntries(
|
|
10703
10735
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
10704
10736
|
);
|
|
10705
|
-
const child = (0,
|
|
10737
|
+
const child = (0, import_child_process13.spawn)(executable, cmdArgs, {
|
|
10706
10738
|
stdio: ["pipe", "pipe", "inherit"],
|
|
10707
10739
|
// control stdin/stdout; inherit stderr
|
|
10708
10740
|
shell: false,
|
|
@@ -10785,8 +10817,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10785
10817
|
return;
|
|
10786
10818
|
} finally {
|
|
10787
10819
|
authPending = false;
|
|
10788
|
-
|
|
10789
|
-
|
|
10820
|
+
if (deferredStdinEnd) {
|
|
10821
|
+
child.stdin.end();
|
|
10822
|
+
} else {
|
|
10823
|
+
agentIn.resume();
|
|
10824
|
+
}
|
|
10790
10825
|
if (deferredExitCode !== null) process.exit(deferredExitCode);
|
|
10791
10826
|
}
|
|
10792
10827
|
return;
|
|
@@ -11521,7 +11556,43 @@ registerMcpGatewayCommand(program);
|
|
|
11521
11556
|
registerMcpServerCommand(program);
|
|
11522
11557
|
registerCheckCommand(program);
|
|
11523
11558
|
registerLogCommand(program);
|
|
11524
|
-
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").
|
|
11559
|
+
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
11560
|
+
"after",
|
|
11561
|
+
`
|
|
11562
|
+
Outputs up to 3 lines to stdout, then exits:
|
|
11563
|
+
|
|
11564
|
+
Line 1 \u2014 Security state (always shown):
|
|
11565
|
+
\u{1F6E1} node9 | <mode> [shields] | \u2705 allowed \u{1F6D1} blocked \u{1F6A8} dlp ~$cost
|
|
11566
|
+
Shows "offline" if the node9 daemon is not running.
|
|
11567
|
+
|
|
11568
|
+
Line 2 \u2014 Claude context & rate limits (shown when available):
|
|
11569
|
+
<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)
|
|
11570
|
+
Only appears when Claude Code passes context_window / rate_limits data via stdin.
|
|
11571
|
+
|
|
11572
|
+
Line 3 \u2014 Environment counts (shown when non-zero):
|
|
11573
|
+
2 CLAUDE.md | 5 rules | 4 MCPs | 3 hooks
|
|
11574
|
+
Counts CLAUDE.md files, rules/, MCP servers, and hook entries across user + project scope.
|
|
11575
|
+
Disable with: { "settings": { "hud": { "showEnvironmentCounts": false } } } in node9.config.json
|
|
11576
|
+
|
|
11577
|
+
Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
11578
|
+
Run "node9 addto claude" to register it as the statusLine.`
|
|
11579
|
+
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
11580
|
+
if (subcommand === "debug") {
|
|
11581
|
+
const flagFile = import_path30.default.join(import_os23.default.homedir(), ".node9", "hud-debug");
|
|
11582
|
+
if (state === "on") {
|
|
11583
|
+
import_fs27.default.mkdirSync(import_path30.default.dirname(flagFile), { recursive: true });
|
|
11584
|
+
import_fs27.default.writeFileSync(flagFile, "");
|
|
11585
|
+
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
11586
|
+
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
11587
|
+
} else if (state === "off") {
|
|
11588
|
+
if (import_fs27.default.existsSync(flagFile)) import_fs27.default.unlinkSync(flagFile);
|
|
11589
|
+
console.log("HUD debug logging disabled.");
|
|
11590
|
+
} else {
|
|
11591
|
+
console.error("Usage: node9 hud debug on|off");
|
|
11592
|
+
process.exit(1);
|
|
11593
|
+
}
|
|
11594
|
+
return;
|
|
11595
|
+
}
|
|
11525
11596
|
const { main: main2 } = await Promise.resolve().then(() => (init_hud(), hud_exports));
|
|
11526
11597
|
await main2();
|
|
11527
11598
|
});
|