@jeffreyjyz/opencode-command-code 0.2.8 → 0.2.11
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/catalog.d.ts +29 -0
- package/dist/index.js +2542 -12
- package/dist/sidebar/rows.d.ts +8 -2
- package/dist/sidebar/usageDb.d.ts +16 -0
- package/dist/sidebar/windows.d.ts +10 -0
- package/dist/tui.d.ts +3 -3
- package/dist/tui.js +101 -10
- package/dist/v2.d.ts +12 -5
- package/package.json +8 -6
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;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 5-hour window length in seconds. Mirrors cmduse_core::FIVE_HOUR_SECS. */
|
|
2
|
+
export declare const FIVE_HOUR_SECS: number;
|
|
3
|
+
/** Weekly window length in seconds. Mirrors cmduse_core::WEEKLY_SECS. */
|
|
4
|
+
export declare const WEEKLY_SECS: number;
|
|
5
|
+
/**
|
|
6
|
+
* Percentage of a rolling window that has elapsed, or undefined when the
|
|
7
|
+
* window has not started (or has no reset time). `nowSecs` is whole seconds —
|
|
8
|
+
* the same unit cmduse-core's vectors pin.
|
|
9
|
+
*/
|
|
10
|
+
export declare function elapsedPct(resetAtMs: number | undefined, durSecs: number, nowSecs: number): number | undefined;
|
package/dist/tui.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
import type { RGBA } from "@opentui/core";
|
|
3
|
-
import { Plugin } from "@opencode/plugin/tui";
|
|
3
|
+
import type { Plugin as TuiPluginNs } from "@opencode/plugin/tui";
|
|
4
4
|
interface V1Api {
|
|
5
5
|
slots: {
|
|
6
6
|
register: (config: {
|
|
@@ -31,10 +31,10 @@ interface V1Api {
|
|
|
31
31
|
}
|
|
32
32
|
/** v1 half — snake_case slot map registered through `api.slots`. */
|
|
33
33
|
export declare const tui: (api: V1Api) => Promise<void>;
|
|
34
|
-
export declare const commandCodeTui:
|
|
34
|
+
export declare const commandCodeTui: TuiPluginNs.Definition;
|
|
35
35
|
declare const _default: {
|
|
36
36
|
tui: (api: V1Api) => Promise<void>;
|
|
37
37
|
id: string;
|
|
38
|
-
setup: (context:
|
|
38
|
+
setup: (context: TuiPluginNs.Context) => Promise<TuiPluginNs.Cleanup | void> | TuiPluginNs.Cleanup | void;
|
|
39
39
|
};
|
|
40
40
|
export default _default;
|
package/dist/tui.js
CHANGED
|
@@ -8,7 +8,6 @@ import { createComponent as _$createComponent } from "@opentui/solid";
|
|
|
8
8
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
9
9
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
10
10
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
11
|
-
import { Plugin } from "@opencode/plugin/tui";
|
|
12
11
|
import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
|
13
12
|
|
|
14
13
|
// src/cli.ts
|
|
@@ -389,6 +388,22 @@ function canonicalizeModelId(model) {
|
|
|
389
388
|
return findKnown(stripDate(model)) ?? model;
|
|
390
389
|
}
|
|
391
390
|
|
|
391
|
+
// src/sidebar/windows.ts
|
|
392
|
+
var FIVE_HOUR_SECS = 5 * 3600;
|
|
393
|
+
var WEEKLY_SECS = 7 * 86400;
|
|
394
|
+
function elapsedPct(resetAtMs, durSecs, nowSecs) {
|
|
395
|
+
if (typeof resetAtMs !== "number" || !Number.isFinite(resetAtMs))
|
|
396
|
+
return;
|
|
397
|
+
const resetSecs = Math.floor(resetAtMs / 1000);
|
|
398
|
+
if (resetSecs < durSecs)
|
|
399
|
+
return;
|
|
400
|
+
const start = resetSecs - durSecs;
|
|
401
|
+
if (nowSecs < start)
|
|
402
|
+
return;
|
|
403
|
+
const pct = (nowSecs - start) / durSecs * 100;
|
|
404
|
+
return Math.round(Math.min(100, Math.max(0, pct)));
|
|
405
|
+
}
|
|
406
|
+
|
|
392
407
|
// src/sidebar/rows.ts
|
|
393
408
|
var TIER_DISPLAY = {
|
|
394
409
|
opensource: "open source",
|
|
@@ -425,13 +440,15 @@ function until(epoch, now = Date.now()) {
|
|
|
425
440
|
const days = Math.floor(hours / 24);
|
|
426
441
|
return hours % 24 ? `${days}d ${hours % 24}h` : `${days}d`;
|
|
427
442
|
}
|
|
428
|
-
function windowRow(label, w, now) {
|
|
443
|
+
function windowRow(label, w, now, durSecs) {
|
|
429
444
|
if (!w || typeof w.cap !== "number" || w.cap <= 0)
|
|
430
445
|
return;
|
|
431
446
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
432
447
|
const pct = Math.round(used / w.cap * 100);
|
|
448
|
+
const elapsed = elapsedPct(w.resetAt, durSecs, Math.floor(now / 1000));
|
|
433
449
|
const reset = until(w.resetAt, now);
|
|
434
|
-
|
|
450
|
+
const parts = [`${money(used)} / ${money(w.cap)} (${pct}%)`, ...elapsed === undefined ? [] : [`${elapsed}% elapsed`], ...reset ? [`resets ${reset}`] : []];
|
|
451
|
+
return [label, parts.join(" \xB7 ")];
|
|
435
452
|
}
|
|
436
453
|
function usageRows(usage, now = Date.now()) {
|
|
437
454
|
if (!usage)
|
|
@@ -446,10 +463,10 @@ function usageRows(usage, now = Date.now()) {
|
|
|
446
463
|
const pct = usage.monthlyCap > 0 ? Math.round(used / usage.monthlyCap * 100) : 0;
|
|
447
464
|
rows.push(["Monthly", `${money(used)} / ${money(usage.monthlyCap)} (${pct}%)`]);
|
|
448
465
|
}
|
|
449
|
-
const five = windowRow("5-hour", usage.fiveHour, now);
|
|
466
|
+
const five = windowRow("5-hour", usage.fiveHour, now, FIVE_HOUR_SECS);
|
|
450
467
|
if (five)
|
|
451
468
|
rows.push(five);
|
|
452
|
-
const week = windowRow("Weekly", usage.weekly, now);
|
|
469
|
+
const week = windowRow("Weekly", usage.weekly, now, WEEKLY_SECS);
|
|
453
470
|
if (week)
|
|
454
471
|
rows.push(week);
|
|
455
472
|
if (typeof usage.requests === "number" || typeof usage.cost === "number") {
|
|
@@ -462,10 +479,14 @@ function usageRows(usage, now = Date.now()) {
|
|
|
462
479
|
}
|
|
463
480
|
return rows;
|
|
464
481
|
}
|
|
465
|
-
function modelRows(meta) {
|
|
482
|
+
function modelRows(meta, usage) {
|
|
466
483
|
if (!meta)
|
|
467
484
|
return [];
|
|
468
485
|
const rows = [["Model", meta.name]];
|
|
486
|
+
if (usage) {
|
|
487
|
+
const spent = usage.cost > 0 ? ` \xB7 ${money(usage.cost)}` : "";
|
|
488
|
+
rows.push(["Usage (this model)", `${count(usage.requests)} req${spent}`]);
|
|
489
|
+
}
|
|
469
490
|
if (meta.tier)
|
|
470
491
|
rows.push(["Tier", TIER_DISPLAY[meta.tier]]);
|
|
471
492
|
if (typeof meta.allowance === "number")
|
|
@@ -593,6 +614,72 @@ async function loadUsage() {
|
|
|
593
614
|
return parseUsageJson(await runFirst(CMDUSE, ["-1", "--json", "--plain"]));
|
|
594
615
|
}
|
|
595
616
|
|
|
617
|
+
// src/sidebar/usageDb.ts
|
|
618
|
+
import { Database } from "bun:sqlite";
|
|
619
|
+
import { homedir as homedir2 } from "os";
|
|
620
|
+
import { join as join2 } from "path";
|
|
621
|
+
function usageDbPath() {
|
|
622
|
+
if (process.env.OPENCODE_DB)
|
|
623
|
+
return process.env.OPENCODE_DB;
|
|
624
|
+
const base = process.env.XDG_DATA_HOME ?? join2(homedir2(), ".local", "share");
|
|
625
|
+
return join2(base, "opencode", "opencode.db");
|
|
626
|
+
}
|
|
627
|
+
function periodStart(periodEnd, now = Date.now()) {
|
|
628
|
+
const end = periodEnd ? Date.parse(`${periodEnd}T23:59:59`) : Number.NaN;
|
|
629
|
+
if (!Number.isFinite(end))
|
|
630
|
+
return now - 30 * 24 * 60 * 60 * 1000;
|
|
631
|
+
const start = new Date(end);
|
|
632
|
+
start.setMonth(start.getMonth() - 1);
|
|
633
|
+
return start.getTime();
|
|
634
|
+
}
|
|
635
|
+
function usage(value) {
|
|
636
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
637
|
+
}
|
|
638
|
+
function loadModelUsage(modelID, sinceMs, path = usageDbPath()) {
|
|
639
|
+
let db;
|
|
640
|
+
try {
|
|
641
|
+
db = new Database(path, {
|
|
642
|
+
readonly: true
|
|
643
|
+
});
|
|
644
|
+
} catch {
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const rows = db.query(`SELECT data, time_created FROM message
|
|
649
|
+
WHERE data LIKE '%"tokens"%' AND time_created >= ?`).all(sinceMs);
|
|
650
|
+
const key = modelKey(modelID);
|
|
651
|
+
const seen = new Set;
|
|
652
|
+
let requests = 0;
|
|
653
|
+
let cost = 0;
|
|
654
|
+
for (const row of rows) {
|
|
655
|
+
let data;
|
|
656
|
+
try {
|
|
657
|
+
data = JSON.parse(row.data);
|
|
658
|
+
} catch {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (data.role !== "assistant" || !data.modelID || !data.tokens)
|
|
662
|
+
continue;
|
|
663
|
+
if (modelKey(data.modelID) !== key)
|
|
664
|
+
continue;
|
|
665
|
+
const id = `${data.modelID}@${row.time_created}`;
|
|
666
|
+
if (seen.has(id))
|
|
667
|
+
continue;
|
|
668
|
+
seen.add(id);
|
|
669
|
+
requests += 1;
|
|
670
|
+
cost += usage(data.cost);
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
requests,
|
|
674
|
+
cost
|
|
675
|
+
};
|
|
676
|
+
} catch {
|
|
677
|
+
return null;
|
|
678
|
+
} finally {
|
|
679
|
+
db.close();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
596
683
|
// src/tui.tsx
|
|
597
684
|
var ID = "command-code.tui";
|
|
598
685
|
var POLL_MS = 30000;
|
|
@@ -628,10 +715,14 @@ function Panel(props) {
|
|
|
628
715
|
function useRows(activeModelId, active) {
|
|
629
716
|
const [usage, setUsage] = createSignal([]);
|
|
630
717
|
const [meta, setMeta] = createSignal(new Map);
|
|
718
|
+
const [modelUsage, setModelUsage] = createSignal();
|
|
631
719
|
loadMeta().then(setMeta).catch(() => {});
|
|
632
720
|
const refresh = async () => {
|
|
633
721
|
try {
|
|
634
|
-
|
|
722
|
+
const snapshot = await loadUsage();
|
|
723
|
+
setUsage(usageRows(snapshot));
|
|
724
|
+
const id = activeModelId();
|
|
725
|
+
setModelUsage(id ? loadModelUsage(id, periodStart(snapshot.periodEnd)) ?? undefined : undefined);
|
|
635
726
|
} catch {}
|
|
636
727
|
};
|
|
637
728
|
createEffect(() => {
|
|
@@ -650,7 +741,7 @@ function useRows(activeModelId, active) {
|
|
|
650
741
|
...found,
|
|
651
742
|
tier: found.tier ?? (id ? tierFor(id) : undefined)
|
|
652
743
|
} : undefined;
|
|
653
|
-
return [...usage(), ...modelRows(model)];
|
|
744
|
+
return [...usage(), ...modelRows(model, modelUsage())];
|
|
654
745
|
});
|
|
655
746
|
}
|
|
656
747
|
var isOurs = (providerID) => Boolean(providerID?.startsWith("command-code"));
|
|
@@ -690,7 +781,7 @@ function PanelV2(props) {
|
|
|
690
781
|
muted: () => ctx.theme.text.subdued
|
|
691
782
|
});
|
|
692
783
|
}
|
|
693
|
-
var commandCodeTui =
|
|
784
|
+
var commandCodeTui = {
|
|
694
785
|
id: ID,
|
|
695
786
|
setup(ctx) {
|
|
696
787
|
ctx.ui.slot({
|
|
@@ -741,7 +832,7 @@ var commandCodeTui = Plugin.define({
|
|
|
741
832
|
}
|
|
742
833
|
});
|
|
743
834
|
}
|
|
744
|
-
}
|
|
835
|
+
};
|
|
745
836
|
var tui_default = {
|
|
746
837
|
...commandCodeTui,
|
|
747
838
|
tui
|
package/dist/v2.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plugin } from "@opencode/plugin";
|
|
1
|
+
import type { Plugin as PluginNs } from "@opencode/plugin";
|
|
2
2
|
import { type CmdModel } from "./models";
|
|
3
3
|
export declare const PLUGIN_ID = "command-code";
|
|
4
4
|
export declare const PROVIDER_BASE = "https://api.commandcode.ai/provider/v1";
|
|
@@ -15,9 +15,10 @@ type Lane = {
|
|
|
15
15
|
reasoningField?: string;
|
|
16
16
|
};
|
|
17
17
|
export type { Lane };
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
18
|
+
export declare const TOOL_DESCRIPTION = "Fetch live Command Code plan/usage: plan name, monthly credits, 5-hour & weekly windows, billing-period summary. Pass arg='plans' for the plan comparison table only, or extra cmduse flags (e.g. '--tz +05:30 daily').";
|
|
19
|
+
/** API/known model → v2 Model.Info. Capabilities, variants and $/1M cost all
|
|
20
|
+
* come from the generated catalog (the listing API publishes none of them);
|
|
21
|
+
* only the open lane needs the reasoning round-trip field. */
|
|
21
22
|
export declare function toV2Model(m: CmdModel, lane: Lane): unknown;
|
|
22
23
|
/** Instant static seed from gating.json's known model ids, split by wire lane.
|
|
23
24
|
* Replaced by the gated live list once the background fetch lands; also the
|
|
@@ -37,4 +38,10 @@ type CredentialContext = {
|
|
|
37
38
|
* back to CMD_API_KEY / ~/.commandcode/auth.json. Resolved per call — a
|
|
38
39
|
* /connect mid-session must be picked up. */
|
|
39
40
|
export declare function credentialKey(ctx: CredentialContext): Promise<string | undefined>;
|
|
40
|
-
|
|
41
|
+
/**
|
|
42
|
+
* opencode validates the plugin *by decoding* the default export against its
|
|
43
|
+
* own `Plugin` interface (identity `define` on both sides), so the package is
|
|
44
|
+
* only needed for types. Keeps the runtime dependency surface to opencode's
|
|
45
|
+
* own rewrite list (@opentui/*, solid-js).
|
|
46
|
+
*/
|
|
47
|
+
export declare const commandCodeV2: PluginNs.Plugin;
|
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.11",
|
|
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",
|
|
@@ -13,18 +13,20 @@
|
|
|
13
13
|
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target bun --external @opencode-ai/plugin --external @opencode/plugin --external @opencode/plugin/tui && bun scripts/build-tui.ts && tsc -p tsconfig.build.json",
|
|
14
14
|
"test": "bun test",
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
|
-
"build:tui": "bun scripts/build-tui.ts"
|
|
16
|
+
"build:tui": "bun scripts/build-tui.ts",
|
|
17
|
+
"extract:catalog": "bun scripts/extract-catalog.ts"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@opencode-ai/plugin": "*",
|
|
20
|
-
"@opencode/plugin": "^2.0.11",
|
|
21
20
|
"@opentui/core": "^0.5.4",
|
|
22
21
|
"@opentui/solid": "^0.5.4",
|
|
23
|
-
"solid-js": "1.9.12"
|
|
22
|
+
"solid-js": "1.9.12",
|
|
23
|
+
"zod": "4.1.8"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
|
27
|
-
"typescript": "^5"
|
|
27
|
+
"typescript": "^5",
|
|
28
|
+
"@opencode-ai/plugin": "*",
|
|
29
|
+
"@opencode/plugin": "^2.0.11"
|
|
28
30
|
},
|
|
29
31
|
"files": [
|
|
30
32
|
"dist",
|