@jeffreyjyz/opencode-command-code 0.5.1 → 0.5.3

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 CHANGED
@@ -2414,7 +2414,7 @@ function superRefine(fn) {
2414
2414
  // package.json
2415
2415
  var package_default = {
2416
2416
  name: "@jeffreyjyz/opencode-command-code",
2417
- version: "0.5.1",
2417
+ version: "0.5.3",
2418
2418
  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.",
2419
2419
  type: "module",
2420
2420
  main: "dist/index.js",
@@ -24,6 +24,21 @@ export declare function periodStart(usage: {
24
24
  * while the session said 2.7K).
25
25
  */
26
26
  export declare function loadModelUsage(modelID: string, sinceMs: number, path?: string, sessionID?: string): ModelUsage | null;
27
+ /**
28
+ * The oldest in-window assistant turn for one model, as epoch ms — the start of
29
+ * the rolling window the Go/Zen panel needs. A rolling 5-hour/weekly window has
30
+ * no service-reported reset (Go has no usage API), but its clock does have a
31
+ * real start: the oldest request still inside it ages out at `start + window`,
32
+ * which is that window's honest reset. Null when the model has no turn in the
33
+ * window, or the store is missing/unreadable, so callers then omit the
34
+ * continuation exactly as before.
35
+ *
36
+ * Account-wide like ocuse's per-model window totals, so it is *not* scoped to a
37
+ * session: a rolling window counts the model's spend across every conversation.
38
+ * Matching is on the canonical key, the same normalisation `loadModelUsage` uses
39
+ * (the session id and the stored `modelID` differ by vendor prefix/punctuation).
40
+ */
41
+ export declare function loadModelWindowStart(modelID: string, sinceMs: number, path?: string): number | null;
27
42
  /**
28
43
  * Requests and cost for one session, so the panel can show what this
29
44
  * conversation has spent rather than only the period-to-date figure. Null when
@@ -34,9 +34,26 @@ export interface ZenUsage {
34
34
  }
35
35
  /** Parse `ocuse -1 --json`. Anything malformed reads as "no usage". */
36
36
  export declare function parseZenJson(text: string): ZenUsage;
37
+ /**
38
+ * The starts of the active model's rolling windows, epoch ms: the oldest request
39
+ * still inside each one, from opencode's own store. The Go/Zen panel turns each
40
+ * into a reset via `rollingReset`; absent when no such request exists.
41
+ */
42
+ export interface ZenWindowStarts {
43
+ fiveHour?: number;
44
+ weekly?: number;
45
+ }
46
+ /**
47
+ * A rolling window's reset, derived from the oldest request still inside it:
48
+ * that request leaves the window at `oldest + durSecs`, which is an honest reset
49
+ * epoch — unlike `now + durSecs`, a constant that would be noise in a usage
50
+ * panel. Undefined when there is no oldest request (an empty window), so the row
51
+ * keeps the same spend-only degrade it had before.
52
+ */
53
+ export declare function rollingReset(startMs: number | undefined, durSecs: number): number | undefined;
37
54
  /** The OpenCode Go / Zen panel: period windows, a rule, then the active model.
38
55
  * Like `modelRows`, `fallbackName` (the session's own id) keeps the model block
39
56
  * — and the local `Usage (this model)` / `Session` figures, which need no network
40
57
  * — on screen when mpc's catalog is unreachable; only the catalog-derived rows
41
58
  * then drop out. */
42
- export declare function zenRows(usage: ZenUsage | undefined, meta: ModelMeta | undefined, modelUsage?: ModelUsage, session?: ModelUsage, fallbackName?: string): SidebarRow[];
59
+ export declare function zenRows(usage: ZenUsage | undefined, meta: ModelMeta | undefined, modelUsage?: ModelUsage, session?: ModelUsage, fallbackName?: string, windows?: ZenWindowStarts): SidebarRow[];
package/dist/tui.js CHANGED
@@ -2090,6 +2090,11 @@ function monthPeriod(usage) {
2090
2090
  durSecs
2091
2091
  };
2092
2092
  }
2093
+ function rollingReset(startMs, durSecs) {
2094
+ if (typeof startMs !== "number" || !Number.isFinite(startMs))
2095
+ return;
2096
+ return startMs + durSecs * 1000;
2097
+ }
2093
2098
  function zenWindow(label, spent, limit, share, now, durSecs, resetAt) {
2094
2099
  const cap = typeof limit === "number" && limit > 0 ? limit * share : undefined;
2095
2100
  if (cap === undefined) {
@@ -2102,17 +2107,17 @@ function zenWindow(label, spent, limit, share, now, durSecs, resetAt) {
2102
2107
  exceeded: spent.cost >= cap
2103
2108
  }, now, durSecs, spent.requests > 0 ? `${count(spent.requests)} req` : undefined);
2104
2109
  }
2105
- function zenRows(usage, meta, modelUsage, session, fallbackName) {
2110
+ function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
2106
2111
  if (!usage)
2107
2112
  return [];
2108
2113
  const now = Date.now();
2109
2114
  const oc = meta?.oc;
2110
2115
  const rows = [["Plan", `${oc?.plan ?? "Go"} \xB7 limits per model`, "base"]];
2111
- const mine = meta ? usage.models.get(modelKey(meta.name)) : undefined;
2116
+ const mine = usage.models.get(modelKey(fallbackName ?? "")) ?? (meta ? usage.models.get(modelKey(meta.name)) : undefined);
2112
2117
  const limit = oc?.allowance ?? mine?.limit;
2113
2118
  const cap = mine ? limit : undefined;
2114
- rows.push(...zenWindow("5-hour", mine?.fiveHour ?? usage.totals.fiveHour, cap, GO_WINDOW_SHARE.fiveHour, now, FIVE_HOUR_SECS));
2115
- rows.push(...zenWindow("Weekly", mine?.weekly ?? usage.totals.weekly, cap, GO_WINDOW_SHARE.weekly, now, WEEKLY_SECS));
2119
+ rows.push(...zenWindow("5-hour", mine?.fiveHour ?? usage.totals.fiveHour, cap, GO_WINDOW_SHARE.fiveHour, now, FIVE_HOUR_SECS, rollingReset(windows?.fiveHour, FIVE_HOUR_SECS)));
2120
+ rows.push(...zenWindow("Weekly", mine?.weekly ?? usage.totals.weekly, cap, GO_WINDOW_SHARE.weekly, now, WEEKLY_SECS, rollingReset(windows?.weekly, WEEKLY_SECS)));
2116
2121
  const period = monthPeriod(usage);
2117
2122
  rows.push(...zenWindow("Monthly", mine?.month ?? usage.totals.month, cap, GO_WINDOW_SHARE.month, now, period?.durSecs ?? 0, period?.resetAt));
2118
2123
  rows.push(["Period", `${count(usage.totals.month.requests)} requests \xB7 ${money(usage.totals.month.cost)}`, "base"]);
@@ -2122,11 +2127,11 @@ function zenRows(usage, meta, modelUsage, session, fallbackName) {
2122
2127
  return rows;
2123
2128
  rows.push(["Model", clip(name, valueWidth("Model")), "strong", true]);
2124
2129
  rows.push(...localUsageRows(modelUsage, session));
2125
- if (!meta)
2126
- return rows;
2127
2130
  if (typeof limit === "number") {
2128
2131
  rows.push(["Allowance", allowanceLabel(limit, oc?.free), "base"]);
2129
2132
  }
2133
+ if (!meta)
2134
+ return rows;
2130
2135
  if (oc?.rates) {
2131
2136
  const cache = [`cache read ${rate(oc.rates.cacheRead)}`];
2132
2137
  if (typeof oc.rates.cacheWrite === "number" && oc.rates.cacheWrite > 0) {
@@ -2375,6 +2380,53 @@ function loadModelUsage(modelID, sinceMs, path = usageDbPath(), sessionID) {
2375
2380
  db.close();
2376
2381
  }
2377
2382
  }
2383
+ function loadModelWindowStart(modelID, sinceMs, path = usageDbPath()) {
2384
+ let db;
2385
+ try {
2386
+ db = new Database(path, {
2387
+ readonly: true
2388
+ });
2389
+ } catch {
2390
+ return null;
2391
+ }
2392
+ try {
2393
+ const key = modelKey(modelID);
2394
+ if (hasV2(db)) {
2395
+ const rows = db.query(`SELECT json_extract(data, '$.model.id') AS model,
2396
+ min(time_created) AS start
2397
+ FROM session_message
2398
+ WHERE type = 'assistant' AND time_created >= ?
2399
+ GROUP BY model`).all(sinceMs);
2400
+ for (const row of rows) {
2401
+ if (row.model && modelKey(row.model) === key)
2402
+ return row.start;
2403
+ }
2404
+ return null;
2405
+ }
2406
+ const rows = db.query(`SELECT data, time_created FROM message
2407
+ WHERE data LIKE '%"tokens"%' AND time_created >= ?`).all(sinceMs);
2408
+ let oldest = null;
2409
+ for (const row of rows) {
2410
+ let data;
2411
+ try {
2412
+ data = JSON.parse(row.data);
2413
+ } catch {
2414
+ continue;
2415
+ }
2416
+ if (data.role !== "assistant" || !data.tokens || !data.modelID)
2417
+ continue;
2418
+ if (modelKey(data.modelID) !== key)
2419
+ continue;
2420
+ if (oldest === null || row.time_created < oldest)
2421
+ oldest = row.time_created;
2422
+ }
2423
+ return oldest;
2424
+ } catch {
2425
+ return null;
2426
+ } finally {
2427
+ db.close();
2428
+ }
2429
+ }
2378
2430
  function loadSessionUsage(sessionID, path = usageDbPath()) {
2379
2431
  let db;
2380
2432
  try {
@@ -2485,6 +2537,7 @@ function useRows(activeModelId, providerID, sessionID) {
2485
2537
  const [meta, setMeta] = createSignal(seededMeta());
2486
2538
  const [modelUsage, setModelUsage] = createSignal(lastModelUsage);
2487
2539
  const [sessionUsage, setSessionUsage] = createSignal();
2540
+ const [modelWindows, setModelWindows] = createSignal();
2488
2541
  const [wasOurs, setWasOurs] = createSignal(false);
2489
2542
  const refreshMeta = () => {
2490
2543
  loadMeta(loadPrefs().ocPlan).then((next) => {
@@ -2505,6 +2558,15 @@ function useRows(activeModelId, providerID, sessionID) {
2505
2558
  rememberModelUsage(id, fresh, session);
2506
2559
  setModelUsage(lastModelUsage);
2507
2560
  };
2561
+ const refreshModelWindows = (id) => {
2562
+ if (!id)
2563
+ return;
2564
+ const now = Date.now();
2565
+ setModelWindows({
2566
+ fiveHour: loadModelWindowStart(id, now - FIVE_HOUR_SECS * 1000) ?? undefined,
2567
+ weekly: loadModelWindowStart(id, now - WEEKLY_SECS * 1000) ?? undefined
2568
+ });
2569
+ };
2508
2570
  let inFlight = false;
2509
2571
  const refresh = async () => {
2510
2572
  if (inFlight)
@@ -2524,6 +2586,7 @@ function useRows(activeModelId, providerID, sessionID) {
2524
2586
  } else if (kind === "go" || kind === "zen") {
2525
2587
  setZen(await loadZen(loadPrefs().ocPlan));
2526
2588
  refreshModelUsage(activeModelId());
2589
+ refreshModelWindows(activeModelId());
2527
2590
  }
2528
2591
  } catch {} finally {
2529
2592
  inFlight = false;
@@ -2541,7 +2604,12 @@ function useRows(activeModelId, providerID, sessionID) {
2541
2604
  createEffect(() => {
2542
2605
  const id = activeModelId();
2543
2606
  const session = sessionID();
2544
- if (!shows(sessionKind(providerID())) || !id || !session)
2607
+ const kind = sessionKind(providerID());
2608
+ if (!shows(kind) || !id)
2609
+ return;
2610
+ if (kind === "go" || kind === "zen")
2611
+ refreshModelWindows(id);
2612
+ if (!session)
2545
2613
  return;
2546
2614
  refreshModelUsage(id);
2547
2615
  });
@@ -2561,7 +2629,7 @@ function useRows(activeModelId, providerID, sessionID) {
2561
2629
  const found = id ? meta().get(modelKey(id)) ?? meta().get(id) : undefined;
2562
2630
  const fallbackName = id ? id.split("/").pop() ?? id : undefined;
2563
2631
  if (kind === "go" || kind === "zen") {
2564
- return zenRows(zen(), found, usageFor(id), sessionUsage(), fallbackName);
2632
+ return zenRows(zen(), found, usageFor(id), sessionUsage(), fallbackName, modelWindows());
2565
2633
  }
2566
2634
  const model = found ? {
2567
2635
  ...found,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffreyjyz/opencode-command-code",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
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",