agents-deck 1.21.0 → 1.22.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/dist/web/assets/{index-BDP3vsJ3.css → index-BRoNoC-Z.css} +1 -1
- package/dist/web/assets/index-Dc4xConU.js +62 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/server/ccusage.mjs +93 -0
- package/src/server/index.mjs +13 -0
- package/dist/web/assets/index-B-TDYmpb.js +0 -62
package/dist/web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agents-deck</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Dc4xConU.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BRoNoC-Z.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Fetches historical usage from the `ccusage` CLI (https://github.com/ccusage/ccusage).
|
|
2
|
+
// ccusage reads the local ~/.claude (and other agent) logs and reports cost +
|
|
3
|
+
// token usage grouped by day. We shell out to it via `npx -y ccusage@latest`
|
|
4
|
+
// — it is NOT a dependency; npx fetches it on first run (cached afterwards).
|
|
5
|
+
//
|
|
6
|
+
// The whole backend is: spawn the CLI with --json, slice the JSON out of stdout
|
|
7
|
+
// (npx can print banner noise), JSON.parse, cache. Ported from the task-board
|
|
8
|
+
// project's three Next.js routes, collapsed to one daily fetch.
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
|
|
11
|
+
const CACHE_MS = 120_000; // 2 min — ccusage spawn is heavy; modal is manual-open
|
|
12
|
+
const TIMEOUT_MS = 90_000;
|
|
13
|
+
|
|
14
|
+
const _cache = new Map(); // key `${since}|${until}` → { result, at }
|
|
15
|
+
|
|
16
|
+
// Run `ccusage <args> --json` and resolve its raw stdout.
|
|
17
|
+
function runCcusage(args) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
// shell:true + windowsHide:true so `npx` resolves on Windows without a
|
|
20
|
+
// popup console. ccusage@latest is pinned so behavior is stable.
|
|
21
|
+
const child = spawn("npx", ["-y", "ccusage@latest", ...args], {
|
|
22
|
+
shell: true,
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
});
|
|
25
|
+
let out = "", err = "";
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
child.kill();
|
|
28
|
+
reject(new Error("ccusage timed out"));
|
|
29
|
+
}, TIMEOUT_MS);
|
|
30
|
+
child.stdout.on("data", d => { out += d; });
|
|
31
|
+
child.stderr.on("data", d => { err += d; });
|
|
32
|
+
child.on("error", e => { clearTimeout(timer); reject(e); });
|
|
33
|
+
child.on("close", code => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
if (code === 0) resolve(out);
|
|
36
|
+
else reject(new Error(err.trim() || `ccusage exited ${code}`));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ccusage prints the JSON object somewhere in stdout; slice first { to last }.
|
|
42
|
+
function extractJson(out) {
|
|
43
|
+
const start = out.indexOf("{");
|
|
44
|
+
const end = out.lastIndexOf("}");
|
|
45
|
+
if (start === -1 || end === -1) throw new Error("no JSON in ccusage output");
|
|
46
|
+
return JSON.parse(out.slice(start, end + 1));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// YYYYMMDD for the CLI's --since/--until.
|
|
50
|
+
function toCliDate(d) {
|
|
51
|
+
return d.toISOString().slice(0, 10).replace(/-/g, "");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fetch daily usage from ccusage for a date range.
|
|
56
|
+
* @param {{ since?: string, until?: string, force?: boolean }} opts
|
|
57
|
+
* since/until are YYYYMMDD strings (CLI format). Defaults to last 30 days.
|
|
58
|
+
* @returns {{ ok, days, totals, since, until, fetchedAt } | { ok:false, error }}
|
|
59
|
+
*/
|
|
60
|
+
export async function fetchCcusageDaily({ since, until, force = false } = {}) {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const sinceArg = since || toCliDate(new Date(now - 30 * 86400_000));
|
|
63
|
+
const key = `${sinceArg}|${until ?? ""}`;
|
|
64
|
+
|
|
65
|
+
const cached = _cache.get(key);
|
|
66
|
+
if (!force && cached && now - cached.at < CACHE_MS) return cached.result;
|
|
67
|
+
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
const args = ["daily", "--json", "--since", sinceArg];
|
|
71
|
+
if (until) args.push("--until", until);
|
|
72
|
+
const raw = extractJson(await runCcusage(args));
|
|
73
|
+
const days = Array.isArray(raw.daily) ? raw.daily : [];
|
|
74
|
+
result = {
|
|
75
|
+
ok: true,
|
|
76
|
+
days,
|
|
77
|
+
totals: raw.totals ?? null,
|
|
78
|
+
since: sinceArg,
|
|
79
|
+
until: until ?? null,
|
|
80
|
+
fetchedAt: now,
|
|
81
|
+
};
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error("agents-deck ccusage: fetch failed:", err?.message ?? err);
|
|
84
|
+
result = { ok: false, error: String(err?.message ?? err), fetchedAt: now };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_cache.set(key, { result, at: now });
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function invalidateCcusageCache() {
|
|
92
|
+
_cache.clear();
|
|
93
|
+
}
|
package/src/server/index.mjs
CHANGED
|
@@ -1023,6 +1023,18 @@ async function handleCodexQuota(req, res) {
|
|
|
1023
1023
|
send(res, 200, quota);
|
|
1024
1024
|
}
|
|
1025
1025
|
|
|
1026
|
+
async function handleCcusage(req, res) {
|
|
1027
|
+
const { fetchCcusageDaily } = await import(
|
|
1028
|
+
pathToFileURL(join(PKG_ROOT, "src/server/ccusage.mjs")).href
|
|
1029
|
+
);
|
|
1030
|
+
const url = new URL(req.url, "http://localhost");
|
|
1031
|
+
const force = url.searchParams.get("refresh") === "1";
|
|
1032
|
+
const since = url.searchParams.get("since") || undefined;
|
|
1033
|
+
const until = url.searchParams.get("until") || undefined;
|
|
1034
|
+
const data = await fetchCcusageDaily({ since, until, force });
|
|
1035
|
+
send(res, 200, data);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1026
1038
|
function handleHealth(_req, res) {
|
|
1027
1039
|
send(res, 200, {
|
|
1028
1040
|
ok: true,
|
|
@@ -1092,6 +1104,7 @@ export async function startServer({ port = 4317, host = "127.0.0.1", persist = n
|
|
|
1092
1104
|
if (req.method === "GET" && url.pathname === "/api/quota") return handleQuota(req, res);
|
|
1093
1105
|
if (req.method === "GET" && url.pathname === "/api/codex-usage") return handleCodexUsage(req, res);
|
|
1094
1106
|
if (req.method === "GET" && url.pathname === "/api/codex-quota") return handleCodexQuota(req, res);
|
|
1107
|
+
if (req.method === "GET" && url.pathname === "/api/ccusage") return handleCcusage(req, res);
|
|
1095
1108
|
|
|
1096
1109
|
if (req.method === "GET" && url.pathname === "/api/events") {
|
|
1097
1110
|
const since = Number(url.searchParams.get("since") ?? 0);
|