@jeffreyjyz/opencode-command-code 0.5.19 → 0.5.20
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/README.md +84 -0
- package/dist/index.js +1 -1
- package/dist/sidebar/rows.d.ts +11 -0
- package/dist/sidebar/useRows.d.ts +15 -0
- package/dist/tui.js +20 -4
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @jeffreyjyz/opencode-command-code
|
|
2
|
+
|
|
3
|
+
Command Code providers, a live gated model list and a usage sidebar for opencode, with all usage rendering delegated to the `cmduse` CLI.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@jeffreyjyz/opencode-command-code) [](https://github.com/JeffreyJYZ/cmdcode-tools/actions/workflows/ci.yml) [](LICENSE)
|
|
6
|
+
|
|
7
|
+
## What it is
|
|
8
|
+
|
|
9
|
+
One package, both opencode plugin APIs: v1 hosts call the default export's `server()` hook map, v2 hosts decode `{ id, setup }` and call `setup(context)`. The two halves are independent implementations sharing one CLI layer, and neither reimplements usage math — every window figure comes from the `cmduse` binaries.
|
|
10
|
+
|
|
11
|
+
It registers two provider lanes against `api.commandcode.ai`:
|
|
12
|
+
|
|
13
|
+
- `command-code-anthropic` — Claude models on the Anthropic wire.
|
|
14
|
+
- `command-code-openai` — everything else on the OpenAI-compatible wire, where the reasoning text is round-tripped through `reasoning_content` so a compaction or model switch doesn't get rejected.
|
|
15
|
+
|
|
16
|
+
The model list is fetched live from Command Code's listing API, split by wire, and gated to your plan (`core/gating.json` in the repo is the source of truth). The picker never waits on the network: a static seed from the known-model table, then the last live list cached on disk, paint instantly and the live fetch merges over them — union by model id, so a gated or partial response can never make a model vanish. On v2 hosts the fetch then repeats on an interval, and a tick only touches the provider registry when the model ids actually changed.
|
|
17
|
+
|
|
18
|
+
Each model is registered with Command Code's published $/1M rates, so opencode's own cost display works for a subscription provider instead of showing $0. Vision support and reasoning-effort variants come from a generated catalog, because the listing API publishes no capabilities.
|
|
19
|
+
|
|
20
|
+
Credentials resolve in one order everywhere: `CMD_API_KEY`, then the key `/connect` stored in opencode's credential store, then `~/.commandcode/auth.json` from `cmd login`.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
Add the plugin to `opencode.json` (v2 hosts use `plugins`; v1's singular `plugin` is auto-normalized):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"plugins": ["@jeffreyjyz/opencode-command-code"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Usage rendering needs the `cmduse` CLI on `PATH` — it owns the plan table, the window math and the formatting:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
brew install JeffreyJYZ/tap/cmduse
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The sidebar's per-model catalog (tier, rates, allowance, deals) comes from `mpc`, which is optional: without it the section keeps the model name and the store-backed rows and simply omits the catalog rows.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
bun add -g @jeffreyjyz/mpc
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Run `/connect`, choose **Command Code** and paste an API key (create one at commandcode.ai/settings/keys), or use the `CMD_API_KEY` env method, or log in once with `cmd login`. With no credential at all the providers register as `auto`, so a later `/connect` lights them up without a restart.
|
|
47
|
+
|
|
48
|
+
One caveat on the v1 half: its `/connect` entry is bound to the Anthropic lane (opencode's plugin API allows one auth hook per provider id), so the OpenAI lane shares the key through the resolved `options.apiKey` — a `/connect`-only v1 setup needs `cmd login` or `CMD_API_KEY` for that lane.
|
|
49
|
+
|
|
50
|
+
While a session uses a Command Code, OpenCode Go/Zen or Volcengine ARK model, the sidebar grows one section for that family — plain text unless you ask for colour. Write `~/.config/opencode/command-code.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{ "colors": true, "ocPlan": "go-plus" }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`CMD_COLORS=1` (or `0`) and `CMD_OC_PLAN=go|go-plus` override it for a single run. `ocPlan` only matters on the Go side: the two Go plans share token prices but grant different per-model limits and nothing local records which one you have. The file is re-read every few seconds, so a flip lands on the next poll.
|
|
57
|
+
|
|
58
|
+
What the section shows:
|
|
59
|
+
|
|
60
|
+
- plan, price, monthly credits used, and the 5-hour / weekly windows with used / cap, percent used, elapsed share, reset countdown and a `cap in …` pace estimate; an over-cap window is flagged `LIMIT EXCEEDED`
|
|
61
|
+
- this period's requests and spend, the active model's tier, allowance and $/M rates, its `Usage (this model)` share, any running `Deal`, and this session's own totals
|
|
62
|
+
- on OpenCode Go/Zen and ARK sessions, the same shape from `ocuse` and `arkuse` instead — account meters where the provider publishes them, per-model spend from opencode's own store
|
|
63
|
+
|
|
64
|
+
Usage comes from `cmduse` polled every 5 seconds, read from opencode's message store for the per-model and per-session rows, and cached from `mpc` for the catalog. Every block remembers its last reading, so a remount, a session switch or a network outage repaints instead of blanking; the model figure is keyed by model and session, so switching models never shows the previous model's numbers.
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
Each command passes its arguments through to the CLI that owns it, and each has an agent-facing tool of the same name, so the model can fetch the same text without you typing a slash command.
|
|
69
|
+
|
|
70
|
+
| Command | Tool | Spawns |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `/cmd-usage` | `cmd_usage` | `cmduse` — plan, credits, windows, billing period; `plans` for the plan table |
|
|
73
|
+
| `/ocuse-usage` | `ocuse_usage` | `ocuse` — OpenCode Go/Zen meters and per-model spend; empty args mean `-1` |
|
|
74
|
+
| `/arkuse-usage` | `arkuse_usage` | `arkuse` — Volcengine ARK Agent Plan meters in AFP; empty args mean `-1` |
|
|
75
|
+
| — | `mpc_prices` | `mpc` — per-model price comparison across Command Code and OpenCode Go plans |
|
|
76
|
+
|
|
77
|
+
A bare `ocuse` or `arkuse` is a live watch loop that would never return to the dialog, so an empty argument means the one-shot dashboard.
|
|
78
|
+
|
|
79
|
+
## Links
|
|
80
|
+
|
|
81
|
+
- [Source (GitHub)](https://github.com/JeffreyJYZ/cmdcode-tools/tree/main/opencode-plugin)
|
|
82
|
+
- [npm](https://www.npmjs.com/package/@jeffreyjyz/opencode-command-code)
|
|
83
|
+
- [`cmduse` CLI](https://github.com/JeffreyJYZ/cmdcode-tools/tree/main/cmduse) — the usage renderer this plugin spawns
|
|
84
|
+
- [MIT license](LICENSE)
|
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.
|
|
2417
|
+
version: "0.5.20",
|
|
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",
|
package/dist/sidebar/rows.d.ts
CHANGED
|
@@ -30,6 +30,17 @@ export declare function pctTone(pct: number): Tone;
|
|
|
30
30
|
export declare function separator(): SidebarRow;
|
|
31
31
|
/** Truncate to `width`, ellipsis included. */
|
|
32
32
|
export declare function clip(text: string, width?: number): string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a usage-API reason is worth a row in the panel.
|
|
35
|
+
*
|
|
36
|
+
* Only a reason that names something the user can fix is: a missing or expired
|
|
37
|
+
* key, a missing CLI/profile, a rejected request. Everything else — a dropped
|
|
38
|
+
* connection, a DNS blip, a timeout — is transport, heals on the next poll, and
|
|
39
|
+
* used to make an `usage API: …` line pop in and out of the sidebar every few
|
|
40
|
+
* seconds. The pattern list is a whitelist on purpose: a new transport wording
|
|
41
|
+
* must stay quiet by default.
|
|
42
|
+
*/
|
|
43
|
+
export declare function actionableAccountError(reason: string | undefined): boolean;
|
|
33
44
|
/** Value budget for a labelled row: the panel draws `label: value`. */
|
|
34
45
|
export declare function valueWidth(label: string): number;
|
|
35
46
|
/** How the panel splits a row: a muted label (or banner) and a toned value. */
|
|
@@ -45,4 +45,19 @@ export declare function seededZen(): ZenUsage | undefined;
|
|
|
45
45
|
export declare function rememberArk(usage: ArkUsage): void;
|
|
46
46
|
/** The remembered ARK snapshot, or undefined on the first panel. */
|
|
47
47
|
export declare function seededArk(): ArkUsage | undefined;
|
|
48
|
+
/** The account half both panels share. */
|
|
49
|
+
export type Accounted = {
|
|
50
|
+
account?: unknown;
|
|
51
|
+
accountError?: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Keep the previous account meters when this poll only failed on the wire.
|
|
55
|
+
*
|
|
56
|
+
* ocuse/arkuse answer with `accountError` and no `account` for *any* failure, so
|
|
57
|
+
* one dropped request blinked the whole meter block to the local estimate and
|
|
58
|
+
* back 5s later. Only a reason the user can act on may drop the old numbers;
|
|
59
|
+
* a transport fault keeps them, unlabelled, because the next poll restores them
|
|
60
|
+
* anyway.
|
|
61
|
+
*/
|
|
62
|
+
export declare function keepAccount<T extends Accounted>(next: T, last: Accounted | undefined): T;
|
|
48
63
|
export declare function useRows(activeModelId: () => string | undefined, providerID: () => string | undefined, sessionID: () => string | undefined): import("solid-js").Accessor<SidebarRow[]>;
|
package/dist/tui.js
CHANGED
|
@@ -675,6 +675,11 @@ function separator() {
|
|
|
675
675
|
function clip(text, width = ROW_WIDTH) {
|
|
676
676
|
return text.length <= width ? text : `${text.slice(0, width - 1)}\u2026`;
|
|
677
677
|
}
|
|
678
|
+
function actionableAccountError(reason) {
|
|
679
|
+
if (!reason)
|
|
680
|
+
return false;
|
|
681
|
+
return /api_key_expired|no Go key|no agent-plan|not found on PATH|HTTP 40[13]|invalid.*key|unauthori[sz]ed/i.test(reason);
|
|
682
|
+
}
|
|
678
683
|
function valueWidth(label) {
|
|
679
684
|
return ROW_WIDTH - label.length - 2;
|
|
680
685
|
}
|
|
@@ -2270,7 +2275,7 @@ function arkRows(usage, fallbackName) {
|
|
|
2270
2275
|
return [];
|
|
2271
2276
|
const now = Date.now();
|
|
2272
2277
|
const rows = [["Plan", clip(`${usage.plan} \xB7 AFP plan${usage.profile ? ` \xB7 ${usage.profile}` : ""}`, valueWidth("Plan")), "base", true]];
|
|
2273
|
-
if (usage.accountError) {
|
|
2278
|
+
if (usage.accountError && actionableAccountError(usage.accountError)) {
|
|
2274
2279
|
rows.push(["", clip(`usage API: ${usage.accountError}`, valueWidth("")), "warn"]);
|
|
2275
2280
|
}
|
|
2276
2281
|
const period = monthPeriod(usage);
|
|
@@ -2419,7 +2424,7 @@ function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
|
|
|
2419
2424
|
const now = Date.now();
|
|
2420
2425
|
const oc = meta?.oc;
|
|
2421
2426
|
const rows = [["Plan", `${oc?.plan ?? "Go"} \xB7 limits per model`, "base"]];
|
|
2422
|
-
if (usage.accountError) {
|
|
2427
|
+
if (usage.accountError && actionableAccountError(usage.accountError)) {
|
|
2423
2428
|
rows.push(["", clip(`usage API: ${usage.accountError}`, valueWidth("")), "warn"]);
|
|
2424
2429
|
}
|
|
2425
2430
|
const mine = usage.models.get(modelKey(fallbackName ?? "")) ?? (meta ? usage.models.get(modelKey(meta.name)) : undefined);
|
|
@@ -2849,6 +2854,17 @@ function rememberArk(usage) {
|
|
|
2849
2854
|
function seededArk() {
|
|
2850
2855
|
return lastArk;
|
|
2851
2856
|
}
|
|
2857
|
+
function keepAccount(next, last) {
|
|
2858
|
+
if (next.account || !last?.account)
|
|
2859
|
+
return next;
|
|
2860
|
+
if (actionableAccountError(next.accountError))
|
|
2861
|
+
return next;
|
|
2862
|
+
return {
|
|
2863
|
+
...next,
|
|
2864
|
+
account: last.account,
|
|
2865
|
+
accountError: undefined
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2852
2868
|
function useRows(activeModelId, providerID, sessionID) {
|
|
2853
2869
|
const [usage, setUsage] = createSignal(seededRows());
|
|
2854
2870
|
const [zen, setZen] = createSignal(seededZen());
|
|
@@ -2903,13 +2919,13 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2903
2919
|
setUsage(usageRows(snapshot));
|
|
2904
2920
|
refreshModelUsage(activeModelId());
|
|
2905
2921
|
} else if (kind === "go" || kind === "zen") {
|
|
2906
|
-
const snapshot = await loadZen(loadPrefs().ocPlan);
|
|
2922
|
+
const snapshot = keepAccount(await loadZen(loadPrefs().ocPlan), lastZen);
|
|
2907
2923
|
rememberZen(snapshot);
|
|
2908
2924
|
setZen(snapshot);
|
|
2909
2925
|
refreshModelUsage(activeModelId());
|
|
2910
2926
|
refreshModelWindows(activeModelId());
|
|
2911
2927
|
} else if (kind === "ark") {
|
|
2912
|
-
const snapshot = await loadArk();
|
|
2928
|
+
const snapshot = keepAccount(await loadArk(), lastArk);
|
|
2913
2929
|
rememberArk(snapshot);
|
|
2914
2930
|
setArk(snapshot);
|
|
2915
2931
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffreyjyz/opencode-command-code",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.20",
|
|
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",
|