@jeffreyjyz/opencode-command-code 0.3.11 → 0.3.12
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/index.js +1 -1
- package/dist/sidebar/data.d.ts +12 -1
- package/dist/sidebar/panel.d.ts +1 -0
- package/dist/sidebar/rows.d.ts +22 -2
- package/dist/sidebar/usageDb.d.ts +7 -0
- package/dist/sidebar/useRows.d.ts +8 -6
- package/dist/sidebar/windows.d.ts +11 -0
- package/dist/sidebar/zen.d.ts +36 -0
- package/dist/tui.js +233 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4063,7 +4063,7 @@ var commandCodeV2 = {
|
|
|
4063
4063
|
// package.json
|
|
4064
4064
|
var package_default = {
|
|
4065
4065
|
name: "@jeffreyjyz/opencode-command-code",
|
|
4066
|
-
version: "0.3.
|
|
4066
|
+
version: "0.3.12",
|
|
4067
4067
|
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.",
|
|
4068
4068
|
type: "module",
|
|
4069
4069
|
main: "dist/index.js",
|
package/dist/sidebar/data.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { type ModelMeta, type Usage } from "./rows";
|
|
3
|
+
import { type ZenUsage } from "./zen";
|
|
3
4
|
/** `detached` is load-bearing, not tidiness: it starts the child in its own
|
|
4
5
|
* session, so it has no controlling terminal. cmduse's snapshot() paints a
|
|
5
6
|
* "fetching usage…" spinner straight to /dev/tty — piping stdout/stderr does
|
|
@@ -8,9 +9,19 @@ import { type ModelMeta, type Usage } from "./rows";
|
|
|
8
9
|
export declare const SPAWN_OPTIONS: Parameters<typeof spawn>[2];
|
|
9
10
|
/** Parse `cmduse -1 --json` into the usage snapshot. */
|
|
10
11
|
export declare function parseUsageJson(text: string): Usage;
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Parse `mpc --json` into per-model meta: the CommandCode fields at the top
|
|
14
|
+
* level and the OpenCode Go / Zen side under `oc`, so one lookup serves either
|
|
15
|
+
* kind of session. Rows that carry only one side are kept.
|
|
16
|
+
*/
|
|
12
17
|
export declare function parseMpcJson(text: string): Map<string, ModelMeta>;
|
|
13
18
|
/** mpc's catalog, cached on disk: it scrapes live docs over the network. */
|
|
14
19
|
export declare function loadMeta(): Promise<Map<string, ModelMeta>>;
|
|
15
20
|
/** One usage snapshot from cmduse. Throws when cmduse is missing or fails. */
|
|
16
21
|
export declare function loadUsage(): Promise<Usage>;
|
|
22
|
+
/**
|
|
23
|
+
* One OpenCode Go / Zen snapshot from ocuse, which reads opencode's own store
|
|
24
|
+
* (those providers have no usage API). Throws when ocuse is missing or fails,
|
|
25
|
+
* and the caller then omits the panel.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadZen(): Promise<ZenUsage>;
|
package/dist/sidebar/panel.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export declare function legacyColors(theme: {
|
|
|
45
45
|
export declare function mono(colors: PanelColors): PanelColors;
|
|
46
46
|
/** Bold title, then one line per row. Renders nothing when there are no rows. */
|
|
47
47
|
export declare function Panel(props: {
|
|
48
|
+
title: () => string;
|
|
48
49
|
rows: () => SidebarRow[];
|
|
49
50
|
colors: () => PanelColors;
|
|
50
51
|
}): import("solid-js").JSX.Element;
|
package/dist/sidebar/rows.d.ts
CHANGED
|
@@ -51,11 +51,30 @@ export interface ModelMeta {
|
|
|
51
51
|
};
|
|
52
52
|
intelligence?: number;
|
|
53
53
|
tps?: number;
|
|
54
|
+
/**
|
|
55
|
+
* The OpenCode Go / Zen side of the same mpc row (0.4.0): the CommandCode
|
|
56
|
+
* fields above are the default, this is what a Go/Zen session reads.
|
|
57
|
+
*/
|
|
58
|
+
oc?: {
|
|
59
|
+
provider?: string;
|
|
60
|
+
plan?: string;
|
|
61
|
+
allowance?: number;
|
|
62
|
+
rates?: {
|
|
63
|
+
input: number;
|
|
64
|
+
output: number;
|
|
65
|
+
cacheRead: number;
|
|
66
|
+
cacheWrite?: number | null;
|
|
67
|
+
};
|
|
68
|
+
ability?: number;
|
|
69
|
+
tps?: number;
|
|
70
|
+
};
|
|
54
71
|
}
|
|
55
72
|
export interface WindowUsage {
|
|
56
73
|
cap?: number;
|
|
57
74
|
used?: number;
|
|
58
75
|
resetAt?: number;
|
|
76
|
+
/** The account API's own over-cap flag (cmduse passes it through). */
|
|
77
|
+
exceeded?: boolean;
|
|
59
78
|
}
|
|
60
79
|
/** One model's usage for the account's current billing period. */
|
|
61
80
|
export interface ModelUsage {
|
|
@@ -85,8 +104,9 @@ export declare function until(epoch: number | undefined, now?: number): string;
|
|
|
85
104
|
/** Plan + rolling windows + period totals, from a cmduse snapshot. */
|
|
86
105
|
export declare function usageRows(usage: Usage | undefined, now?: number): SidebarRow[];
|
|
87
106
|
/** Active model's allowance, rates and benchmarks, from mpc's catalog; its
|
|
88
|
-
* period usage (when the store has it) rides directly under the model name
|
|
89
|
-
|
|
107
|
+
* period usage (when the store has it) rides directly under the model name, and
|
|
108
|
+
* this session's own totals under that (0.4.0). */
|
|
109
|
+
export declare function modelRows(meta: ModelMeta | undefined, usage?: ModelUsage, session?: ModelUsage): SidebarRow[];
|
|
90
110
|
/**
|
|
91
111
|
* Canonical key for a model id, mirroring mpc's normalizeKey: lowercase, drop
|
|
92
112
|
* the vendor prefix and any parenthetical, strip punctuation. Used to match the
|
|
@@ -18,3 +18,10 @@ export declare function periodStart(usage: {
|
|
|
18
18
|
* differ by vendor prefix/punctuation.
|
|
19
19
|
*/
|
|
20
20
|
export declare function loadModelUsage(modelID: string, sinceMs: number, path?: string): ModelUsage | null;
|
|
21
|
+
/**
|
|
22
|
+
* Requests and cost for one session, so the panel can show what this
|
|
23
|
+
* conversation has spent rather than only the period-to-date figure. Null when
|
|
24
|
+
* the store is missing or unreadable, and callers then omit the row. Rows are
|
|
25
|
+
* keyed by their primary key: message rows get rewritten in place.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadSessionUsage(sessionID: string, path?: string): ModelUsage | null;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type ModelUsage, type SidebarRow, type Usage } from "./rows";
|
|
2
|
-
/** Which provider a session is on, as far as this panel is concerned. */
|
|
3
|
-
export type SessionKind = "ours" | "other" | "unknown";
|
|
4
2
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* Which family a session's provider belongs to, as far as this panel is
|
|
4
|
+
* concerned. `unknown` is the frame during a session switch, before the host has
|
|
5
|
+
* resolved the new session's model; it must not be read as someone else's, or
|
|
6
|
+
* the panel blanks and refetches on every tab change.
|
|
8
7
|
*/
|
|
8
|
+
export type SessionKind = "ours" | "go" | "zen" | "other" | "unknown";
|
|
9
9
|
export declare function sessionKind(providerID: string | undefined): SessionKind;
|
|
10
|
+
/** The section's title, per family. */
|
|
11
|
+
export declare function panelTitle(kind: SessionKind): string;
|
|
10
12
|
/** Keep an account snapshot for the next panel that needs it. */
|
|
11
13
|
export declare function rememberSnapshot(snapshot: Usage): void;
|
|
12
14
|
/** Rows to paint before this panel's first fetch of the session completes. */
|
|
@@ -22,4 +24,4 @@ export declare function rememberModelUsage(id: string, usage: ModelUsage): void;
|
|
|
22
24
|
* beats blanking it, the same call `wasOurs` makes for the account block.
|
|
23
25
|
*/
|
|
24
26
|
export declare function seededModelUsage(id: string | undefined): ModelUsage | undefined;
|
|
25
|
-
export declare function useRows(activeModelId: () => string | undefined, providerID: () => string | undefined): import("solid-js").Accessor<SidebarRow[]>;
|
|
27
|
+
export declare function useRows(activeModelId: () => string | undefined, providerID: () => string | undefined, sessionID: () => string | undefined): import("solid-js").Accessor<SidebarRow[]>;
|
|
@@ -14,3 +14,14 @@ export declare function elapsedPct(resetAtMs: number | undefined, durSecs: numbe
|
|
|
14
14
|
* Exact zero stays "0%".
|
|
15
15
|
*/
|
|
16
16
|
export declare function elapsedLabel(resetAtMs: number | undefined, durSecs: number, nowSecs: number): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Seconds until the cap is reached at the window's current burn rate, or
|
|
19
|
+
* undefined when there is nothing honest to warn about: no reset, a window that
|
|
20
|
+
* has not started or is already rolling over, under a tenth elapsed (a flat-rate
|
|
21
|
+
* projection off a few seconds of data is noise), no spend yet, a cap already
|
|
22
|
+
* reached, or a cap that would land after the reset anyway.
|
|
23
|
+
*
|
|
24
|
+
* Mirrors `cmduse_core::pace_eta`; the `paceEta` vectors in the shared
|
|
25
|
+
* `core/conformance.json` are asserted against this in test/conformance.test.ts.
|
|
26
|
+
*/
|
|
27
|
+
export declare function paceEtaSecs(resetAtMs: number | undefined, durSecs: number, used: number, cap: number, nowSecs: number): number | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ModelMeta, type ModelUsage, type SidebarRow } from "./rows";
|
|
2
|
+
/** Go's documented per-model window shares of the monthly allowance. */
|
|
3
|
+
export declare const GO_WINDOW_SHARE: {
|
|
4
|
+
readonly fiveHour: 0.2;
|
|
5
|
+
readonly weekly: 0.5;
|
|
6
|
+
readonly month: 1;
|
|
7
|
+
};
|
|
8
|
+
export interface ZenTotals {
|
|
9
|
+
requests: number;
|
|
10
|
+
cost: number;
|
|
11
|
+
tokens: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ZenModel {
|
|
14
|
+
id: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
/** Monthly allowance the docs give this model, when they give one. */
|
|
17
|
+
limit?: number;
|
|
18
|
+
fiveHour: ZenTotals;
|
|
19
|
+
weekly: ZenTotals;
|
|
20
|
+
month: ZenTotals;
|
|
21
|
+
}
|
|
22
|
+
export interface ZenUsage {
|
|
23
|
+
periodStart?: number;
|
|
24
|
+
source?: string;
|
|
25
|
+
totals: {
|
|
26
|
+
fiveHour: ZenTotals;
|
|
27
|
+
weekly: ZenTotals;
|
|
28
|
+
month: ZenTotals;
|
|
29
|
+
};
|
|
30
|
+
/** Keyed by canonical model key, the same normalisation the CC side matches on. */
|
|
31
|
+
models: Map<string, ZenModel>;
|
|
32
|
+
}
|
|
33
|
+
/** Parse `ocuse -1 --json`. Anything malformed reads as "no usage". */
|
|
34
|
+
export declare function parseZenJson(text: string): ZenUsage;
|
|
35
|
+
/** The OpenCode Go / Zen panel: period windows, a rule, then the active model. */
|
|
36
|
+
export declare function zenRows(usage: ZenUsage | undefined, meta: ModelMeta | undefined, session?: ModelUsage): SidebarRow[];
|
package/dist/tui.js
CHANGED
|
@@ -120,7 +120,6 @@ async function runCmduse(arg, opts = {}) {
|
|
|
120
120
|
|
|
121
121
|
// src/sidebar/panel.tsx
|
|
122
122
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
123
|
-
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
124
123
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
125
124
|
import { spread as _$spread } from "@opentui/solid";
|
|
126
125
|
import { mergeProps as _$mergeProps } from "@opentui/solid";
|
|
@@ -469,6 +468,26 @@ function elapsedLabel(resetAtMs, durSecs, nowSecs) {
|
|
|
469
468
|
return "<1%";
|
|
470
469
|
return `${pct}%`;
|
|
471
470
|
}
|
|
471
|
+
function paceEtaSecs(resetAtMs, durSecs, used, cap, nowSecs) {
|
|
472
|
+
if (typeof resetAtMs !== "number" || !Number.isFinite(resetAtMs))
|
|
473
|
+
return;
|
|
474
|
+
const reset = Math.floor(resetAtMs / 1000);
|
|
475
|
+
if (reset < durSecs)
|
|
476
|
+
return;
|
|
477
|
+
const start = reset - durSecs;
|
|
478
|
+
if (nowSecs <= start || nowSecs >= reset)
|
|
479
|
+
return;
|
|
480
|
+
const elapsed = nowSecs - start;
|
|
481
|
+
if (elapsed / durSecs < 0.1)
|
|
482
|
+
return;
|
|
483
|
+
const rate = used / elapsed;
|
|
484
|
+
if (rate <= 0 || used >= cap)
|
|
485
|
+
return;
|
|
486
|
+
const secsToCap = (cap - used) / rate;
|
|
487
|
+
if (secsToCap >= reset - nowSecs)
|
|
488
|
+
return;
|
|
489
|
+
return secsToCap;
|
|
490
|
+
}
|
|
472
491
|
|
|
473
492
|
// src/sidebar/rows.ts
|
|
474
493
|
function pctTone(pct) {
|
|
@@ -533,10 +552,13 @@ function windowRows(label, w, now, durSecs) {
|
|
|
533
552
|
return [];
|
|
534
553
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
535
554
|
const pct = Math.round(used / w.cap * 100);
|
|
536
|
-
const
|
|
555
|
+
const nowSecs = Math.floor(now / 1000);
|
|
556
|
+
const elapsed = elapsedLabel(w.resetAt, durSecs, nowSecs);
|
|
557
|
+
const eta = paceEtaSecs(w.resetAt, durSecs, used, w.cap, nowSecs);
|
|
537
558
|
const reset = until(w.resetAt, now);
|
|
538
|
-
const
|
|
539
|
-
|
|
559
|
+
const lead = w.exceeded ? "LIMIT EXCEEDED" : eta === undefined ? elapsed === undefined ? "" : `${elapsed} elapsed` : `cap in ${until(now + eta * 1000, now)}`;
|
|
560
|
+
const detail = [lead, reset ? `resets ${reset}` : ""].filter(Boolean).join(" \xB7 ");
|
|
561
|
+
return [[label, `${money(used)}/${money(w.cap)} (${pct}%)`, w.exceeded ? "crit" : pctTone(pct)], ...detail ? [["", detail, "muted"]] : []];
|
|
540
562
|
}
|
|
541
563
|
function monthlyDetail(usage, now) {
|
|
542
564
|
const {
|
|
@@ -581,7 +603,7 @@ function usageRows(usage, now = Date.now()) {
|
|
|
581
603
|
}
|
|
582
604
|
return rows;
|
|
583
605
|
}
|
|
584
|
-
function modelRows(meta, usage) {
|
|
606
|
+
function modelRows(meta, usage, session) {
|
|
585
607
|
if (!meta)
|
|
586
608
|
return [];
|
|
587
609
|
const rows = [["Model", clip(meta.name, valueWidth("Model")), "strong", true]];
|
|
@@ -589,6 +611,10 @@ function modelRows(meta, usage) {
|
|
|
589
611
|
const spent = usage.cost > 0 ? ` \xB7 ${money(usage.cost)}` : "";
|
|
590
612
|
rows.push(["Usage (this model)", `${count(usage.requests)} req${spent}`, "base"]);
|
|
591
613
|
}
|
|
614
|
+
if (session) {
|
|
615
|
+
const spent = session.cost > 0 ? ` \xB7 ${money(session.cost)}` : "";
|
|
616
|
+
rows.push(["Session", `${count(session.requests)} req${spent}`, "base"]);
|
|
617
|
+
}
|
|
592
618
|
if (meta.tier)
|
|
593
619
|
rows.push(["Tier", TIER_DISPLAY[meta.tier], "base"]);
|
|
594
620
|
else if (meta.minPlan)
|
|
@@ -704,7 +730,7 @@ function Panel(props) {
|
|
|
704
730
|
var _el$7 = _$createElement("box"), _el$8 = _$createElement("text"), _el$9 = _$createElement("b");
|
|
705
731
|
_$insertNode(_el$7, _el$8);
|
|
706
732
|
_$insertNode(_el$8, _el$9);
|
|
707
|
-
_$
|
|
733
|
+
_$insert(_el$9, () => props.title());
|
|
708
734
|
_$insert(_el$7, _$createComponent(For, {
|
|
709
735
|
get each() {
|
|
710
736
|
return props.rows();
|
|
@@ -1834,8 +1860,107 @@ import { spawn as spawn2 } from "child_process";
|
|
|
1834
1860
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
1835
1861
|
import { homedir as homedir2 } from "os";
|
|
1836
1862
|
import { dirname, join as join2 } from "path";
|
|
1863
|
+
|
|
1864
|
+
// src/sidebar/zen.ts
|
|
1865
|
+
var GO_WINDOW_SHARE = {
|
|
1866
|
+
fiveHour: 0.2,
|
|
1867
|
+
weekly: 0.5,
|
|
1868
|
+
month: 1
|
|
1869
|
+
};
|
|
1870
|
+
function totals(value) {
|
|
1871
|
+
const v = value ?? {};
|
|
1872
|
+
const num = (key) => typeof v[key] === "number" ? v[key] : 0;
|
|
1873
|
+
return {
|
|
1874
|
+
requests: num("requests"),
|
|
1875
|
+
cost: num("cost"),
|
|
1876
|
+
tokens: num("tokens")
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
function parseZenJson(text) {
|
|
1880
|
+
let body;
|
|
1881
|
+
try {
|
|
1882
|
+
body = JSON.parse(text);
|
|
1883
|
+
} catch {
|
|
1884
|
+
body = {};
|
|
1885
|
+
}
|
|
1886
|
+
const models = new Map;
|
|
1887
|
+
for (const raw of body.models ?? []) {
|
|
1888
|
+
if (typeof raw.id !== "string")
|
|
1889
|
+
continue;
|
|
1890
|
+
models.set(modelKey(raw.id), {
|
|
1891
|
+
id: raw.id,
|
|
1892
|
+
provider: typeof raw.provider === "string" ? raw.provider : "",
|
|
1893
|
+
limit: typeof raw.limit === "number" ? raw.limit : undefined,
|
|
1894
|
+
fiveHour: totals(raw.fiveHour),
|
|
1895
|
+
weekly: totals(raw.weekly),
|
|
1896
|
+
month: totals(raw.month)
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
const top = body.totals ?? {};
|
|
1900
|
+
return {
|
|
1901
|
+
periodStart: typeof body.periodStart === "number" ? body.periodStart : undefined,
|
|
1902
|
+
source: typeof body.source === "string" ? body.source : undefined,
|
|
1903
|
+
totals: {
|
|
1904
|
+
fiveHour: totals(top.fiveHour),
|
|
1905
|
+
weekly: totals(top.weekly),
|
|
1906
|
+
month: totals(top.month)
|
|
1907
|
+
},
|
|
1908
|
+
models
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
function windowRows2(label, spent, limit, share) {
|
|
1912
|
+
if (typeof limit !== "number" || limit <= 0) {
|
|
1913
|
+
return [[label, `${money(spent.cost)} \xB7 ${count(spent.requests)} req`, "base"]];
|
|
1914
|
+
}
|
|
1915
|
+
const cap = limit * share;
|
|
1916
|
+
const pct = cap > 0 ? Math.round(spent.cost / cap * 100) : 0;
|
|
1917
|
+
const rows = [[label, `${money(spent.cost)}/${money(cap)} (${pct}%)`, pctTone(pct)]];
|
|
1918
|
+
if (spent.requests > 0)
|
|
1919
|
+
rows.push(["", `${count(spent.requests)} req`, "muted"]);
|
|
1920
|
+
return rows;
|
|
1921
|
+
}
|
|
1922
|
+
function zenRows(usage, meta, session) {
|
|
1923
|
+
if (!usage)
|
|
1924
|
+
return [];
|
|
1925
|
+
const oc = meta?.oc;
|
|
1926
|
+
const rows = [["Plan", `${oc?.plan ?? "Go"} \xB7 limits per model`, "base"]];
|
|
1927
|
+
const mine = meta ? usage.models.get(modelKey(meta.name)) : undefined;
|
|
1928
|
+
const limit = oc?.allowance ?? mine?.limit;
|
|
1929
|
+
rows.push(...windowRows2("5-hour", mine?.fiveHour ?? usage.totals.fiveHour, limit, GO_WINDOW_SHARE.fiveHour));
|
|
1930
|
+
rows.push(...windowRows2("Weekly", mine?.weekly ?? usage.totals.weekly, limit, GO_WINDOW_SHARE.weekly));
|
|
1931
|
+
rows.push(...windowRows2("Month", mine?.month ?? usage.totals.month, limit, GO_WINDOW_SHARE.month));
|
|
1932
|
+
rows.push(["Period", `${count(usage.totals.month.requests)} requests \xB7 ${money(usage.totals.month.cost)}`, "base"]);
|
|
1933
|
+
rows.push(separator());
|
|
1934
|
+
if (!meta)
|
|
1935
|
+
return rows;
|
|
1936
|
+
rows.push(["Model", clip(meta.name, valueWidth("Model")), "strong", true]);
|
|
1937
|
+
if (session) {
|
|
1938
|
+
const spent = session.cost > 0 ? ` \xB7 ${money(session.cost)}` : "";
|
|
1939
|
+
rows.push(["Session", `${count(session.requests)} req${spent}`, "base"]);
|
|
1940
|
+
}
|
|
1941
|
+
if (typeof limit === "number") {
|
|
1942
|
+
rows.push(["Allowance", `${money(limit)}/mo`, "base"]);
|
|
1943
|
+
}
|
|
1944
|
+
if (oc?.rates) {
|
|
1945
|
+
const cache = [`cache read ${rate(oc.rates.cacheRead)}`];
|
|
1946
|
+
if (typeof oc.rates.cacheWrite === "number" && oc.rates.cacheWrite > 0) {
|
|
1947
|
+
cache.push(`write ${rate(oc.rates.cacheWrite)}`);
|
|
1948
|
+
}
|
|
1949
|
+
rows.push(["Rates", `${rate(oc.rates.input)}/${rate(oc.rates.output)} in/out`, "base"]);
|
|
1950
|
+
rows.push(["", cache.join(" \xB7 "), "muted"]);
|
|
1951
|
+
}
|
|
1952
|
+
if (typeof oc?.ability === "number") {
|
|
1953
|
+
rows.push(["Intelligence", String(oc.ability), "base"]);
|
|
1954
|
+
}
|
|
1955
|
+
if (typeof oc?.tps === "number")
|
|
1956
|
+
rows.push(["Tok/s", String(oc.tps), "base"]);
|
|
1957
|
+
return rows;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// src/sidebar/data.ts
|
|
1837
1961
|
var CMDUSE = cmduseCandidates();
|
|
1838
1962
|
var MPC = ["mpc", join2(homedir2(), ".bun/bin/mpc"), "/opt/homebrew/bin/mpc"];
|
|
1963
|
+
var OCUSE = [process.env.OCUSE_BIN, "ocuse", "/opt/homebrew/bin/ocuse", "/usr/local/bin/ocuse"].filter((bin) => Boolean(bin));
|
|
1839
1964
|
var CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
1840
1965
|
var SPAWN_OPTIONS = {
|
|
1841
1966
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -1874,7 +1999,8 @@ function parseUsageJson(text) {
|
|
|
1874
1999
|
return {
|
|
1875
2000
|
cap: typeof w.cap === "number" ? w.cap : undefined,
|
|
1876
2001
|
used: typeof w.used === "number" ? w.used : undefined,
|
|
1877
|
-
resetAt: typeof w.resetAt === "number" ? w.resetAt : undefined
|
|
2002
|
+
resetAt: typeof w.resetAt === "number" ? w.resetAt : undefined,
|
|
2003
|
+
exceeded: typeof w.exceeded === "boolean" ? w.exceeded : undefined
|
|
1878
2004
|
};
|
|
1879
2005
|
};
|
|
1880
2006
|
const summary = raw.summary ?? {};
|
|
@@ -1895,16 +2021,25 @@ function parseMpcJson(text) {
|
|
|
1895
2021
|
const body = JSON.parse(text);
|
|
1896
2022
|
const meta = new Map;
|
|
1897
2023
|
for (const row of body.rows ?? []) {
|
|
1898
|
-
|
|
1899
|
-
if (!cc || !row.name)
|
|
2024
|
+
if (!row.name || !row.cc && !row.oc)
|
|
1900
2025
|
continue;
|
|
2026
|
+
const cc = row.cc;
|
|
2027
|
+
const oc = row.oc;
|
|
1901
2028
|
const entry = {
|
|
1902
2029
|
key: row.key ?? modelKey(row.name),
|
|
1903
2030
|
name: row.name,
|
|
1904
|
-
allowance: cc
|
|
1905
|
-
rates: cc
|
|
1906
|
-
intelligence: cc
|
|
1907
|
-
tps: cc
|
|
2031
|
+
allowance: cc?.allowance,
|
|
2032
|
+
rates: cc?.pricing,
|
|
2033
|
+
intelligence: cc?.ability ?? undefined,
|
|
2034
|
+
tps: cc?.tps ?? undefined,
|
|
2035
|
+
oc: oc ? {
|
|
2036
|
+
provider: oc.provider,
|
|
2037
|
+
plan: oc.plan,
|
|
2038
|
+
allowance: oc.allowance,
|
|
2039
|
+
rates: oc.pricing,
|
|
2040
|
+
ability: oc.ability ?? undefined,
|
|
2041
|
+
tps: oc.tps ?? undefined
|
|
2042
|
+
} : undefined
|
|
1908
2043
|
};
|
|
1909
2044
|
meta.set(entry.key, entry);
|
|
1910
2045
|
meta.set(modelKey(row.name), entry);
|
|
@@ -1938,6 +2073,9 @@ async function loadMeta() {
|
|
|
1938
2073
|
async function loadUsage() {
|
|
1939
2074
|
return parseUsageJson(await runFirst(CMDUSE, ["-1", "--json", "--plain"]));
|
|
1940
2075
|
}
|
|
2076
|
+
async function loadZen() {
|
|
2077
|
+
return parseZenJson(await runFirst(OCUSE, ["-1", "--json"]));
|
|
2078
|
+
}
|
|
1941
2079
|
|
|
1942
2080
|
// src/sidebar/usageDb.ts
|
|
1943
2081
|
import { Database } from "bun:sqlite";
|
|
@@ -2006,13 +2144,69 @@ function loadModelUsage(modelID, sinceMs, path = usageDbPath()) {
|
|
|
2006
2144
|
db.close();
|
|
2007
2145
|
}
|
|
2008
2146
|
}
|
|
2147
|
+
function loadSessionUsage(sessionID, path = usageDbPath()) {
|
|
2148
|
+
let db;
|
|
2149
|
+
try {
|
|
2150
|
+
db = new Database(path, {
|
|
2151
|
+
readonly: true
|
|
2152
|
+
});
|
|
2153
|
+
} catch {
|
|
2154
|
+
return null;
|
|
2155
|
+
}
|
|
2156
|
+
try {
|
|
2157
|
+
const rows = db.query(`SELECT id, data FROM message
|
|
2158
|
+
WHERE session_id = ? AND data LIKE '%"tokens"%'`).all(sessionID);
|
|
2159
|
+
const seen = new Set;
|
|
2160
|
+
let requests = 0;
|
|
2161
|
+
let cost = 0;
|
|
2162
|
+
for (const row of rows) {
|
|
2163
|
+
if (seen.has(row.id))
|
|
2164
|
+
continue;
|
|
2165
|
+
seen.add(row.id);
|
|
2166
|
+
let data;
|
|
2167
|
+
try {
|
|
2168
|
+
data = JSON.parse(row.data);
|
|
2169
|
+
} catch {
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (data.role !== "assistant" || !data.tokens)
|
|
2173
|
+
continue;
|
|
2174
|
+
requests += 1;
|
|
2175
|
+
cost += usage(data.cost);
|
|
2176
|
+
}
|
|
2177
|
+
return {
|
|
2178
|
+
requests,
|
|
2179
|
+
cost
|
|
2180
|
+
};
|
|
2181
|
+
} catch {
|
|
2182
|
+
return null;
|
|
2183
|
+
} finally {
|
|
2184
|
+
db.close();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2009
2187
|
|
|
2010
2188
|
// src/sidebar/useRows.ts
|
|
2011
2189
|
var POLL_MS = 60000;
|
|
2012
2190
|
function sessionKind(providerID) {
|
|
2013
2191
|
if (!providerID)
|
|
2014
2192
|
return "unknown";
|
|
2015
|
-
|
|
2193
|
+
if (providerID.startsWith("command-code"))
|
|
2194
|
+
return "ours";
|
|
2195
|
+
if (providerID.startsWith("opencode-go"))
|
|
2196
|
+
return "go";
|
|
2197
|
+
if (providerID === "opencode" || providerID.startsWith("opencode-zen"))
|
|
2198
|
+
return "zen";
|
|
2199
|
+
return "other";
|
|
2200
|
+
}
|
|
2201
|
+
function shows(kind) {
|
|
2202
|
+
return kind === "ours" || kind === "go" || kind === "zen";
|
|
2203
|
+
}
|
|
2204
|
+
function panelTitle(kind) {
|
|
2205
|
+
if (kind === "ours")
|
|
2206
|
+
return "Command Code";
|
|
2207
|
+
if (kind === "zen")
|
|
2208
|
+
return "OpenCode Zen";
|
|
2209
|
+
return "OpenCode Go";
|
|
2016
2210
|
}
|
|
2017
2211
|
var lastSnapshot;
|
|
2018
2212
|
function rememberSnapshot(snapshot) {
|
|
@@ -2035,10 +2229,12 @@ function seededModelUsage(id) {
|
|
|
2035
2229
|
return lastModelUsage.usage;
|
|
2036
2230
|
return lastModelUsage.key === modelKey(id) ? lastModelUsage.usage : undefined;
|
|
2037
2231
|
}
|
|
2038
|
-
function useRows(activeModelId, providerID) {
|
|
2232
|
+
function useRows(activeModelId, providerID, sessionID) {
|
|
2039
2233
|
const [usage, setUsage] = createSignal(seededRows());
|
|
2234
|
+
const [zen, setZen] = createSignal();
|
|
2040
2235
|
const [meta, setMeta] = createSignal(new Map);
|
|
2041
2236
|
const [modelUsage, setModelUsage] = createSignal(lastModelUsage);
|
|
2237
|
+
const [sessionUsage, setSessionUsage] = createSignal();
|
|
2042
2238
|
const [wasOurs, setWasOurs] = createSignal(false);
|
|
2043
2239
|
loadMeta().then(setMeta).catch(() => {});
|
|
2044
2240
|
const refreshModelUsage = (id, sinceMs) => {
|
|
@@ -2050,16 +2246,24 @@ function useRows(activeModelId, providerID) {
|
|
|
2050
2246
|
setModelUsage(lastModelUsage);
|
|
2051
2247
|
};
|
|
2052
2248
|
const refresh = async () => {
|
|
2249
|
+
const kind = sessionKind(providerID());
|
|
2250
|
+
const session = sessionID();
|
|
2251
|
+
if (session)
|
|
2252
|
+
setSessionUsage(loadSessionUsage(session) ?? undefined);
|
|
2053
2253
|
try {
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2254
|
+
if (kind === "ours") {
|
|
2255
|
+
const snapshot = await loadUsage();
|
|
2256
|
+
rememberSnapshot(snapshot);
|
|
2257
|
+
setUsage(usageRows(snapshot));
|
|
2258
|
+
refreshModelUsage(activeModelId(), periodStart(snapshot));
|
|
2259
|
+
} else if (kind === "go" || kind === "zen") {
|
|
2260
|
+
setZen(await loadZen());
|
|
2261
|
+
}
|
|
2058
2262
|
} catch {}
|
|
2059
2263
|
};
|
|
2060
2264
|
createEffect(() => {
|
|
2061
2265
|
const kind = sessionKind(providerID());
|
|
2062
|
-
if (kind
|
|
2266
|
+
if (!shows(kind))
|
|
2063
2267
|
return;
|
|
2064
2268
|
setWasOurs(true);
|
|
2065
2269
|
refresh();
|
|
@@ -2081,18 +2285,19 @@ function useRows(activeModelId, providerID) {
|
|
|
2081
2285
|
};
|
|
2082
2286
|
return createMemo(() => {
|
|
2083
2287
|
const kind = sessionKind(providerID());
|
|
2084
|
-
if (kind === "other")
|
|
2085
|
-
return [];
|
|
2086
|
-
if (kind === "unknown" && !wasOurs())
|
|
2288
|
+
if (!shows(kind) && (kind === "other" || !wasOurs()))
|
|
2087
2289
|
return [];
|
|
2088
2290
|
const id = activeModelId();
|
|
2089
2291
|
const found = id ? meta().get(modelKey(id)) ?? meta().get(id) : undefined;
|
|
2292
|
+
if (kind === "go" || kind === "zen") {
|
|
2293
|
+
return zenRows(zen(), found, sessionUsage());
|
|
2294
|
+
}
|
|
2090
2295
|
const model = found ? {
|
|
2091
2296
|
...found,
|
|
2092
2297
|
tier: found.tier ?? (id ? tierFor(id) : undefined),
|
|
2093
2298
|
minPlan: id ? minPlan(id) : undefined
|
|
2094
2299
|
} : undefined;
|
|
2095
|
-
return [...usage(), separator(), ...modelRows(model, usageFor(id))];
|
|
2300
|
+
return [...usage(), separator(), ...modelRows(model, usageFor(id), sessionUsage())];
|
|
2096
2301
|
});
|
|
2097
2302
|
}
|
|
2098
2303
|
|
|
@@ -2101,8 +2306,9 @@ var ID = "command-code.tui";
|
|
|
2101
2306
|
var themed = (colors) => loadPrefs().colors ? colors : mono(colors);
|
|
2102
2307
|
function PanelV1(props) {
|
|
2103
2308
|
const current = () => props.api.state.session.get(props.sessionID)?.model;
|
|
2104
|
-
const rows = useRows(() => current()?.id, () => current()?.providerID);
|
|
2309
|
+
const rows = useRows(() => current()?.id, () => current()?.providerID, () => props.sessionID);
|
|
2105
2310
|
return _$createComponent2(Panel, {
|
|
2311
|
+
title: () => panelTitle(sessionKind(current()?.providerID)),
|
|
2106
2312
|
rows,
|
|
2107
2313
|
colors: () => themed(legacyColors(props.api.theme.current))
|
|
2108
2314
|
});
|
|
@@ -2127,8 +2333,9 @@ function PanelV2(props) {
|
|
|
2127
2333
|
ctx
|
|
2128
2334
|
} = props;
|
|
2129
2335
|
const current = () => ctx.data.session.get(props.sessionID)?.model;
|
|
2130
|
-
const rows = useRows(() => current()?.id, () => current()?.providerID);
|
|
2336
|
+
const rows = useRows(() => current()?.id, () => current()?.providerID, () => props.sessionID);
|
|
2131
2337
|
return _$createComponent2(Panel, {
|
|
2338
|
+
title: () => panelTitle(sessionKind(current()?.providerID)),
|
|
2132
2339
|
rows,
|
|
2133
2340
|
colors: () => themed(hostColors(ctx.theme))
|
|
2134
2341
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffreyjyz/opencode-command-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
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",
|