@node9/proxy 1.20.1 → 1.21.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/README.md CHANGED
@@ -8,21 +8,33 @@
8
8
  <a href="https://huggingface.co/spaces/Node9ai/node9-security-demo"><img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/open-in-hf-spaces-sm.svg" alt="Try on HF Spaces" /></a>
9
9
  </p>
10
10
 
11
- Node9 sits between your AI agent and the tools it can use — recording every action, intervening on risky ones, and showing you what happened both live and in retrospect.
11
+ Node9 sits between your AI agent and the tools it can use — **discover** what it's already been doing, **protect** against risky actions in real time, and **review** what happened over any time window.
12
12
 
13
13
  Works with **Claude Code · Codex CLI · Gemini CLI · Cursor · Windsurf · any MCP server**.
14
14
 
15
15
  ## What Node9 does
16
16
 
17
- - 🛡 **Review or block** risky commands before they run `rm -rf`, `git push --force`, `DROP TABLE`, credential reads, `curl | bash`
18
- - 🔍 **Scan** what your AI has already been doingloops, leaked secrets, blocked operations across every session
19
- - 🔑 **Catch credential leaks** — AWS keys, GitHub tokens, JWTs, GCP API keys, PEM private keys flagged in tool args, file contents, and shell config
20
- - 🔭 **Map your blast radius** — every SSH key, AWS credential, and `.env` file an AI agent on this machine could reach right now
17
+ - 🔍 **Discover** scan every past AI session for credential leaks, agent loops, blocked operations, and every secret on disk an agent could reach right now
18
+ - 🛡 **Protect** review or block risky commands before they run `rm -rf`, `git push --force`, `DROP TABLE`, credential reads, `curl | bash`, AWS/GitHub/Stripe key leaks
19
+ - 📊 **Review** — period-windowed report (today / week / month / 90 days) cost per agent, top tools, shields fired, blast radius
20
+
21
+ ## Retrospective scan
22
+
23
+ This is my own machine — 90 days while building Node9. Score 25/100, 5 credential files an AI agent could reach right now.
24
+
25
+ ```bash
26
+ npx node9-ai scan # before installation, runs in ~10s, nothing uploads
27
+ node9 scan # after installation, same output
28
+ ```
29
+
30
+ <p align="center">
31
+ <img src="https://github.com/user-attachments/assets/7c5b30f1-1ca1-40b4-bfd5-d6671002e98e" width="720" alt="Node9 scan scorecard" />
32
+ </p>
21
33
 
22
34
  ## Live monitoring
23
35
 
24
36
  <p align="center">
25
- <img src="https://github.com/user-attachments/assets/997b7b42-b251-4046-b9c5-e000f8b5a481" width="720" alt="Node9 monitor dashboard" />
37
+ <img src="https://github.com/user-attachments/assets/4661da97-c174-4bae-ae54-4c52a1d69213" width="720" alt="Node9 monitor dashboard" />
26
38
  </p>
27
39
 
28
40
  `node9 monitor` opens an interactive terminal dashboard with two views:
@@ -30,31 +42,18 @@ Works with **Claude Code · Codex CLI · Gemini CLI · Cursor · Windsurf · any
30
42
  - **`[1]` Realtime** — live activity, approvals, security alerts, current risk score
31
43
  - **`[2]` Report** — period-windowed summary: cost, top tools, shields fired, blast radius
32
44
 
33
- ## Retrospective scan
45
+ ## Report
34
46
 
35
- This is my own machine 30 days while building Node9. Score 25/100, 5 credential files an AI agent could reach right now.
47
+ Press `[2]` in monitor for a period-windowed summary. Toggle the window with `[T]oday` · `[W]eek` · `[M]onth` · `[N]inety` same panels as the scan above, driven by your post-install audit log.
36
48
 
37
49
  <p align="center">
38
- <img src="https://github.com/user-attachments/assets/bc165779-4200-438d-967a-20d42bbfe69e" width="720" alt="Node9 scan scorecard" />
50
+ <img src="https://github.com/user-attachments/assets/66c02a72-e477-443d-807f-d65a21d096cd" width="720" alt="Node9 monitor [2] Report" />
39
51
  </p>
40
52
 
53
+ ```bash
54
+ node9 monitor # press [2] for Report view
55
+ node9 report --period 7d # CLI form, no TUI
41
56
  ```
