oc-inspector 1.3.0 → 1.3.1
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/bin/cli.mjs +250 -45
- package/package.json +1 -1
- package/src/proxy.mjs +19 -0
package/bin/cli.mjs
CHANGED
|
@@ -38,6 +38,17 @@ import { fileURLToPath } from "node:url";
|
|
|
38
38
|
const __filename = fileURLToPath(import.meta.url);
|
|
39
39
|
const args = process.argv.slice(2);
|
|
40
40
|
|
|
41
|
+
// ── ANSI escapes & column widths (must be before command routing) ──
|
|
42
|
+
const E = {
|
|
43
|
+
R: "\x1b[0m", B: "\x1b[1m", D: "\x1b[90m",
|
|
44
|
+
RED: "\x1b[31m", GRN: "\x1b[32m", YEL: "\x1b[33m",
|
|
45
|
+
CYN: "\x1b[36m", ORG: "\x1b[38;5;208m",
|
|
46
|
+
BG: "\x1b[48;5;236m",
|
|
47
|
+
CLR: "\x1b[2J\x1b[H",
|
|
48
|
+
HIDE: "\x1b[?25l", SHOW: "\x1b[?25h",
|
|
49
|
+
};
|
|
50
|
+
const COL = { name: 24, bar: 20, tokens: 8, reqs: 7, cost: 10 };
|
|
51
|
+
|
|
41
52
|
/** Inspector state directory for PID/log files. */
|
|
42
53
|
const INSPECTOR_DIR = join(homedir(), ".openclaw", ".inspector-runtime");
|
|
43
54
|
|
|
@@ -59,6 +70,7 @@ function parseArgs(argv) {
|
|
|
59
70
|
open: false,
|
|
60
71
|
config: undefined,
|
|
61
72
|
json: false,
|
|
73
|
+
live: false,
|
|
62
74
|
days: 7,
|
|
63
75
|
lines: 50,
|
|
64
76
|
help: false,
|
|
@@ -67,7 +79,7 @@ function parseArgs(argv) {
|
|
|
67
79
|
"start", "stop", "run", "restart",
|
|
68
80
|
"enable", "disable", "status",
|
|
69
81
|
"stats", "providers", "history", "pricing", "config",
|
|
70
|
-
"logs", "_serve",
|
|
82
|
+
"logs", "help", "_serve",
|
|
71
83
|
]);
|
|
72
84
|
for (let i = 0; i < argv.length; i++) {
|
|
73
85
|
const arg = argv[i];
|
|
@@ -76,6 +88,7 @@ function parseArgs(argv) {
|
|
|
76
88
|
if (arg === "--open") { opts.open = true; continue; }
|
|
77
89
|
if (arg === "--config" && argv[i + 1]) { opts.config = argv[++i]; continue; }
|
|
78
90
|
if (arg === "--json") { opts.json = true; continue; }
|
|
91
|
+
if (arg === "--live" || arg === "-w") { opts.live = true; continue; }
|
|
79
92
|
if (arg === "--days" && argv[i + 1]) { opts.days = parseInt(argv[++i], 10); continue; }
|
|
80
93
|
if (arg === "--lines" && argv[i + 1]) { opts.lines = parseInt(argv[++i], 10); continue; }
|
|
81
94
|
if (arg === "--help" || arg === "-h") { opts.help = true; continue; }
|
|
@@ -117,6 +130,7 @@ if (opts.help) {
|
|
|
117
130
|
--open Auto-open the dashboard in a browser
|
|
118
131
|
--config <path> Custom path to openclaw.json
|
|
119
132
|
--json Output as JSON (for stats, status, providers, history)
|
|
133
|
+
--live, -w Live auto-refreshing stats (for stats command)
|
|
120
134
|
--days <number> Number of days to show in history (default: 7)
|
|
121
135
|
--lines <number> Number of log lines to show (default: 50)
|
|
122
136
|
--help, -h Show this help message
|
|
@@ -128,7 +142,8 @@ if (opts.help) {
|
|
|
128
142
|
npx oc-inspector restart # Restart daemon
|
|
129
143
|
npx oc-inspector enable # Enable interception
|
|
130
144
|
npx oc-inspector disable # Disable interception
|
|
131
|
-
npx oc-inspector stats # Show
|
|
145
|
+
npx oc-inspector stats # Show token stats
|
|
146
|
+
npx oc-inspector stats --live # Live auto-refreshing dashboard
|
|
132
147
|
npx oc-inspector stats --json # Stats as JSON
|
|
133
148
|
npx oc-inspector history --days 30 # Last 30 days
|
|
134
149
|
npx oc-inspector pricing # Show pricing table
|
|
@@ -141,6 +156,15 @@ if (opts.help) {
|
|
|
141
156
|
// Command routing
|
|
142
157
|
// ═══════════════════════════════════════════════════════════════
|
|
143
158
|
|
|
159
|
+
// help: show commands
|
|
160
|
+
if (opts.command === "help") {
|
|
161
|
+
console.log("");
|
|
162
|
+
console.log(" \x1b[38;5;208m🦞 oc-inspector\x1b[0m — Real-time API traffic inspector for OpenClaw");
|
|
163
|
+
console.log("");
|
|
164
|
+
printCommandsHelp();
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
|
|
144
168
|
// Hidden: _serve is the actual foreground server (spawned by start)
|
|
145
169
|
if (opts.command === "_serve") {
|
|
146
170
|
await runServe(opts);
|
|
@@ -212,8 +236,7 @@ async function runDaemonStart(opts) {
|
|
|
212
236
|
console.log(` \x1b[33m⚠\x1b[0m Already running (PID ${running.pid})`);
|
|
213
237
|
console.log(` \x1b[32m✓\x1b[0m Dashboard: http://127.0.0.1:${opts.port}`);
|
|
214
238
|
console.log("");
|
|
215
|
-
|
|
216
|
-
console.log("");
|
|
239
|
+
printCommandsHelp();
|
|
217
240
|
return;
|
|
218
241
|
}
|
|
219
242
|
|
|
@@ -254,9 +277,7 @@ async function runDaemonStart(opts) {
|
|
|
254
277
|
console.log(` \x1b[32m✓\x1b[0m Dashboard: http://127.0.0.1:${opts.port}`);
|
|
255
278
|
console.log(` \x1b[32m✓\x1b[0m Log file: ${LOG_FILE}`);
|
|
256
279
|
console.log("");
|
|
257
|
-
|
|
258
|
-
console.log(` \x1b[90mView logs:\x1b[0m oc-inspector logs`);
|
|
259
|
-
console.log("");
|
|
280
|
+
printCommandsHelp();
|
|
260
281
|
} else {
|
|
261
282
|
console.log(` \x1b[31m✗ Failed to start — check logs:\x1b[0m`);
|
|
262
283
|
console.log(` ${LOG_FILE}`);
|
|
@@ -516,6 +537,10 @@ async function runRemoteCommand(opts) {
|
|
|
516
537
|
const base = `http://127.0.0.1:${opts.port}`;
|
|
517
538
|
|
|
518
539
|
if (opts.command === "stats") {
|
|
540
|
+
if (opts.live) {
|
|
541
|
+
await runLiveStats(base);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
519
544
|
const data = await fetchApi(`${base}/api/stats`);
|
|
520
545
|
if (!data) return;
|
|
521
546
|
if (opts.json) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
@@ -564,6 +589,36 @@ async function runRemoteCommand(opts) {
|
|
|
564
589
|
// Helpers / Printers
|
|
565
590
|
// ═══════════════════════════════════════════════════════════════
|
|
566
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Print a compact command reference table.
|
|
594
|
+
* Shown after successful daemon start and via `help` command.
|
|
595
|
+
*/
|
|
596
|
+
function printCommandsHelp() {
|
|
597
|
+
const C = "\x1b[36m"; // cyan
|
|
598
|
+
const D = "\x1b[90m"; // dim
|
|
599
|
+
const R = "\x1b[0m"; // reset
|
|
600
|
+
const B = "\x1b[1m"; // bold
|
|
601
|
+
|
|
602
|
+
console.log(` ${B}Commands:${R}`);
|
|
603
|
+
console.log(` ${C}oc-inspector${R} Start daemon ${D}(default)${R}`);
|
|
604
|
+
console.log(` ${C}oc-inspector stop${R} Stop daemon`);
|
|
605
|
+
console.log(` ${C}oc-inspector restart${R} Restart daemon`);
|
|
606
|
+
console.log(` ${C}oc-inspector run${R} Foreground mode ${D}(interactive)${R}`);
|
|
607
|
+
console.log(` ${C}oc-inspector status${R} Daemon + interception status`);
|
|
608
|
+
console.log(` ${C}oc-inspector enable${R} Enable interception`);
|
|
609
|
+
console.log(` ${C}oc-inspector disable${R} Disable interception`);
|
|
610
|
+
console.log(` ${C}oc-inspector stats${R} Token/cost statistics ${D}(--live for auto-refresh)${R}`);
|
|
611
|
+
console.log(` ${C}oc-inspector history${R} Daily usage history`);
|
|
612
|
+
console.log(` ${C}oc-inspector pricing${R} Model pricing table`);
|
|
613
|
+
console.log(` ${C}oc-inspector providers${R} Active providers list`);
|
|
614
|
+
console.log(` ${C}oc-inspector config${R} Inspector config info`);
|
|
615
|
+
console.log(` ${C}oc-inspector logs${R} Daemon log output`);
|
|
616
|
+
console.log(` ${C}oc-inspector help${R} Show this help`);
|
|
617
|
+
console.log("");
|
|
618
|
+
console.log(` ${D}Use --json for machine-readable output. See oc-inspector --help for all options.${R}`);
|
|
619
|
+
console.log("");
|
|
620
|
+
}
|
|
621
|
+
|
|
567
622
|
async function fetchApi(url) {
|
|
568
623
|
try {
|
|
569
624
|
const res = await fetch(url);
|
|
@@ -575,64 +630,214 @@ async function fetchApi(url) {
|
|
|
575
630
|
}
|
|
576
631
|
}
|
|
577
632
|
|
|
633
|
+
// ── Formatting helpers ──
|
|
634
|
+
|
|
578
635
|
function fmtNum(n) {
|
|
579
636
|
if (n >= 1_000_000) return (n / 1_000_000).toFixed(2) + "M";
|
|
580
637
|
if (n >= 1_000) return (n / 1_000).toFixed(1) + "k";
|
|
581
638
|
return String(n);
|
|
582
639
|
}
|
|
583
640
|
|
|
584
|
-
function
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return `${label} ${"█".repeat(filled)}${"░".repeat(empty)} ${fmtNum(val)}`;
|
|
590
|
-
};
|
|
641
|
+
function fmtCost(n) {
|
|
642
|
+
if (!n || n === 0) return "$0.00";
|
|
643
|
+
if (n < 0.01) return "$" + n.toFixed(4);
|
|
644
|
+
return "$" + n.toFixed(2);
|
|
645
|
+
}
|
|
591
646
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
647
|
+
function fmtMs(ms) {
|
|
648
|
+
if (ms >= 60_000) return (ms / 60_000).toFixed(1) + "m";
|
|
649
|
+
if (ms >= 1_000) return (ms / 1_000).toFixed(1) + "s";
|
|
650
|
+
return ms + "ms";
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Build a horizontal bar string.
|
|
655
|
+
*
|
|
656
|
+
* @param {number} val - Current value.
|
|
657
|
+
* @param {number} max - Maximum value (100%).
|
|
658
|
+
* @param {number} [width=20] - Bar width in characters.
|
|
659
|
+
* @returns {string} Bar characters.
|
|
660
|
+
*/
|
|
661
|
+
function bar(val, max, width = 20) {
|
|
662
|
+
const pct = max > 0 ? Math.min(val / max, 1) : 0;
|
|
663
|
+
const filled = Math.round(pct * width);
|
|
664
|
+
const empty = width - filled;
|
|
665
|
+
return "█".repeat(filled) + "░".repeat(empty);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Render stats data to an array of strings (no ANSI clears).
|
|
670
|
+
*
|
|
671
|
+
* @param {object} s - Stats from /api/stats.
|
|
672
|
+
* @param {object} [opts] - Render options.
|
|
673
|
+
* @param {boolean} [opts.compact=false] - Skip header/footer.
|
|
674
|
+
* @returns {string[]} Lines to print.
|
|
675
|
+
*/
|
|
676
|
+
function renderStats(s, { compact = false } = {}) {
|
|
677
|
+
const lines = [];
|
|
678
|
+
const w = COL;
|
|
679
|
+
|
|
680
|
+
if (!compact) {
|
|
681
|
+
lines.push("");
|
|
682
|
+
lines.push(` ${E.ORG}🦞 OpenClaw Inspector — Stats${E.R}`);
|
|
683
|
+
lines.push(" " + "─".repeat(68));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ── Summary box ──
|
|
687
|
+
lines.push("");
|
|
688
|
+
const errStr = s.errors > 0 ? ` ${E.RED}(${s.errors} errors)${E.R}` : "";
|
|
689
|
+
lines.push(` ${E.B}Requests${E.R} ${String(s.totalRequests).padStart(6)}${errStr}`);
|
|
690
|
+
|
|
691
|
+
const inTok = fmtNum(s.totalInputTokens);
|
|
692
|
+
const outTok = fmtNum(s.totalOutputTokens);
|
|
693
|
+
lines.push(` ${E.B}Tokens${E.R} ${fmtNum(s.totalTokens).padStart(6)} ${E.D}in ${inTok} out ${outTok}${E.R}`);
|
|
596
694
|
|
|
597
|
-
console.log(` \x1b[1mRequests:\x1b[0m ${s.totalRequests}${s.errors > 0 ? ` (\x1b[31m${s.errors} errors\x1b[0m)` : ""}`);
|
|
598
|
-
console.log(` \x1b[1mTokens:\x1b[0m ${fmtNum(s.totalTokens)} total (in: ${fmtNum(s.totalInputTokens)} out: ${fmtNum(s.totalOutputTokens)})`);
|
|
599
695
|
if (s.totalCachedTokens > 0) {
|
|
600
|
-
|
|
696
|
+
lines.push(` ${E.B}Cached${E.R} ${fmtNum(s.totalCachedTokens).padStart(6)}`);
|
|
601
697
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
698
|
+
|
|
699
|
+
lines.push(` ${E.B}Cost${E.R} ${E.GRN}${fmtCost(s.totalCost || 0).padStart(8)}${E.R}`);
|
|
700
|
+
|
|
701
|
+
if (s.totalDuration > 0 && s.totalRequests > 0) {
|
|
702
|
+
const avg = Math.round(s.totalDuration / s.totalRequests);
|
|
703
|
+
lines.push(` ${E.B}Avg latency${E.R} ${fmtMs(avg).padStart(6)}`);
|
|
606
704
|
}
|
|
607
705
|
|
|
608
|
-
|
|
706
|
+
// ── By Provider ──
|
|
707
|
+
const providers = Object.entries(s.byProvider || {});
|
|
609
708
|
if (providers.length > 0) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
709
|
+
lines.push("");
|
|
710
|
+
lines.push(` ${E.B}${"Provider".padEnd(w.name)} ${"Tokens".padStart(w.tokens)} ${"Bar".padEnd(w.bar)} ${"Reqs".padStart(w.reqs)} ${"Cost".padStart(w.cost)}${E.R}`);
|
|
711
|
+
lines.push(" " + "─".repeat(68));
|
|
712
|
+
|
|
713
|
+
const maxT = Math.max(...providers.map(([, v]) => (v.inputTokens || 0) + (v.outputTokens || 0)));
|
|
714
|
+
const sorted = providers.sort((a, b) =>
|
|
715
|
+
(b[1].cost || 0) - (a[1].cost || 0) ||
|
|
716
|
+
((b[1].inputTokens || 0) + (b[1].outputTokens || 0)) - ((a[1].inputTokens || 0) + (a[1].outputTokens || 0))
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
for (const [name, v] of sorted) {
|
|
720
|
+
const total = (v.inputTokens || 0) + (v.outputTokens || 0);
|
|
721
|
+
const n = name.length > w.name - 2 ? name.slice(0, w.name - 3) + "…" : name;
|
|
722
|
+
const costStr = v.cost > 0 ? `${E.GRN}${fmtCost(v.cost).padStart(w.cost)}${E.R}` : `${E.D}${fmtCost(0).padStart(w.cost)}${E.R}`;
|
|
723
|
+
lines.push(` ${E.CYN}${n.padEnd(w.name)}${E.R} ${fmtNum(total).padStart(w.tokens)} ${bar(total, maxT, w.bar)} ${String(v.requests).padStart(w.reqs)} ${costStr}`);
|
|
618
724
|
}
|
|
619
725
|
}
|
|
620
726
|
|
|
621
|
-
|
|
727
|
+
// ── By Model ──
|
|
728
|
+
const models = Object.entries(s.byModel || {});
|
|
622
729
|
if (models.length > 0) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
730
|
+
lines.push("");
|
|
731
|
+
lines.push(` ${E.B}${"Model".padEnd(w.name)} ${"Tokens".padStart(w.tokens)} ${"Bar".padEnd(w.bar)} ${"Reqs".padStart(w.reqs)} ${"Cost".padStart(w.cost)}${E.R}`);
|
|
732
|
+
lines.push(" " + "─".repeat(68));
|
|
733
|
+
|
|
734
|
+
const maxT = Math.max(...models.map(([, v]) => (v.inputTokens || 0) + (v.outputTokens || 0)));
|
|
735
|
+
const sorted = models.sort((a, b) =>
|
|
736
|
+
(b[1].cost || 0) - (a[1].cost || 0) ||
|
|
737
|
+
((b[1].inputTokens || 0) + (b[1].outputTokens || 0)) - ((a[1].inputTokens || 0) + (a[1].outputTokens || 0))
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
for (const [name, v] of sorted) {
|
|
741
|
+
const total = (v.inputTokens || 0) + (v.outputTokens || 0);
|
|
742
|
+
const n = name.length > w.name - 2 ? name.slice(0, w.name - 3) + "…" : name;
|
|
743
|
+
const costStr = v.cost > 0 ? `${E.GRN}${fmtCost(v.cost).padStart(w.cost)}${E.R}` : `${E.D}${fmtCost(0).padStart(w.cost)}${E.R}`;
|
|
744
|
+
lines.push(` ${E.YEL}${n.padEnd(w.name)}${E.R} ${fmtNum(total).padStart(w.tokens)} ${bar(total, maxT, w.bar)} ${String(v.requests).padStart(w.reqs)} ${costStr}`);
|
|
632
745
|
}
|
|
633
746
|
}
|
|
634
747
|
|
|
635
|
-
|
|
748
|
+
lines.push("");
|
|
749
|
+
return lines;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function printStats(s) {
|
|
753
|
+
for (const line of renderStats(s)) process.stdout.write(line + "\n");
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Live auto-refreshing stats dashboard.
|
|
758
|
+
* Clears the terminal and redraws every 2 seconds.
|
|
759
|
+
* Press q or Ctrl+C to exit.
|
|
760
|
+
*
|
|
761
|
+
* @param {string} base - Inspector API base URL.
|
|
762
|
+
*/
|
|
763
|
+
async function runLiveStats(base) {
|
|
764
|
+
const url = `${base}/api/stats`;
|
|
765
|
+
|
|
766
|
+
// Enable raw mode for keypress detection
|
|
767
|
+
if (process.stdin.isTTY) {
|
|
768
|
+
process.stdin.setRawMode(true);
|
|
769
|
+
process.stdin.resume();
|
|
770
|
+
process.stdin.setEncoding("utf8");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
process.stdout.write(E.HIDE); // hide cursor
|
|
774
|
+
|
|
775
|
+
let running = true;
|
|
776
|
+
let tick = 0;
|
|
777
|
+
|
|
778
|
+
const cleanup = () => {
|
|
779
|
+
running = false;
|
|
780
|
+
process.stdout.write(E.SHOW); // show cursor
|
|
781
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
782
|
+
console.log("");
|
|
783
|
+
process.exit(0);
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
process.on("SIGINT", cleanup);
|
|
787
|
+
process.on("SIGTERM", cleanup);
|
|
788
|
+
|
|
789
|
+
if (process.stdin.isTTY) {
|
|
790
|
+
process.stdin.on("data", (key) => {
|
|
791
|
+
if (key === "q" || key === "Q" || key === "\x03") cleanup(); // q or Ctrl+C
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
while (running) {
|
|
796
|
+
try {
|
|
797
|
+
const res = await fetch(url);
|
|
798
|
+
const data = await res.json();
|
|
799
|
+
|
|
800
|
+
const lines = [];
|
|
801
|
+
lines.push(E.CLR); // clear screen
|
|
802
|
+
lines.push(` ${E.ORG}🦞 OpenClaw Inspector${E.R} ${E.D}— Live Stats${E.R}`);
|
|
803
|
+
lines.push(" " + "═".repeat(68));
|
|
804
|
+
|
|
805
|
+
// Uptime spinner
|
|
806
|
+
const spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
807
|
+
const sp = spinner[tick % spinner.length];
|
|
808
|
+
const now = new Date().toLocaleTimeString();
|
|
809
|
+
lines.push(` ${E.GRN}${sp}${E.R} ${E.D}Updated ${now} Press ${E.B}q${E.R}${E.D} to exit${E.R}`);
|
|
810
|
+
|
|
811
|
+
// Render stats body (compact, no header)
|
|
812
|
+
const body = renderStats(data, { compact: true });
|
|
813
|
+
lines.push(...body);
|
|
814
|
+
|
|
815
|
+
// Recently seen models (last 5 entries by model)
|
|
816
|
+
const recent = data.recentEntries || [];
|
|
817
|
+
if (recent.length > 0) {
|
|
818
|
+
lines.push(` ${E.B}Recent Requests${E.R}`);
|
|
819
|
+
lines.push(" " + "─".repeat(68));
|
|
820
|
+
for (const e of recent.slice(0, 8)) {
|
|
821
|
+
const model = (e.model || "?").length > 22 ? (e.model || "?").slice(0, 21) + "…" : (e.model || "?");
|
|
822
|
+
const st = e.state === "done" ? `${E.GRN}✓${E.R}` : e.state === "error" ? `${E.RED}✗${E.R}` : `${E.YEL}…${E.R}`;
|
|
823
|
+
const dur = e.duration ? fmtMs(e.duration) : "—";
|
|
824
|
+
const tok = e.totalTokens ? fmtNum(e.totalTokens) : "—";
|
|
825
|
+
const cost = e.cost > 0 ? `${E.GRN}${fmtCost(e.cost)}${E.R}` : `${E.D}—${E.R}`;
|
|
826
|
+
lines.push(` ${st} ${E.YEL}${model.padEnd(24)}${E.R} ${E.CYN}${(e.provider || "?").padEnd(12)}${E.R} ${tok.padStart(8)} tok ${dur.padStart(6)} ${cost}`);
|
|
827
|
+
}
|
|
828
|
+
lines.push("");
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
832
|
+
} catch {
|
|
833
|
+
process.stdout.write(E.CLR);
|
|
834
|
+
process.stdout.write(`\n ${E.RED}✗ Cannot connect to inspector at ${base}${E.R}\n`);
|
|
835
|
+
process.stdout.write(` ${E.D}Retrying...${E.R}\n`);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
tick++;
|
|
839
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
840
|
+
}
|
|
636
841
|
}
|
|
637
842
|
|
|
638
843
|
function printPricing(models) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc-inspector",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Real-time API traffic inspector for OpenClaw — intercepts LLM provider requests, shows token usage, costs, and message flow in a live web dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/proxy.mjs
CHANGED
|
@@ -112,6 +112,24 @@ export function getStats() {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Recent entries (last 10) for live view
|
|
116
|
+
const recentEntries = [];
|
|
117
|
+
const recentIds = entryOrder.slice(-10).reverse();
|
|
118
|
+
for (const id of recentIds) {
|
|
119
|
+
const e = entries.get(id);
|
|
120
|
+
if (!e) continue;
|
|
121
|
+
const u = e.usage || {};
|
|
122
|
+
recentEntries.push({
|
|
123
|
+
id: e.id,
|
|
124
|
+
provider: e.provider || "?",
|
|
125
|
+
model: u.model || e.reqModel || "?",
|
|
126
|
+
state: e.state,
|
|
127
|
+
duration: e.duration || 0,
|
|
128
|
+
totalTokens: (u.inputTokens || 0) + (u.outputTokens || 0),
|
|
129
|
+
cost: e.cost || 0,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
return {
|
|
116
134
|
totalRequests,
|
|
117
135
|
totalInputTokens,
|
|
@@ -123,6 +141,7 @@ export function getStats() {
|
|
|
123
141
|
errors,
|
|
124
142
|
byProvider,
|
|
125
143
|
byModel,
|
|
144
|
+
recentEntries,
|
|
126
145
|
};
|
|
127
146
|
}
|
|
128
147
|
|