@jeffreyjyz/opencode-command-code 0.3.12 → 0.3.14
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/usageDb.d.ts +7 -1
- package/dist/sidebar/windows.d.ts +4 -3
- package/dist/tui.js +48 -27
- 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.14",
|
|
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",
|
|
@@ -16,8 +16,14 @@ export declare function periodStart(usage: {
|
|
|
16
16
|
* missing or unreadable, so callers can fall back to omitting the row.
|
|
17
17
|
* Matching is on the canonical key: the session id and the stored modelID can
|
|
18
18
|
* differ by vendor prefix/punctuation.
|
|
19
|
+
*
|
|
20
|
+
* Given a `sessionID`, the scan is scoped to that conversation and `sinceMs` is
|
|
21
|
+
* ignored — the panel shows "this model in this session" beside the session's
|
|
22
|
+
* own total, and two figures measuring different windows than each other read
|
|
23
|
+
* as a bug (the billing period had just rolled over, so the model row said 129
|
|
24
|
+
* while the session said 2.7K).
|
|
19
25
|
*/
|
|
20
|
-
export declare function loadModelUsage(modelID: string, sinceMs: number, path?: string): ModelUsage | null;
|
|
26
|
+
export declare function loadModelUsage(modelID: string, sinceMs: number, path?: string, sessionID?: string): ModelUsage | null;
|
|
21
27
|
/**
|
|
22
28
|
* Requests and cost for one session, so the panel can show what this
|
|
23
29
|
* conversation has spent rather than only the period-to-date figure. Null when
|
|
@@ -9,9 +9,10 @@ export declare const WEEKLY_SECS: number;
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function elapsedPct(resetAtMs: number | undefined, durSecs: number, nowSecs: number): number | undefined;
|
|
11
11
|
/**
|
|
12
|
-
* `elapsedPct` as text: a seven-day window forty minutes in is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `elapsedPct` as text, one decimal: a seven-day window forty minutes in is
|
|
13
|
+
* "0.4%". Only a fraction too small to show at one decimal (<0.05%) reads
|
|
14
|
+
* "<0.1%", so a just-started window is not mistaken for an unstarted one.
|
|
15
|
+
* Exact zero stays "0.0%".
|
|
15
16
|
*/
|
|
16
17
|
export declare function elapsedLabel(resetAtMs: number | undefined, durSecs: number, nowSecs: number): string | undefined;
|
|
17
18
|
/**
|
package/dist/tui.js
CHANGED
|
@@ -458,15 +458,15 @@ function elapsedPct(resetAtMs, durSecs, nowSecs) {
|
|
|
458
458
|
if (nowSecs < start)
|
|
459
459
|
return;
|
|
460
460
|
const pct = (nowSecs - start) / durSecs * 100;
|
|
461
|
-
return Math.round(Math.min(100, Math.max(0, pct)));
|
|
461
|
+
return Math.round(Math.min(100, Math.max(0, pct)) * 10) / 10;
|
|
462
462
|
}
|
|
463
463
|
function elapsedLabel(resetAtMs, durSecs, nowSecs) {
|
|
464
464
|
const pct = elapsedPct(resetAtMs, durSecs, nowSecs);
|
|
465
465
|
if (pct === undefined)
|
|
466
466
|
return;
|
|
467
467
|
if (pct === 0 && nowSecs > Math.floor(resetAtMs / 1000) - durSecs)
|
|
468
|
-
return "<1%";
|
|
469
|
-
return `${pct}%`;
|
|
468
|
+
return "<0.1%";
|
|
469
|
+
return `${pct.toFixed(1)}%`;
|
|
470
470
|
}
|
|
471
471
|
function paceEtaSecs(resetAtMs, durSecs, used, cap, nowSecs) {
|
|
472
472
|
if (typeof resetAtMs !== "number" || !Number.isFinite(resetAtMs))
|
|
@@ -551,14 +551,14 @@ function windowRows(label, w, now, durSecs) {
|
|
|
551
551
|
if (!w || typeof w.cap !== "number" || w.cap <= 0)
|
|
552
552
|
return [];
|
|
553
553
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
554
|
-
const pct =
|
|
554
|
+
const pct = used / w.cap * 100;
|
|
555
555
|
const nowSecs = Math.floor(now / 1000);
|
|
556
556
|
const elapsed = elapsedLabel(w.resetAt, durSecs, nowSecs);
|
|
557
557
|
const eta = paceEtaSecs(w.resetAt, durSecs, used, w.cap, nowSecs);
|
|
558
558
|
const reset = until(w.resetAt, now);
|
|
559
559
|
const lead = w.exceeded ? "LIMIT EXCEEDED" : eta === undefined ? elapsed === undefined ? "" : `${elapsed} elapsed` : `cap in ${until(now + eta * 1000, now)}`;
|
|
560
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"]] : []];
|
|
561
|
+
return [[label, `${money(used)}/${money(w.cap)} (${pct.toFixed(1)}%)`, w.exceeded ? "crit" : pctTone(pct)], ...detail ? [["", detail, "muted"]] : []];
|
|
562
562
|
}
|
|
563
563
|
function monthlyDetail(usage, now) {
|
|
564
564
|
const {
|
|
@@ -585,8 +585,8 @@ function usageRows(usage, now = Date.now()) {
|
|
|
585
585
|
}
|
|
586
586
|
if (typeof usage.monthlyCap === "number" && typeof usage.monthlyCredits === "number") {
|
|
587
587
|
const used = Math.min(usage.monthlyCap, Math.max(0, usage.monthlyCap - usage.monthlyCredits));
|
|
588
|
-
const pct = usage.monthlyCap > 0 ?
|
|
589
|
-
rows.push(["Monthly", `${money(used)} / ${money(usage.monthlyCap)} (${pct}%)`, pctTone(pct)]);
|
|
588
|
+
const pct = usage.monthlyCap > 0 ? used / usage.monthlyCap * 100 : 0;
|
|
589
|
+
rows.push(["Monthly", `${money(used)} / ${money(usage.monthlyCap)} (${pct.toFixed(1)}%)`, pctTone(pct)]);
|
|
590
590
|
const detail = monthlyDetail(usage, now);
|
|
591
591
|
if (detail)
|
|
592
592
|
rows.push(["", detail, "muted"]);
|
|
@@ -1913,8 +1913,8 @@ function windowRows2(label, spent, limit, share) {
|
|
|
1913
1913
|
return [[label, `${money(spent.cost)} \xB7 ${count(spent.requests)} req`, "base"]];
|
|
1914
1914
|
}
|
|
1915
1915
|
const cap = limit * share;
|
|
1916
|
-
const pct = cap > 0 ?
|
|
1917
|
-
const rows = [[label, `${money(spent.cost)}/${money(cap)} (${pct}%)`, pctTone(pct)]];
|
|
1916
|
+
const pct = cap > 0 ? spent.cost / cap * 100 : 0;
|
|
1917
|
+
const rows = [[label, `${money(spent.cost)}/${money(cap)} (${pct.toFixed(1)}%)`, pctTone(pct)]];
|
|
1918
1918
|
if (spent.requests > 0)
|
|
1919
1919
|
rows.push(["", `${count(spent.requests)} req`, "muted"]);
|
|
1920
1920
|
return rows;
|
|
@@ -2087,20 +2087,14 @@ function usageDbPath() {
|
|
|
2087
2087
|
const base = process.env.XDG_DATA_HOME ?? join3(homedir3(), ".local", "share");
|
|
2088
2088
|
return join3(base, "opencode", "opencode.db");
|
|
2089
2089
|
}
|
|
2090
|
-
function periodStart(usage, now = Date.now()) {
|
|
2091
|
-
if (typeof usage.periodStartAt === "number")
|
|
2092
|
-
return usage.periodStartAt;
|
|
2093
|
-
const end = usage.periodEnd ? Date.parse(`${usage.periodEnd}T23:59:59`) : Number.NaN;
|
|
2094
|
-
if (!Number.isFinite(end))
|
|
2095
|
-
return now - 30 * 24 * 60 * 60 * 1000;
|
|
2096
|
-
const start = new Date(end);
|
|
2097
|
-
start.setMonth(start.getMonth() - 1);
|
|
2098
|
-
return start.getTime();
|
|
2099
|
-
}
|
|
2100
2090
|
function usage(value) {
|
|
2101
2091
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2102
2092
|
}
|
|
2103
|
-
function
|
|
2093
|
+
function hasV2(db) {
|
|
2094
|
+
const row = db.query("SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = 'session_message'").get();
|
|
2095
|
+
return Boolean(row);
|
|
2096
|
+
}
|
|
2097
|
+
function loadModelUsage(modelID, sinceMs, path = usageDbPath(), sessionID) {
|
|
2104
2098
|
let db;
|
|
2105
2099
|
try {
|
|
2106
2100
|
db = new Database(path, {
|
|
@@ -2110,8 +2104,26 @@ function loadModelUsage(modelID, sinceMs, path = usageDbPath()) {
|
|
|
2110
2104
|
return null;
|
|
2111
2105
|
}
|
|
2112
2106
|
try {
|
|
2107
|
+
if (hasV2(db)) {
|
|
2108
|
+
const rows = db.query(`SELECT json_extract(data, '$.model.id') AS model, count(*) AS c
|
|
2109
|
+
FROM session_message
|
|
2110
|
+
WHERE type = 'assistant'
|
|
2111
|
+
${sessionID ? "AND session_id = ?" : "AND time_created >= ?"}
|
|
2112
|
+
GROUP BY model`).all(sessionID ?? sinceMs);
|
|
2113
|
+
const key = modelKey(modelID);
|
|
2114
|
+
let requests = 0;
|
|
2115
|
+
for (const row of rows) {
|
|
2116
|
+
if (row.model && modelKey(row.model) === key)
|
|
2117
|
+
requests += row.c;
|
|
2118
|
+
}
|
|
2119
|
+
return {
|
|
2120
|
+
requests,
|
|
2121
|
+
cost: 0
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2113
2124
|
const rows = db.query(`SELECT data, time_created FROM message
|
|
2114
|
-
WHERE data LIKE '%"tokens"%'
|
|
2125
|
+
WHERE data LIKE '%"tokens"%'
|
|
2126
|
+
${sessionID ? "AND session_id = ?" : "AND time_created >= ?"}`).all(sessionID ?? sinceMs);
|
|
2115
2127
|
const key = modelKey(modelID);
|
|
2116
2128
|
const seen = new Set;
|
|
2117
2129
|
let requests = 0;
|
|
@@ -2154,6 +2166,14 @@ function loadSessionUsage(sessionID, path = usageDbPath()) {
|
|
|
2154
2166
|
return null;
|
|
2155
2167
|
}
|
|
2156
2168
|
try {
|
|
2169
|
+
if (hasV2(db)) {
|
|
2170
|
+
const totals = db.query("SELECT cost FROM session_v2 WHERE id = ?").get(sessionID);
|
|
2171
|
+
const counted = db.query("SELECT count(*) AS c FROM session_message WHERE session_id = ? AND type = 'assistant'").get(sessionID);
|
|
2172
|
+
return {
|
|
2173
|
+
requests: counted.c,
|
|
2174
|
+
cost: typeof totals?.cost === "number" ? totals.cost : 0
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2157
2177
|
const rows = db.query(`SELECT id, data FROM message
|
|
2158
2178
|
WHERE session_id = ? AND data LIKE '%"tokens"%'`).all(sessionID);
|
|
2159
2179
|
const seen = new Set;
|
|
@@ -2186,7 +2206,7 @@ function loadSessionUsage(sessionID, path = usageDbPath()) {
|
|
|
2186
2206
|
}
|
|
2187
2207
|
|
|
2188
2208
|
// src/sidebar/useRows.ts
|
|
2189
|
-
var POLL_MS =
|
|
2209
|
+
var POLL_MS = 20000;
|
|
2190
2210
|
function sessionKind(providerID) {
|
|
2191
2211
|
if (!providerID)
|
|
2192
2212
|
return "unknown";
|
|
@@ -2237,10 +2257,10 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2237
2257
|
const [sessionUsage, setSessionUsage] = createSignal();
|
|
2238
2258
|
const [wasOurs, setWasOurs] = createSignal(false);
|
|
2239
2259
|
loadMeta().then(setMeta).catch(() => {});
|
|
2240
|
-
const refreshModelUsage = (id
|
|
2260
|
+
const refreshModelUsage = (id) => {
|
|
2241
2261
|
if (!id)
|
|
2242
2262
|
return;
|
|
2243
|
-
const fresh = loadModelUsage(id,
|
|
2263
|
+
const fresh = loadModelUsage(id, 0, usageDbPath(), sessionID());
|
|
2244
2264
|
if (fresh)
|
|
2245
2265
|
rememberModelUsage(id, fresh);
|
|
2246
2266
|
setModelUsage(lastModelUsage);
|
|
@@ -2255,7 +2275,7 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2255
2275
|
const snapshot = await loadUsage();
|
|
2256
2276
|
rememberSnapshot(snapshot);
|
|
2257
2277
|
setUsage(usageRows(snapshot));
|
|
2258
|
-
refreshModelUsage(activeModelId()
|
|
2278
|
+
refreshModelUsage(activeModelId());
|
|
2259
2279
|
} else if (kind === "go" || kind === "zen") {
|
|
2260
2280
|
setZen(await loadZen());
|
|
2261
2281
|
}
|
|
@@ -2272,9 +2292,10 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2272
2292
|
});
|
|
2273
2293
|
createEffect(() => {
|
|
2274
2294
|
const id = activeModelId();
|
|
2275
|
-
|
|
2295
|
+
const session = sessionID();
|
|
2296
|
+
if (sessionKind(providerID()) !== "ours" || !id || !session)
|
|
2276
2297
|
return;
|
|
2277
|
-
refreshModelUsage(id
|
|
2298
|
+
refreshModelUsage(id);
|
|
2278
2299
|
});
|
|
2279
2300
|
const usageFor = (id) => {
|
|
2280
2301
|
const seen = modelUsage();
|
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.14",
|
|
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",
|