42
- 🛡 Node9 Scan · 21 sessions · 8,114 tool calls · Apr 6 – May 1, 2026
43
-
44
- Security Score: 25/100 · Critical
45
- $3,789 AI spend · 62 risky operations
46
-
47
- 🔑 14 credential leak (Bearer Token ×4, GCP API Key ×4, JWT ×2)
48
- 🛑 15 would have blocked (force-push ×5, read-ssh ×4, read-aws ×4)
49
- 🔁 193 agent loops (18% wasted · ~$6.51)
50
- 👁 33 flagged for review (git-destructive ×19, rm ×9, sudo ×2)
51
-
52
- 🔭 Blast radius ssh × gcp × npm × other (5 exposures)
53
-
54
- → npx node9-ai scan run this on your machine
55
- ```
56
-
57
- Run it on yours — `npx node9-ai scan` finishes in ~10 seconds and runs entirely local. Nothing uploads.
58
57
 
59
58
  ## Install
60
59
 
@@ -141,17 +140,17 @@ node9 mcp pin reset # clear all pins
141
140
 
142
141
  </details>
143
142
 
144
- ## Observability — five views
145
-
146
- | Command | What it shows | When to use |
147
- | ---------------- | --------------------------------------------------------- | ----------------------------------------- |
148
- | `node9 blast` | What an AI agent can reach right now — files, creds, env | First thing to run on any machine |
149
- | `node9 scan` | Retrospective audit of existing agent history | Before installing, or to review past risk |
150
- | `node9 tail` | Live stream of every tool call | Watching an agent work in real time |
151
- | `node9 report` | Per-period summary: allowed/blocked/DLP/cost + top tools | Reviewing what happened after a session |
152
- | `node9 sessions` | Session history with prompt, tool trace, cost, snapshot | Reviewing a handoff or past work |
153
- | `node9 dlp` | Credential-leak findings in Claude response text | Any time a DLP desktop alert fires |
154
- | `node9 mask` | Redact plaintext secrets from local session history files | After a DLP finding — cleans local disk |
143
+ ## Other commands
144
+
145
+ Beyond the three flow commands above (`scan` / `monitor` / `report`):
146
+
147
+ | Command | What it shows | When to use |
148
+ | ---------------- | --------------------------------------------------------- | --------------------------------------- |
149
+ | `node9 blast` | What an AI agent can reach right now — files, creds, env | First thing to run on any machine |
150
+ | `node9 tail` | Live stream of every tool call (text-only, no TUI) | Piping into other tools, CI, logs |
151
+ | `node9 sessions` | Session history with prompt, tool trace, cost, snapshot | Reviewing a handoff or past work |
152
+ | `node9 dlp` | Credential-leak findings in Claude response text | Any time a DLP desktop alert fires |
153
+ | `node9 mask` | Redact plaintext secrets from local session history files | After a DLP finding — cleans local disk |
155
154
 
156
155
  Plus a **live HUD** in your Claude Code statusline:
157
156
 
package/dist/cli.js CHANGED
@@ -10505,7 +10505,7 @@ function originForRule(ruleName, sections) {
10505
10505
  return "";
10506
10506
  }
10507
10507
  function registerScanCommand(program2) {
10508
- program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option(
10508
+ program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option("--classic", "Original chalk-based scorecard layout (default: new Ink-rendered view)").option(
10509
10509
  "--json",
10510
10510
  "Emit machine-readable JSON to stdout (suppresses banner, progress, and renderer)"
10511
10511
  ).option(
@@ -10539,7 +10539,8 @@ function registerScanCommand(program2) {
10539
10539
  })();
10540
10540
  const isWired = getAgentsStatus().some((a) => a.wired);
10541
10541
  const screenshotMode = options.compact || options.narrative;
10542
- const quiet = screenshotMode || options.json;
10542
+ const useInk = !options.classic && !drillDown;
10543
+ const quiet = screenshotMode || options.json || useInk;
10543
10544
  if (!quiet) {
10544
10545
  console.log("");
10545
10546
  if (!isWired) {
@@ -10677,6 +10678,7 @@ function registerScanCommand(program2) {
10677
10678
  });
10678
10679
  return;
10679
10680
  }
10681
+ const useInkForHero = !options.classic && !drillDown;
10680
10682
  if (totalFindings === 0 && scan.dlpFindings.length === 0) {
10681
10683
  console.log(import_chalk5.default.green(" \u2705 No risky operations found in your history."));
10682
10684
  console.log(
@@ -10696,9 +10698,11 @@ function registerScanCommand(program2) {
10696
10698
  const since = daysAgo === 0 ? "today" : daysAgo === 1 ? "yesterday" : `${daysAgo} days ago`;
10697
10699
  return import_chalk5.default.dim(" \xB7 ") + arrow + import_chalk5.default.dim(` since ${since}`);
10698
10700
  })();
10699
- console.log(
10700
- " " + (score.band === "critical" ? import_chalk5.default.red.bold("\u26A0 ") : "") + import_chalk5.default.bold("Security Score ") + score.color.bold(`${blast.score}/100`) + " " + severityDisplay + trendSuffix + import_chalk5.default.dim(" \xB7 ") + (totalRisky > 0 ? import_chalk5.default.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) : import_chalk5.default.green("No risky operations"))
10701
- );
10701
+ if (!useInkForHero) {
10702
+ console.log(
10703
+ " " + (score.band === "critical" ? import_chalk5.default.red.bold("\u26A0 ") : "") + import_chalk5.default.bold("Security Score ") + score.color.bold(`${blast.score}/100`) + " " + severityDisplay + trendSuffix + import_chalk5.default.dim(" \xB7 ") + (totalRisky > 0 ? import_chalk5.default.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) : import_chalk5.default.green("No risky operations"))
10704
+ );
10705
+ }
10702
10706
  const cardParts = [];
10703
10707
  if (scan.dlpFindings.length > 0) {
10704
10708
  cardParts.push(
@@ -10727,10 +10731,10 @@ function registerScanCommand(program2) {
10727
10731
  import_chalk5.default.red("\u{1F52D} ") + import_chalk5.default.red.bold(String(blastExposures)) + import_chalk5.default.dim(" exposures")
10728
10732
  );
10729
10733
  }
10730
- if (cardParts.length > 0) {
10734
+ if (cardParts.length > 0 && !useInkForHero) {
10731
10735
  console.log(" " + cardParts.join(import_chalk5.default.dim(" ")));
10732
10736
  }
10733
- if (scan.totalCostUSD > 0) {
10737
+ if (scan.totalCostUSD > 0 && !useInkForHero) {
10734
10738
  console.log(
10735
10739
  " " + import_chalk5.default.dim("AI spend ") + import_chalk5.default.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? import_chalk5.default.dim(" \xB7 wasted on loops ") + import_chalk5.default.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
10736
10740
  );
@@ -10742,16 +10746,38 @@ function registerScanCommand(program2) {
10742
10746
  )
10743
10747
  );
10744
10748
  }
10745
- console.log("");
10749
+ if (!useInkForHero) {
10750
+ console.log("");
10751
+ }
10746
10752
  if (!drillDown) {
10747
- renderPanelScorecard({
10748
- scan,
10749
- summary,
10750
- blast,
10751
- blastExposures,
10752
- blockedCount,
10753
- reviewCount
10754
- });
10753
+ const useInk2 = !options.classic;
10754
+ if (useInk2) {
10755
+ const scanInkPath = import_path21.default.join(__dirname, "scan-ink.mjs");
10756
+ const dynamicImport = new Function("id", "return import(id)");
10757
+ const mod = await dynamicImport(`file://${scanInkPath}`);
10758
+ const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
10759
+ mod.renderScanScorecardInk(
10760
+ {
10761
+ scan,
10762
+ summary,
10763
+ blast,
10764
+ blastExposures,
10765
+ blockedCount,
10766
+ reviewCount
10767
+ },
10768
+ rangeLabel2
10769
+ );
10770
+ console.log("");
10771
+ } else {
10772
+ renderPanelScorecard({
10773
+ scan,
10774
+ summary,
10775
+ blast,
10776
+ blastExposures,
10777
+ blockedCount,
10778
+ reviewCount
10779
+ });
10780
+ }
10755
10781
  const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
