@jeffreyjyz/opencode-command-code 0.2.8 → 0.2.9
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/sidebar/rows.d.ts +8 -2
- package/dist/sidebar/usageDb.d.ts +16 -0
- package/dist/tui.js +77 -3
- package/package.json +1 -1
package/dist/sidebar/rows.d.ts
CHANGED
|
@@ -19,6 +19,11 @@ export interface WindowUsage {
|
|
|
19
19
|
used?: number;
|
|
20
20
|
resetAt?: number;
|
|
21
21
|
}
|
|
22
|
+
/** One model's usage for the account's current billing period. */
|
|
23
|
+
export interface ModelUsage {
|
|
24
|
+
requests: number;
|
|
25
|
+
cost: number;
|
|
26
|
+
}
|
|
22
27
|
export interface Usage {
|
|
23
28
|
plan?: string;
|
|
24
29
|
monthlyCap?: number;
|
|
@@ -38,8 +43,9 @@ export declare function count(value: number): string;
|
|
|
38
43
|
export declare function until(epoch: number | undefined, now?: number): string;
|
|
39
44
|
/** Plan + rolling windows + period totals, from a cmduse snapshot. */
|
|
40
45
|
export declare function usageRows(usage: Usage | undefined, now?: number): SidebarRow[];
|
|
41
|
-
/** Active model's allowance, rates and benchmarks, from mpc's catalog
|
|
42
|
-
|
|
46
|
+
/** Active model's allowance, rates and benchmarks, from mpc's catalog; its
|
|
47
|
+
* period usage (when the store has it) rides directly under the model name. */
|
|
48
|
+
export declare function modelRows(meta: ModelMeta | undefined, usage?: ModelUsage): SidebarRow[];
|
|
43
49
|
/**
|
|
44
50
|
* Canonical key for a model id, mirroring mpc's normalizeKey: lowercase, drop
|
|
45
51
|
* the vendor prefix and any parenthetical, strip punctuation. Used to match the
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ModelUsage } from "./rows";
|
|
2
|
+
/** opencode's message store: `OPENCODE_DB`, else the XDG data dir. */
|
|
3
|
+
export declare function usageDbPath(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Start of the billing period a cmduse snapshot reports, as epoch ms. cmduse
|
|
6
|
+
* publishes only `periodEnd` (YYYY-MM-DD), so the start is that date one
|
|
7
|
+
* calendar month back. Falls back to the last 30 days when it is missing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function periodStart(periodEnd: string | undefined, now?: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* Requests and cost for one model since `sinceMs`. Null when the store is
|
|
12
|
+
* missing or unreadable, so callers can fall back to omitting the row.
|
|
13
|
+
* Matching is on the canonical key: the session id and the stored modelID can
|
|
14
|
+
* differ by vendor prefix/punctuation.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadModelUsage(modelID: string, sinceMs: number, path?: string): ModelUsage | null;
|
package/dist/tui.js
CHANGED
|
@@ -462,10 +462,14 @@ function usageRows(usage, now = Date.now()) {
|
|
|
462
462
|
}
|
|
463
463
|
return rows;
|
|
464
464
|
}
|
|
465
|
-
function modelRows(meta) {
|
|
465
|
+
function modelRows(meta, usage) {
|
|
466
466
|
if (!meta)
|
|
467
467
|
return [];
|
|
468
468
|
const rows = [["Model", meta.name]];
|
|
469
|
+
if (usage) {
|
|
470
|
+
const spent = usage.cost > 0 ? ` \xB7 ${money(usage.cost)}` : "";
|
|
471
|
+
rows.push(["Usage", `${count(usage.requests)} req${spent}`]);
|
|
472
|
+
}
|
|
469
473
|
if (meta.tier)
|
|
470
474
|
rows.push(["Tier", TIER_DISPLAY[meta.tier]]);
|
|
471
475
|
if (typeof meta.allowance === "number")
|
|
@@ -593,6 +597,72 @@ async function loadUsage() {
|
|
|
593
597
|
return parseUsageJson(await runFirst(CMDUSE, ["-1", "--json", "--plain"]));
|
|
594
598
|
}
|
|
595
599
|
|
|
600
|
+
// src/sidebar/usageDb.ts
|
|
601
|
+
import { Database } from "bun:sqlite";
|
|
602
|
+
import { homedir as homedir2 } from "os";
|
|
603
|
+
import { join as join2 } from "path";
|
|
604
|
+
function usageDbPath() {
|
|
605
|
+
if (process.env.OPENCODE_DB)
|
|
606
|
+
return process.env.OPENCODE_DB;
|
|
607
|
+
const base = process.env.XDG_DATA_HOME ?? join2(homedir2(), ".local", "share");
|
|
608
|
+
return join2(base, "opencode", "opencode.db");
|
|
609
|
+
}
|
|
610
|
+
function periodStart(periodEnd, now = Date.now()) {
|
|
611
|
+
const end = periodEnd ? Date.parse(`${periodEnd}T23:59:59`) : Number.NaN;
|
|
612
|
+
if (!Number.isFinite(end))
|
|
613
|
+
return now - 30 * 24 * 60 * 60 * 1000;
|
|
614
|
+
const start = new Date(end);
|
|
615
|
+
start.setMonth(start.getMonth() - 1);
|
|
616
|
+
return start.getTime();
|
|
617
|
+
}
|
|
618
|
+
function usage(value) {
|
|
619
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
620
|
+
}
|
|
621
|
+
function loadModelUsage(modelID, sinceMs, path = usageDbPath()) {
|
|
622
|
+
let db;
|
|
623
|
+
try {
|
|
624
|
+
db = new Database(path, {
|
|
625
|
+
readonly: true
|
|
626
|
+
});
|
|
627
|
+
} catch {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
const rows = db.query(`SELECT data, time_created FROM message
|
|
632
|
+
WHERE data LIKE '%"tokens"%' AND time_created >= ?`).all(sinceMs);
|
|
633
|
+
const key = modelKey(modelID);
|
|
634
|
+
const seen = new Set;
|
|
635
|
+
let requests = 0;
|
|
636
|
+
let cost = 0;
|
|
637
|
+
for (const row of rows) {
|
|
638
|
+
let data;
|
|
639
|
+
try {
|
|
640
|
+
data = JSON.parse(row.data);
|
|
641
|
+
} catch {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (data.role !== "assistant" || !data.modelID || !data.tokens)
|
|
645
|
+
continue;
|
|
646
|
+
if (modelKey(data.modelID) !== key)
|
|
647
|
+
continue;
|
|
648
|
+
const id = `${data.modelID}@${row.time_created}`;
|
|
649
|
+
if (seen.has(id))
|
|
650
|
+
continue;
|
|
651
|
+
seen.add(id);
|
|
652
|
+
requests += 1;
|
|
653
|
+
cost += usage(data.cost);
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
requests,
|
|
657
|
+
cost
|
|
658
|
+
};
|
|
659
|
+
} catch {
|
|
660
|
+
return null;
|
|
661
|
+
} finally {
|
|
662
|
+
db.close();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
596
666
|
// src/tui.tsx
|
|
597
667
|
var ID = "command-code.tui";
|
|
598
668
|
var POLL_MS = 30000;
|
|
@@ -628,10 +698,14 @@ function Panel(props) {
|
|
|
628
698
|
function useRows(activeModelId, active) {
|
|
629
699
|
const [usage, setUsage] = createSignal([]);
|
|
630
700
|
const [meta, setMeta] = createSignal(new Map);
|
|
701
|
+
const [modelUsage, setModelUsage] = createSignal();
|
|
631
702
|
loadMeta().then(setMeta).catch(() => {});
|
|
632
703
|
const refresh = async () => {
|
|
633
704
|
try {
|
|
634
|
-
|
|
705
|
+
const snapshot = await loadUsage();
|
|
706
|
+
setUsage(usageRows(snapshot));
|
|
707
|
+
const id = activeModelId();
|
|
708
|
+
setModelUsage(id ? loadModelUsage(id, periodStart(snapshot.periodEnd)) ?? undefined : undefined);
|
|
635
709
|
} catch {}
|
|
636
710
|
};
|
|
637
711
|
createEffect(() => {
|
|
@@ -650,7 +724,7 @@ function useRows(activeModelId, active) {
|
|
|
650
724
|
...found,
|
|
651
725
|
tier: found.tier ?? (id ? tierFor(id) : undefined)
|
|
652
726
|
} : undefined;
|
|
653
|
-
return [...usage(), ...modelRows(model)];
|
|
727
|
+
return [...usage(), ...modelRows(model, modelUsage())];
|
|
654
728
|
});
|
|
655
729
|
}
|
|
656
730
|
var isOurs = (providerID) => Boolean(providerID?.startsWith("command-code"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffreyjyz/opencode-command-code",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Command Code provider + usage dashboard for opencode: two providers (Anthropic wire for Claude, OpenAI wire for everything else) against api.commandcode.ai, live model list, plan gating, /cmd-usage. Requires the cmduse CLI for usage rendering.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|