@caius_kong/ccusage-dashboard 0.2.9 → 0.2.10
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/ccusage-ui.js +45 -2
- package/lib/server.py +32 -15
- package/package.json +1 -1
package/bin/ccusage-ui.js
CHANGED
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
* Locates the bundled server.py (and its index.html sibling) and runs it with
|
|
6
6
|
* the local python3, forwarding any CLI args. The server itself shells out to
|
|
7
7
|
* ccusage for all numbers, so the dashboard always matches ccusage.
|
|
8
|
+
*
|
|
9
|
+
* Startup UX:
|
|
10
|
+
* - the server tries to auto-open the default browser (best effort)
|
|
11
|
+
* - regardless of that, this launcher prints a clear URL banner once the
|
|
12
|
+
* server is up, so the user can always click/copy it manually.
|
|
8
13
|
*/
|
|
9
14
|
'use strict';
|
|
10
15
|
|
|
11
16
|
const { spawn, spawnSync } = require('node:child_process');
|
|
12
17
|
const path = require('node:path');
|
|
13
18
|
const fs = require('node:fs');
|
|
19
|
+
const http = require('node:http');
|
|
14
20
|
|
|
15
21
|
// Data files live in package/lib (installed layout) or the repo root (dev layout).
|
|
16
22
|
function resolveLibFile(name) {
|
|
@@ -41,13 +47,50 @@ if (!py) {
|
|
|
41
47
|
process.exit(1);
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
// Parse the user's --port / --host so the banner points at the real URL.
|
|
51
|
+
const rawArgs = process.argv.slice(2);
|
|
52
|
+
function argValue(name, fallback) {
|
|
53
|
+
const i = rawArgs.indexOf(name);
|
|
54
|
+
return i >= 0 && rawArgs[i + 1] ? rawArgs[i + 1] : fallback;
|
|
55
|
+
}
|
|
56
|
+
const host = argValue('--host', '127.0.0.1');
|
|
57
|
+
const port = parseInt(argValue('--port', '8799'), 10);
|
|
58
|
+
const url = `http://${host}:${port}`;
|
|
59
|
+
|
|
60
|
+
const args = rawArgs.slice();
|
|
45
61
|
if (!args.includes('--open') && !process.env.CCUSAGE_UI_NO_OPEN) {
|
|
46
|
-
args.push('--open'); // default: open browser
|
|
62
|
+
args.push('--open'); // default: try to auto-open the browser (best effort)
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
const child = spawn(py, [serverPy, ...args], { stdio: 'inherit', env: { ...process.env } });
|
|
50
66
|
|
|
67
|
+
// Once the server answers /, print a clear URL banner for manual opening.
|
|
68
|
+
let bannerPrinted = false;
|
|
69
|
+
function printBanner() {
|
|
70
|
+
if (bannerPrinted) return;
|
|
71
|
+
bannerPrinted = true;
|
|
72
|
+
const line = '─'.repeat(Math.max(20, url.length + 6));
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(line);
|
|
75
|
+
console.log(` Dashboard is running → ${url}`);
|
|
76
|
+
console.log(` Open it manually: ${url}`);
|
|
77
|
+
console.log(line);
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let checked = false;
|
|
82
|
+
function pingAndBanner() {
|
|
83
|
+
if (checked) return;
|
|
84
|
+
checked = true;
|
|
85
|
+
const req = http.get(url + '/api/health', { timeout: 1500 }, (res) => {
|
|
86
|
+
if (res.statusCode === 200) printBanner();
|
|
87
|
+
else setTimeout(pingAndBanner, 500);
|
|
88
|
+
});
|
|
89
|
+
req.on('error', () => setTimeout(pingAndBanner, 500));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setTimeout(pingAndBanner, 800); // give the python server a beat to bind
|
|
93
|
+
|
|
51
94
|
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
52
95
|
process.on(sig, () => { if (!child.killed) child.kill(sig); });
|
|
53
96
|
}
|
package/lib/server.py
CHANGED
|
@@ -577,6 +577,8 @@ def main() -> None:
|
|
|
577
577
|
print(f"ccusage-ui → {url} (monthly budget ${BUDGET:g}, Ctrl+C to stop)", flush=True)
|
|
578
578
|
if args.open:
|
|
579
579
|
_open_browser(url)
|
|
580
|
+
else:
|
|
581
|
+
print(f"dashboard ready at {url} — open it in your browser", flush=True)
|
|
580
582
|
|
|
581
583
|
try:
|
|
582
584
|
httpd.serve_forever()
|
|
@@ -585,28 +587,43 @@ def main() -> None:
|
|
|
585
587
|
|
|
586
588
|
|
|
587
589
|
def _open_browser(url: str) -> None:
|
|
588
|
-
"""Open the dashboard in
|
|
589
|
-
|
|
590
|
+
"""Open the dashboard in a browser. Tries, in order:
|
|
591
|
+
1. $BROWSER (e.g. 'Google Chrome', 'Safari', 'firefox')
|
|
592
|
+
2. the OS default opener (open / xdg-open / start)
|
|
593
|
+
3. prints the URL as a fallback so the user can open it manually.
|
|
594
|
+
Every attempt logs its outcome so auto-open is verifiable.
|
|
590
595
|
"""
|
|
596
|
+
import os
|
|
591
597
|
import subprocess
|
|
592
598
|
import sys
|
|
593
599
|
|
|
600
|
+
browser = os.environ.get("BROWSER", "").strip()
|
|
601
|
+
attempts = []
|
|
594
602
|
if sys.platform == "darwin":
|
|
595
|
-
|
|
603
|
+
if browser:
|
|
604
|
+
attempts.append(["open", "-a", browser, url])
|
|
605
|
+
attempts.append(["open", url])
|
|
596
606
|
elif sys.platform.startswith("win"):
|
|
597
|
-
|
|
607
|
+
if browser:
|
|
608
|
+
attempts.append(["cmd", "/c", "start", "", "chrome", url])
|
|
609
|
+
attempts.append(["cmd", "/c", "start", "", url])
|
|
598
610
|
else:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
611
|
+
if browser:
|
|
612
|
+
attempts.append([browser, url])
|
|
613
|
+
attempts.append(["xdg-open", url])
|
|
614
|
+
|
|
615
|
+
for cmd in attempts:
|
|
616
|
+
try:
|
|
617
|
+
r = subprocess.run(cmd, timeout=5, capture_output=True, text=True)
|
|
618
|
+
if r.returncode == 0:
|
|
619
|
+
print(f"browser opened → {url} ({' '.join(cmd[:2])})", flush=True)
|
|
620
|
+
return
|
|
621
|
+
print(f"browser attempt failed (rc={r.returncode}): {' '.join(cmd[:2])}", flush=True)
|
|
622
|
+
except FileNotFoundError:
|
|
623
|
+
print(f"opener not found: {' '.join(cmd[:2])}", flush=True)
|
|
624
|
+
except Exception as e: # noqa: BLE001
|
|
625
|
+
print(f"browser open error: {e}", flush=True)
|
|
626
|
+
print(f"could not auto-open a browser — open it manually: {url}", flush=True)
|
|
610
627
|
|
|
611
628
|
|
|
612
629
|
def os_env_budget() -> str:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caius_kong/ccusage-dashboard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "One-command local dashboard for ccusage: today/week/month/custom cost by model, 30-day trend, monthly budget alert. Numbers straight from ccusage.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|