10756
10782
  console.log(" " + import_chalk5.default.green(cta));
10757
10783
  console.log(
package/dist/cli.mjs CHANGED
@@ -10485,7 +10485,7 @@ function originForRule(ruleName, sections) {
10485
10485
  return "";
10486
10486
  }
10487
10487
  function registerScanCommand(program2) {
10488
- program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option(
10488
+ program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option("--classic", "Original chalk-based scorecard layout (default: new Ink-rendered view)").option(
10489
10489
  "--json",
10490
10490
  "Emit machine-readable JSON to stdout (suppresses banner, progress, and renderer)"
10491
10491
  ).option(
@@ -10519,7 +10519,8 @@ function registerScanCommand(program2) {
10519
10519
  })();
10520
10520
  const isWired = getAgentsStatus().some((a) => a.wired);
10521
10521
  const screenshotMode = options.compact || options.narrative;
10522
- const quiet = screenshotMode || options.json;
10522
+ const useInk = !options.classic && !drillDown;
10523
+ const quiet = screenshotMode || options.json || useInk;
10523
10524
  if (!quiet) {
10524
10525
  console.log("");
10525
10526
  if (!isWired) {
@@ -10657,6 +10658,7 @@ function registerScanCommand(program2) {
10657
10658
  });
10658
10659
  return;
10659
10660
  }
10661
+ const useInkForHero = !options.classic && !drillDown;
10660
10662
  if (totalFindings === 0 && scan.dlpFindings.length === 0) {
10661
10663
  console.log(chalk5.green(" \u2705 No risky operations found in your history."));
10662
10664
  console.log(
@@ -10676,9 +10678,11 @@ function registerScanCommand(program2) {
10676
10678
  const since = daysAgo === 0 ? "today" : daysAgo === 1 ? "yesterday" : `${daysAgo} days ago`;
10677
10679
  return chalk5.dim(" \xB7 ") + arrow + chalk5.dim(` since ${since}`);
10678
10680
  })();
10679
- console.log(
10680
- " " + (score.band === "critical" ? chalk5.red.bold("\u26A0 ") : "") + chalk5.bold("Security Score ") + score.color.bold(`${blast.score}/100`) + " " + severityDisplay + trendSuffix + chalk5.dim(" \xB7 ") + (totalRisky > 0 ? chalk5.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) : chalk5.green("No risky operations"))
10681
- );
10681
+ if (!useInkForHero) {
10682
+ console.log(
10683
+ " " + (score.band === "critical" ? chalk5.red.bold("\u26A0 ") : "") + chalk5.bold("Security Score ") + score.color.bold(`${blast.score}/100`) + " " + severityDisplay + trendSuffix + chalk5.dim(" \xB7 ") + (totalRisky > 0 ? chalk5.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) : chalk5.green("No risky operations"))
10684
+ );
10685
+ }
10682
10686
  const cardParts = [];
10683
10687
  if (scan.dlpFindings.length > 0) {
10684
10688
  cardParts.push(
@@ -10707,10 +10711,10 @@ function registerScanCommand(program2) {
10707
10711
  chalk5.red("\u{1F52D} ") + chalk5.red.bold(String(blastExposures)) + chalk5.dim(" exposures")
10708
10712
  );
10709
10713
  }
10710
- if (cardParts.length > 0) {
10714
+ if (cardParts.length > 0 && !useInkForHero) {
10711
10715
  console.log(" " + cardParts.join(chalk5.dim(" ")));
10712
10716
  }
10713
- if (scan.totalCostUSD > 0) {
10717
+ if (scan.totalCostUSD > 0 && !useInkForHero) {
10714
10718
  console.log(
10715
10719
  " " + chalk5.dim("AI spend ") + chalk5.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? chalk5.dim(" \xB7 wasted on loops ") + chalk5.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
10716
10720
  );
@@ -10722,16 +10726,38 @@ function registerScanCommand(program2) {
10722
10726
  )
10723
10727
  );
10724
10728
  }
10725
- console.log("");
10729
+ if (!useInkForHero) {
10730
+ console.log("");
10731
+ }
10726
10732
  if (!drillDown) {
10727
- renderPanelScorecard({
10728
- scan,
10729
- summary,
10730
- blast,
10731
- blastExposures,
10732
- blockedCount,
10733
- reviewCount
10734
- });
10733
+ const useInk2 = !options.classic;
10734
+ if (useInk2) {
10735
+ const scanInkPath = path21.join(__dirname, "scan-ink.mjs");
10736
+ const dynamicImport = new Function("id", "return import(id)");
10737
+ const mod = await dynamicImport(`file://${scanInkPath}`);
10738
+ const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
10739
+ mod.renderScanScorecardInk(
10740
+ {
10741
+ scan,
10742
+ summary,
10743
+ blast,
10744
+ blastExposures,
10745
+ blockedCount,
10746
+ reviewCount
10747
+ },
10748
+ rangeLabel2
10749
+ );
10750
+ console.log("");
10751
+ } else {
10752
+ renderPanelScorecard({
10753
+ scan,
10754
+ summary,
10755
+ blast,
10756
+ blastExposures,
10757
+ blockedCount,
10758
+ reviewCount
10759
+ });
10760
+ }
10735
10761
  const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
10736
10762
  console.log(" " + chalk5.green(cta));
10737
10763
  console.log(