@jeffreyjyz/opencode-command-code 0.5.3 → 0.5.4
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/api.d.ts +0 -10
- package/dist/cli.d.ts +19 -2
- package/dist/constants/providers.d.ts +2 -0
- package/dist/constants/sidebar.d.ts +3 -2
- package/dist/index.js +51 -23
- package/dist/sidebar/data.d.ts +4 -3
- package/dist/sidebar/rows.d.ts +6 -1
- package/dist/sidebar/usageDb.d.ts +3 -3
- package/dist/sidebar/zen.d.ts +21 -0
- package/dist/tui.js +116 -64
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export declare function authHeaders(key: string): Record<string, string>;
|
|
2
|
-
export declare function getJson(path: string, key: string, timeoutMs?: number): Promise<unknown>;
|
|
3
1
|
export type Whoami = {
|
|
4
2
|
success: boolean;
|
|
5
3
|
user?: {
|
|
@@ -30,13 +28,6 @@ export type Credits = {
|
|
|
30
28
|
weekly?: Window;
|
|
31
29
|
};
|
|
32
30
|
};
|
|
33
|
-
export type Summary = {
|
|
34
|
-
totalCount?: number;
|
|
35
|
-
totalCost?: number;
|
|
36
|
-
successRate?: number;
|
|
37
|
-
totalTokensIn?: number;
|
|
38
|
-
totalTokensOut?: number;
|
|
39
|
-
};
|
|
40
31
|
export type ProviderModelsResp = {
|
|
41
32
|
data: Array<{
|
|
42
33
|
id: string;
|
|
@@ -48,5 +39,4 @@ export type ProviderModelsResp = {
|
|
|
48
39
|
export declare function whoami(key: string): Promise<Whoami>;
|
|
49
40
|
export declare function subscriptions(key: string): Promise<SubData>;
|
|
50
41
|
export declare function credits(key: string): Promise<Credits>;
|
|
51
|
-
export declare function usageSummary(key: string): Promise<Summary>;
|
|
52
42
|
export declare function providerModels(key: string): Promise<ProviderModelsResp>;
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
/** Fallback lookup paths: the opencode background service's PATH often lacks
|
|
2
3
|
* the homebrew prefix, so the bare name alone is not enough. `CMDUSE_BIN` wins
|
|
3
4
|
* when set — the dev-build hook the sibling cmduse consumers (mpc) honour too. */
|
|
@@ -6,6 +7,9 @@ export declare function cmduseCandidates(env?: Record<string, string | undefined
|
|
|
6
7
|
* wins when set (the `bun link` dev hook oc-cmd-compare installs),
|
|
7
8
|
* then the bun/homebrew fallbacks. */
|
|
8
9
|
export declare function mpcCandidates(env?: Record<string, string | undefined>): string[];
|
|
10
|
+
/** Same lookup for ocuse, the OpenCode Go/Zen reader shipped in the cmd-usage
|
|
11
|
+
* crate: `OCUSE_BIN` wins when set, then the homebrew fallbacks. */
|
|
12
|
+
export declare function ocuseCandidates(env?: Record<string, string | undefined>): string[];
|
|
9
13
|
/** Shell-words split: whitespace-separated, with single/double quoting and
|
|
10
14
|
* backslash escapes ("--tz +05:30", arg='a b'). Unterminated quotes close at
|
|
11
15
|
* end of input. */
|
|
@@ -24,14 +28,27 @@ export type RunOptions = {
|
|
|
24
28
|
env?: Record<string, string>;
|
|
25
29
|
/** Overridable for tests. */
|
|
26
30
|
candidates?: readonly string[];
|
|
31
|
+
/** Extra spawn options. Excludes `stdio`: the callbacks below assume stdout
|
|
32
|
+
* and stderr are piped (the default here), so callers may only add fields
|
|
33
|
+
* that leave that contract intact — the sidebar passes `detached` (see
|
|
34
|
+
* constants/cli.ts; its own stdio equals the default). */
|
|
35
|
+
spawnOptions?: Omit<NonNullable<Parameters<typeof spawn>[2]>, "stdio">;
|
|
36
|
+
/** Kill the child after this many ms. The sidebar bounds each poll; the
|
|
37
|
+
* tool spawns leave it unset. */
|
|
38
|
+
timeoutMs?: number;
|
|
27
39
|
};
|
|
28
40
|
/** Run cmduse and return its plain-text stdout. Tries PATH, then common
|
|
29
41
|
* homebrew locations; every candidate failing with ENOENT yields a single
|
|
30
42
|
* install-hint error. */
|
|
31
|
-
export declare function runCmduse(arg: string, opts?: RunOptions): Promise<string>;
|
|
43
|
+
export declare function runCmduse(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
|
|
32
44
|
/** Run mpc and return its plain-text stdout (the per-model price
|
|
33
45
|
* comparison). Same contract as runCmduse: `MPC_BIN` wins, then the
|
|
34
46
|
* fallback paths; every candidate failing with ENOENT yields a
|
|
35
47
|
* single install-hint error, and any other failure (a non-zero exit
|
|
36
48
|
* included) surfaces immediately with mpc's last stderr lines. */
|
|
37
|
-
export declare function runMpc(arg: string, opts?: RunOptions): Promise<string>;
|
|
49
|
+
export declare function runMpc(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
|
|
50
|
+
/** Run ocuse and return its plain-text stdout (OpenCode Go/Zen usage). Same
|
|
51
|
+
* contract as runCmduse; ocuse ships in the cmd-usage crate, so its install
|
|
52
|
+
* hint is the same brew formula. A string arg is shell-split, an array used
|
|
53
|
+
* as-is. */
|
|
54
|
+
export declare function runOcuse(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
|
|
@@ -13,6 +13,8 @@ export declare const ZEN_PROVIDER_ID = "opencode";
|
|
|
13
13
|
export declare const ZEN_PROVIDER_PREFIX = "opencode-zen";
|
|
14
14
|
/** TUI plugin id (stable contract; test/tui.test.ts pins it). */
|
|
15
15
|
export declare const TUI_ID = "command-code.tui";
|
|
16
|
+
/** The v2 /cmd-usage slash command id (registered and bound in tui.tsx). */
|
|
17
|
+
export declare const CMD_USAGE_COMMAND_ID = "command-code.cmd-usage";
|
|
16
18
|
/** The cmd_usage tool description, shared by the v1 and v2 halves. */
|
|
17
19
|
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').";
|
|
18
20
|
/** The mpc_prices tool description, shared by the v1 and v2 halves. */
|
|
@@ -11,8 +11,9 @@ export declare const ROW_WIDTH = 37;
|
|
|
11
11
|
export declare const TIER_DISPLAY: Readonly<Record<Category, string>>;
|
|
12
12
|
/**
|
|
13
13
|
* OpenCode Go plans the panel can render: the same token rates, different
|
|
14
|
-
* per-model monthly limits. Which one a user is on is not discoverable
|
|
15
|
-
*
|
|
14
|
+
* per-model monthly limits. Which one a user is on is not discoverable even with
|
|
15
|
+
* the usage endpoint in hand — it answers percentages, not a plan id, and the
|
|
16
|
+
* store records no subscription — so it is a setting.
|
|
16
17
|
*/
|
|
17
18
|
export declare const OC_PLANS: readonly ["go", "go-plus"];
|
|
18
19
|
export type OcPlan = (typeof OC_PLANS)[number];
|
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.4",
|
|
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",
|
|
@@ -3230,6 +3230,11 @@ var MPC_BIN_CANDIDATES = [
|
|
|
3230
3230
|
join(homedir(), ".bun/bin/mpc"),
|
|
3231
3231
|
"/opt/homebrew/bin/mpc"
|
|
3232
3232
|
];
|
|
3233
|
+
var OCUSE_BIN_CANDIDATES = [
|
|
3234
|
+
"ocuse",
|
|
3235
|
+
"/opt/homebrew/bin/ocuse",
|
|
3236
|
+
"/usr/local/bin/ocuse"
|
|
3237
|
+
];
|
|
3233
3238
|
|
|
3234
3239
|
// src/constants/cli.ts
|
|
3235
3240
|
var DEFAULT_ARGS = ["-1", "--plain"];
|
|
@@ -3256,6 +3261,11 @@ function mpcCandidates(env = process.env) {
|
|
|
3256
3261
|
return candidates.filter((bin) => Boolean(bin));
|
|
3257
3262
|
}
|
|
3258
3263
|
var MPC_CANDIDATES = mpcCandidates();
|
|
3264
|
+
function ocuseCandidates(env = process.env) {
|
|
3265
|
+
const candidates = [env.OCUSE_BIN, ...OCUSE_BIN_CANDIDATES];
|
|
3266
|
+
return candidates.filter((bin) => Boolean(bin));
|
|
3267
|
+
}
|
|
3268
|
+
var OCUSE_CANDIDATES = ocuseCandidates();
|
|
3259
3269
|
function splitCliArgs(input) {
|
|
3260
3270
|
const out = [];
|
|
3261
3271
|
let cur = "";
|
|
@@ -3318,36 +3328,53 @@ function runOnce(bin, args, opts, name = "cmduse") {
|
|
|
3318
3328
|
return new Promise((resolve, reject) => {
|
|
3319
3329
|
const child = spawn(bin, args, {
|
|
3320
3330
|
env: { ...process.env, ...opts.env ?? {} },
|
|
3321
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3331
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3332
|
+
...opts.spawnOptions
|
|
3322
3333
|
});
|
|
3323
3334
|
const out = [];
|
|
3324
3335
|
const err = [];
|
|
3336
|
+
let settled = false;
|
|
3337
|
+
const timer = opts.timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
3338
|
+
if (settled)
|
|
3339
|
+
return;
|
|
3340
|
+
settled = true;
|
|
3341
|
+
child.kill();
|
|
3342
|
+
reject(new Error(`${name} ${args.join(" ")}: timed out after ${opts.timeoutMs}ms`));
|
|
3343
|
+
}, opts.timeoutMs);
|
|
3325
3344
|
const onAbort = () => child.kill("SIGTERM");
|
|
3326
3345
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3346
|
+
const settle = (fn) => {
|
|
3347
|
+
if (settled)
|
|
3348
|
+
return;
|
|
3349
|
+
settled = true;
|
|
3350
|
+
if (timer)
|
|
3351
|
+
clearTimeout(timer);
|
|
3352
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
3353
|
+
fn();
|
|
3354
|
+
};
|
|
3327
3355
|
child.stdout.on("data", (d) => out.push(d));
|
|
3328
3356
|
child.stderr.on("data", (d) => err.push(d));
|
|
3329
|
-
child.on("error", (e) =>
|
|
3330
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
3331
|
-
reject(e);
|
|
3332
|
-
});
|
|
3357
|
+
child.on("error", (e) => settle(() => reject(e)));
|
|
3333
3358
|
child.on("close", (code, signal) => {
|
|
3334
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
3335
3359
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
3336
3360
|
if (opts.signal?.aborted) {
|
|
3337
|
-
reject(new Error(`${name} invocation aborted`));
|
|
3361
|
+
settle(() => reject(new Error(`${name} invocation aborted`)));
|
|
3338
3362
|
} else if (code === 0) {
|
|
3339
|
-
resolve(stdout);
|
|
3363
|
+
settle(() => resolve(stdout));
|
|
3340
3364
|
} else {
|
|
3341
3365
|
const why = signal ? `killed by ${signal}` : `exit ${code}`;
|
|
3342
3366
|
const tail = stderrTail(Buffer.concat(err).toString("utf8"));
|
|
3343
|
-
reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
3344
|
-
${tail}` : ""}`));
|
|
3367
|
+
settle(() => reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
3368
|
+
${tail}` : ""}`)));
|
|
3345
3369
|
}
|
|
3346
3370
|
});
|
|
3347
3371
|
});
|
|
3348
3372
|
}
|
|
3373
|
+
function resolveArgs(arg, build) {
|
|
3374
|
+
return typeof arg === "string" ? build(arg) : [...arg];
|
|
3375
|
+
}
|
|
3349
3376
|
async function runCmduse(arg, opts = {}) {
|
|
3350
|
-
const args =
|
|
3377
|
+
const args = resolveArgs(arg, buildCmduseArgs);
|
|
3351
3378
|
const candidates = opts.candidates ?? CANDIDATES;
|
|
3352
3379
|
let last;
|
|
3353
3380
|
for (const bin of candidates) {
|
|
@@ -3362,7 +3389,7 @@ async function runCmduse(arg, opts = {}) {
|
|
|
3362
3389
|
throw new Error(`cmduse binary not found (install: brew install JeffreyJYZ/tap/cmduse)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, { cause: last });
|
|
3363
3390
|
}
|
|
3364
3391
|
async function runMpc(arg, opts = {}) {
|
|
3365
|
-
const args =
|
|
3392
|
+
const args = resolveArgs(arg, buildMpcArgs);
|
|
3366
3393
|
const candidates = opts.candidates ?? MPC_CANDIDATES;
|
|
3367
3394
|
let last;
|
|
3368
3395
|
for (const bin of candidates) {
|
|
@@ -5025,6 +5052,7 @@ var commandCodeV2 = {
|
|
|
5025
5052
|
|
|
5026
5053
|
// src/index.ts
|
|
5027
5054
|
var PROVIDER_NPM = `${package_default.name}@${package_default.version}`;
|
|
5055
|
+
var [ANTHROPIC, OPENAI] = LANES;
|
|
5028
5056
|
function tool(input) {
|
|
5029
5057
|
return input;
|
|
5030
5058
|
}
|
|
@@ -5071,7 +5099,7 @@ function toModelDefs(models, providerID, npm, interleaved = false) {
|
|
|
5071
5099
|
var CommandCodePlugin = async (_input) => {
|
|
5072
5100
|
return {
|
|
5073
5101
|
auth: {
|
|
5074
|
-
provider:
|
|
5102
|
+
provider: ANTHROPIC.id,
|
|
5075
5103
|
loader: async (getAuth) => {
|
|
5076
5104
|
const auth = await getAuth();
|
|
5077
5105
|
if (auth?.type !== "api") {
|
|
@@ -5101,7 +5129,7 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5101
5129
|
return {
|
|
5102
5130
|
type: "success",
|
|
5103
5131
|
key,
|
|
5104
|
-
provider:
|
|
5132
|
+
provider: ANTHROPIC.id
|
|
5105
5133
|
};
|
|
5106
5134
|
} catch {
|
|
5107
5135
|
return { type: "failed" };
|
|
@@ -5125,19 +5153,19 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5125
5153
|
console.warn("[command-code] model list unavailable:", e);
|
|
5126
5154
|
}
|
|
5127
5155
|
}
|
|
5128
|
-
const claudeDefs = split ? toModelDefs(split.claude,
|
|
5129
|
-
const openDefs = split ? toModelDefs(split.open,
|
|
5156
|
+
const claudeDefs = split ? toModelDefs(split.claude, ANTHROPIC.id, PROVIDER_NPM) : {};
|
|
5157
|
+
const openDefs = split ? toModelDefs(split.open, OPENAI.id, PROVIDER_NPM, {
|
|
5130
5158
|
field: "reasoning_content"
|
|
5131
5159
|
}) : {};
|
|
5132
|
-
const userAnthropic = existing(
|
|
5133
|
-
cfg.provider[
|
|
5160
|
+
const userAnthropic = existing(ANTHROPIC.id);
|
|
5161
|
+
cfg.provider[ANTHROPIC.id] = {
|
|
5134
5162
|
npm: userAnthropic.npm ?? PROVIDER_NPM,
|
|
5135
5163
|
name: userAnthropic.name ?? "Command Code (Anthropic)",
|
|
5136
5164
|
options: { baseURL: PROVIDER_BASE, ...userAnthropic.options },
|
|
5137
5165
|
models: { ...claudeDefs, ...userAnthropic.models }
|
|
5138
5166
|
};
|
|
5139
|
-
const userOpenai = existing(
|
|
5140
|
-
cfg.provider[
|
|
5167
|
+
const userOpenai = existing(OPENAI.id);
|
|
5168
|
+
cfg.provider[OPENAI.id] = {
|
|
5141
5169
|
npm: userOpenai.npm ?? PROVIDER_NPM,
|
|
5142
5170
|
name: userOpenai.name ?? "Command Code (OpenAI)",
|
|
5143
5171
|
options: {
|
|
@@ -5154,14 +5182,14 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5154
5182
|
};
|
|
5155
5183
|
},
|
|
5156
5184
|
provider: {
|
|
5157
|
-
id:
|
|
5185
|
+
id: ANTHROPIC.id,
|
|
5158
5186
|
models: async (_provider, ctx) => {
|
|
5159
5187
|
const key = await resolveKey(async () => {
|
|
5160
5188
|
const a = ctx.auth;
|
|
5161
5189
|
return a?.type === "api" && a.key ? { key: a.key } : undefined;
|
|
5162
5190
|
});
|
|
5163
5191
|
const split = await loadModels(key);
|
|
5164
|
-
return toModelDefs(split.claude,
|
|
5192
|
+
return toModelDefs(split.claude, ANTHROPIC.id, PROVIDER_NPM);
|
|
5165
5193
|
}
|
|
5166
5194
|
},
|
|
5167
5195
|
tool: {
|
package/dist/sidebar/data.d.ts
CHANGED
|
@@ -33,8 +33,9 @@ export declare function loadMeta(plan?: OcPlan): Promise<Map<string, ModelMeta>>
|
|
|
33
33
|
* unreachable (see `requireSnapshot`). */
|
|
34
34
|
export declare function loadUsage(): Promise<Usage>;
|
|
35
35
|
/**
|
|
36
|
-
* One OpenCode Go / Zen snapshot from ocuse
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* One OpenCode Go / Zen snapshot from ocuse: opencode's own store for the
|
|
37
|
+
* per-model totals, plus OpenCode Go's usage endpoint for the account meters
|
|
38
|
+
* when the account has a Go key. Throws when ocuse is missing or fails, and the
|
|
39
|
+
* caller then omits the panel.
|
|
39
40
|
*/
|
|
40
41
|
export declare function loadZen(plan?: OcPlan): Promise<ZenUsage>;
|
package/dist/sidebar/rows.d.ts
CHANGED
|
@@ -140,7 +140,12 @@ export declare function until(epoch: number | undefined, now?: number): string;
|
|
|
140
140
|
* the OpenCode Go/Zen side adds the window's request count as `extra`, so
|
|
141
141
|
* `LIMIT EXCEEDED`, the tone, `cap in …`, `elapsed` and `resets` all have one
|
|
142
142
|
* implementation. */
|
|
143
|
-
export declare function windowRows(label: string, w: WindowUsage | undefined, now: number, durSecs: number, extra?: string
|
|
143
|
+
export declare function windowRows(label: string, w: WindowUsage | undefined, now: number, durSecs: number, extra?: string,
|
|
144
|
+
/** How the use line reads. The default is money (`$0.55/$14 (3.9%)`); the
|
|
145
|
+
* account's own meters publish shares rather than dollars and pass a percent
|
|
146
|
+
* formatter, so the tone, `LIMIT EXCEEDED`, `cap in …`, `elapsed` and
|
|
147
|
+
* `resets` stay this one implementation for both. */
|
|
148
|
+
use?: (used: number, cap: number, pct: number) => string): SidebarRow[];
|
|
144
149
|
/**
|
|
145
150
|
* The two store-backed rows for the active model — its requests under `Usage
|
|
146
151
|
* (this model)`, then this conversation's own totals under `Session`. They need
|
|
@@ -27,9 +27,9 @@ export declare function loadModelUsage(modelID: string, sinceMs: number, path?:
|
|
|
27
27
|
/**
|
|
28
28
|
* The oldest in-window assistant turn for one model, as epoch ms — the start of
|
|
29
29
|
* the rolling window the Go/Zen panel needs. A rolling 5-hour/weekly window has
|
|
30
|
-
* no
|
|
31
|
-
* real start: the oldest request still inside it ages out
|
|
32
|
-
* which is that window's honest reset. Null when the model has no turn in the
|
|
30
|
+
* no reset recorded locally (the store carries none for these providers), but
|
|
31
|
+
* its clock does have a real start: the oldest request still inside it ages out
|
|
32
|
+
* at `start + window`, which is that window's honest reset. Null when the model has no turn in the
|
|
33
33
|
* window, or the store is missing/unreadable, so callers then omit the
|
|
34
34
|
* continuation exactly as before.
|
|
35
35
|
*
|
package/dist/sidebar/zen.d.ts
CHANGED
|
@@ -13,6 +13,25 @@ export interface ZenModel {
|
|
|
13
13
|
weekly: ZenTotals;
|
|
14
14
|
month: ZenTotals;
|
|
15
15
|
}
|
|
16
|
+
/** One of the account's own meters (`ocuse`'s `account` block). */
|
|
17
|
+
export interface ZenMeter {
|
|
18
|
+
/** The server's own word; anything but "ok" reads as over the limit. */
|
|
19
|
+
status: string;
|
|
20
|
+
/** Percent of the window's limit already used — the console's number. */
|
|
21
|
+
percent: number;
|
|
22
|
+
/** Reset instant, epoch ms. */
|
|
23
|
+
resetsAt?: number;
|
|
24
|
+
}
|
|
25
|
+
/** The account's three meters, under the endpoint's own names: `rolling` is the
|
|
26
|
+
* 5-hour window. Only present when ocuse reached OpenCode Go's usage endpoint,
|
|
27
|
+
* and then these — not the per-model figures — are what opencode.ai/console
|
|
28
|
+
* shows, because only the server knows its own window anchors and price
|
|
29
|
+
* basis. */
|
|
30
|
+
export interface ZenAccount {
|
|
31
|
+
rolling: ZenMeter;
|
|
32
|
+
weekly: ZenMeter;
|
|
33
|
+
monthly: ZenMeter;
|
|
34
|
+
}
|
|
16
35
|
export interface ZenUsage {
|
|
17
36
|
/**
|
|
18
37
|
* Billing period start, epoch ms: `periodStart` on current ocuse, or
|
|
@@ -29,6 +48,8 @@ export interface ZenUsage {
|
|
|
29
48
|
weekly: ZenTotals;
|
|
30
49
|
month: ZenTotals;
|
|
31
50
|
};
|
|
51
|
+
/** The account's own meters, when ocuse could ask for them. */
|
|
52
|
+
account?: ZenAccount;
|
|
32
53
|
/** Keyed by canonical model key, the same normalisation the CC side matches on. */
|
|
33
54
|
models: Map<string, ZenModel>;
|
|
34
55
|
}
|
package/dist/tui.js
CHANGED
|
@@ -14,6 +14,7 @@ var OCUSE_BIN_CANDIDATES = ["ocuse", "/opt/homebrew/bin/ocuse", "/usr/local/bin/
|
|
|
14
14
|
|
|
15
15
|
// src/constants/cli.ts
|
|
16
16
|
var DEFAULT_ARGS = ["-1", "--plain"];
|
|
17
|
+
var MPC_DEFAULT_ARGS = ["--minimal"];
|
|
17
18
|
var SUBCOMMANDS = new Set(["plans", "models", "daily", "hourly", "model", "session", "statusline", "config"]);
|
|
18
19
|
var SPAWN_OPTIONS = {
|
|
19
20
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -31,6 +32,11 @@ function mpcCandidates(env = process.env) {
|
|
|
31
32
|
return candidates.filter((bin) => Boolean(bin));
|
|
32
33
|
}
|
|
33
34
|
var MPC_CANDIDATES = mpcCandidates();
|
|
35
|
+
function ocuseCandidates(env = process.env) {
|
|
36
|
+
const candidates = [env.OCUSE_BIN, ...OCUSE_BIN_CANDIDATES];
|
|
37
|
+
return candidates.filter((bin) => Boolean(bin));
|
|
38
|
+
}
|
|
39
|
+
var OCUSE_CANDIDATES = ocuseCandidates();
|
|
34
40
|
function splitCliArgs(input) {
|
|
35
41
|
const out = [];
|
|
36
42
|
let cur = "";
|
|
@@ -77,6 +83,12 @@ function buildCmduseArgs(arg) {
|
|
|
77
83
|
return tokens;
|
|
78
84
|
return [...DEFAULT_ARGS, ...tokens];
|
|
79
85
|
}
|
|
86
|
+
function buildMpcArgs(arg) {
|
|
87
|
+
const tokens = splitCliArgs(arg);
|
|
88
|
+
if (!tokens.length)
|
|
89
|
+
return [...MPC_DEFAULT_ARGS];
|
|
90
|
+
return tokens;
|
|
91
|
+
}
|
|
80
92
|
function stderrTail(text) {
|
|
81
93
|
const lines = text.trimEnd().split(`
|
|
82
94
|
`);
|
|
@@ -90,38 +102,55 @@ function runOnce(bin, args, opts, name = "cmduse") {
|
|
|
90
102
|
...process.env,
|
|
91
103
|
...opts.env ?? {}
|
|
92
104
|
},
|
|
93
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
105
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
106
|
+
...opts.spawnOptions
|
|
94
107
|
});
|
|
95
108
|
const out = [];
|
|
96
109
|
const err = [];
|
|
110
|
+
let settled = false;
|
|
111
|
+
const timer = opts.timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
112
|
+
if (settled)
|
|
113
|
+
return;
|
|
114
|
+
settled = true;
|
|
115
|
+
child.kill();
|
|
116
|
+
reject(new Error(`${name} ${args.join(" ")}: timed out after ${opts.timeoutMs}ms`));
|
|
117
|
+
}, opts.timeoutMs);
|
|
97
118
|
const onAbort = () => child.kill("SIGTERM");
|
|
98
119
|
opts.signal?.addEventListener("abort", onAbort, {
|
|
99
120
|
once: true
|
|
100
121
|
});
|
|
122
|
+
const settle = (fn) => {
|
|
123
|
+
if (settled)
|
|
124
|
+
return;
|
|
125
|
+
settled = true;
|
|
126
|
+
if (timer)
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
129
|
+
fn();
|
|
130
|
+
};
|
|
101
131
|
child.stdout.on("data", (d) => out.push(d));
|
|
102
132
|
child.stderr.on("data", (d) => err.push(d));
|
|
103
|
-
child.on("error", (e) =>
|
|
104
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
105
|
-
reject(e);
|
|
106
|
-
});
|
|
133
|
+
child.on("error", (e) => settle(() => reject(e)));
|
|
107
134
|
child.on("close", (code, signal) => {
|
|
108
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
109
135
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
110
136
|
if (opts.signal?.aborted) {
|
|
111
|
-
reject(new Error(`${name} invocation aborted`));
|
|
137
|
+
settle(() => reject(new Error(`${name} invocation aborted`)));
|
|
112
138
|
} else if (code === 0) {
|
|
113
|
-
resolve(stdout);
|
|
139
|
+
settle(() => resolve(stdout));
|
|
114
140
|
} else {
|
|
115
141
|
const why = signal ? `killed by ${signal}` : `exit ${code}`;
|
|
116
142
|
const tail = stderrTail(Buffer.concat(err).toString("utf8"));
|
|
117
|
-
reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
118
|
-
${tail}` : ""}`));
|
|
143
|
+
settle(() => reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
144
|
+
${tail}` : ""}`)));
|
|
119
145
|
}
|
|
120
146
|
});
|
|
121
147
|
});
|
|
122
148
|
}
|
|
149
|
+
function resolveArgs(arg, build) {
|
|
150
|
+
return typeof arg === "string" ? build(arg) : [...arg];
|
|
151
|
+
}
|
|
123
152
|
async function runCmduse(arg, opts = {}) {
|
|
124
|
-
const args =
|
|
153
|
+
const args = resolveArgs(arg, buildCmduseArgs);
|
|
125
154
|
const candidates = opts.candidates ?? CANDIDATES;
|
|
126
155
|
let last;
|
|
127
156
|
for (const bin of candidates) {
|
|
@@ -137,6 +166,40 @@ async function runCmduse(arg, opts = {}) {
|
|
|
137
166
|
cause: last
|
|
138
167
|
});
|
|
139
168
|
}
|
|
169
|
+
async function runMpc(arg, opts = {}) {
|
|
170
|
+
const args = resolveArgs(arg, buildMpcArgs);
|
|
171
|
+
const candidates = opts.candidates ?? MPC_CANDIDATES;
|
|
172
|
+
let last;
|
|
173
|
+
for (const bin of candidates) {
|
|
174
|
+
try {
|
|
175
|
+
return await runOnce(bin, args, opts, "mpc");
|
|
176
|
+
} catch (e) {
|
|
177
|
+
last = e;
|
|
178
|
+
if (e?.code !== "ENOENT")
|
|
179
|
+
throw e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
throw new Error(`mpc binary not found (install: bun add -g @jeffreyjyz/mpc)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, {
|
|
183
|
+
cause: last
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function runOcuse(arg, opts = {}) {
|
|
187
|
+
const args = resolveArgs(arg, splitCliArgs);
|
|
188
|
+
const candidates = opts.candidates ?? OCUSE_CANDIDATES;
|
|
189
|
+
let last;
|
|
190
|
+
for (const bin of candidates) {
|
|
191
|
+
try {
|
|
192
|
+
return await runOnce(bin, args, opts, "ocuse");
|
|
193
|
+
} catch (e) {
|
|
194
|
+
last = e;
|
|
195
|
+
if (e?.code !== "ENOENT")
|
|
196
|
+
throw e;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw new Error(`ocuse binary not found (install: brew install JeffreyJYZ/tap/cmduse)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, {
|
|
200
|
+
cause: last
|
|
201
|
+
});
|
|
202
|
+
}
|
|
140
203
|
|
|
141
204
|
// src/constants/providers.ts
|
|
142
205
|
var PROVIDER_ID_PREFIX = "command-code";
|
|
@@ -144,6 +207,7 @@ var GO_PROVIDER_ID = "opencode-go";
|
|
|
144
207
|
var ZEN_PROVIDER_ID = "opencode";
|
|
145
208
|
var ZEN_PROVIDER_PREFIX = "opencode-zen";
|
|
146
209
|
var TUI_ID = "command-code.tui";
|
|
210
|
+
var CMD_USAGE_COMMAND_ID = "command-code.cmd-usage";
|
|
147
211
|
|
|
148
212
|
// src/sidebar/panel.tsx
|
|
149
213
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
@@ -621,7 +685,7 @@ function until(epoch, now = Date.now()) {
|
|
|
621
685
|
const days = Math.floor(hours / 24);
|
|
622
686
|
return hours % 24 ? `${days}d ${hours % 24}h` : `${days}d`;
|
|
623
687
|
}
|
|
624
|
-
function windowRows(label, w, now, durSecs, extra) {
|
|
688
|
+
function windowRows(label, w, now, durSecs, extra, use) {
|
|
625
689
|
if (!w || typeof w.cap !== "number" || w.cap <= 0)
|
|
626
690
|
return [];
|
|
627
691
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
@@ -632,7 +696,7 @@ function windowRows(label, w, now, durSecs, extra) {
|
|
|
632
696
|
const reset = until(w.resetAt, now);
|
|
633
697
|
const lead = w.exceeded ? "LIMIT EXCEEDED" : eta === undefined ? elapsed === undefined ? "" : `${elapsed} elapsed` : `cap in ${until(now + eta * 1000, now)}`;
|
|
634
698
|
const detail = [lead, reset ? `resets ${reset}` : ""].filter(Boolean).join(" \xB7 ");
|
|
635
|
-
return [[label, `${money(used)}/${money(w.cap)} (${pct.toFixed(1)}%)`, w.exceeded ? "crit" : pctTone(pct)], ...detail ? [["", detail, "muted"]] : [], ...extra ? [["", extra, "muted"]] : []];
|
|
699
|
+
return [[label, use ? use(used, w.cap, pct) : `${money(used)}/${money(w.cap)} (${pct.toFixed(1)}%)`, w.exceeded ? "crit" : pctTone(pct)], ...detail ? [["", detail, "muted"]] : [], ...extra ? [["", extra, "muted"]] : []];
|
|
636
700
|
}
|
|
637
701
|
function localUsageRows(usage, session) {
|
|
638
702
|
const rows = [];
|
|
@@ -2027,7 +2091,6 @@ function minPlan(id) {
|
|
|
2027
2091
|
}
|
|
2028
2092
|
|
|
2029
2093
|
// src/sidebar/data.ts
|
|
2030
|
-
import { spawn as spawn2 } from "child_process";
|
|
2031
2094
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2032
2095
|
import { homedir as homedir3 } from "os";
|
|
2033
2096
|
import { dirname, join as join3 } from "path";
|
|
@@ -2042,6 +2105,14 @@ function totals(value) {
|
|
|
2042
2105
|
tokens: num("tokens")
|
|
2043
2106
|
};
|
|
2044
2107
|
}
|
|
2108
|
+
function meter(value) {
|
|
2109
|
+
const v = value ?? {};
|
|
2110
|
+
return {
|
|
2111
|
+
status: typeof v.status === "string" ? v.status : "ok",
|
|
2112
|
+
percent: typeof v.percent === "number" ? v.percent : 0,
|
|
2113
|
+
resetsAt: typeof v.resetsAt === "number" ? v.resetsAt : undefined
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2045
2116
|
function parseZenJson(text) {
|
|
2046
2117
|
let body;
|
|
2047
2118
|
try {
|
|
@@ -2067,6 +2138,11 @@ function parseZenJson(text) {
|
|
|
2067
2138
|
periodStart: typeof body.periodStartAt === "number" ? body.periodStartAt : typeof body.periodStart === "number" ? body.periodStart : undefined,
|
|
2068
2139
|
periodEndAt: typeof body.periodEndAt === "number" ? body.periodEndAt : undefined,
|
|
2069
2140
|
source: typeof body.source === "string" ? body.source : undefined,
|
|
2141
|
+
account: body.account ? {
|
|
2142
|
+
rolling: meter(body.account.rolling),
|
|
2143
|
+
weekly: meter(body.account.weekly),
|
|
2144
|
+
monthly: meter(body.account.monthly)
|
|
2145
|
+
} : undefined,
|
|
2070
2146
|
totals: {
|
|
2071
2147
|
fiveHour: totals(top.fiveHour),
|
|
2072
2148
|
weekly: totals(top.weekly),
|
|
@@ -2107,6 +2183,14 @@ function zenWindow(label, spent, limit, share, now, durSecs, resetAt) {
|
|
|
2107
2183
|
exceeded: spent.cost >= cap
|
|
2108
2184
|
}, now, durSecs, spent.requests > 0 ? `${count(spent.requests)} req` : undefined);
|
|
2109
2185
|
}
|
|
2186
|
+
function zenMeterRows(label, meter, now, durSecs) {
|
|
2187
|
+
return windowRows(label, {
|
|
2188
|
+
cap: 100,
|
|
2189
|
+
used: meter.percent,
|
|
2190
|
+
resetAt: meter.resetsAt,
|
|
2191
|
+
exceeded: meter.percent >= 100 || meter.status !== "ok"
|
|
2192
|
+
}, now, durSecs, undefined, (_used, _cap, pct) => `${pct.toFixed(1)}% of limit`);
|
|
2193
|
+
}
|
|
2110
2194
|
function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
|
|
2111
2195
|
if (!usage)
|
|
2112
2196
|
return [];
|
|
@@ -2116,10 +2200,16 @@ function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
|
|
|
2116
2200
|
const mine = usage.models.get(modelKey(fallbackName ?? "")) ?? (meta ? usage.models.get(modelKey(meta.name)) : undefined);
|
|
2117
2201
|
const limit = oc?.allowance ?? mine?.limit;
|
|
2118
2202
|
const cap = mine ? limit : undefined;
|
|
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)));
|
|
2121
2203
|
const period = monthPeriod(usage);
|
|
2122
|
-
|
|
2204
|
+
if (usage.account) {
|
|
2205
|
+
rows.push(...zenMeterRows("5-hour", usage.account.rolling, now, FIVE_HOUR_SECS));
|
|
2206
|
+
rows.push(...zenMeterRows("Weekly", usage.account.weekly, now, WEEKLY_SECS));
|
|
2207
|
+
rows.push(...zenMeterRows("Monthly", usage.account.monthly, now, period?.durSecs ?? 0));
|
|
2208
|
+
} else {
|
|
2209
|
+
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)));
|
|
2210
|
+
rows.push(...zenWindow("Weekly", mine?.weekly ?? usage.totals.weekly, cap, GO_WINDOW_SHARE.weekly, now, WEEKLY_SECS, rollingReset(windows?.weekly, WEEKLY_SECS)));
|
|
2211
|
+
rows.push(...zenWindow("Monthly", mine?.month ?? usage.totals.month, cap, GO_WINDOW_SHARE.month, now, period?.durSecs ?? 0, period?.resetAt));
|
|
2212
|
+
}
|
|
2123
2213
|
rows.push(["Period", `${count(usage.totals.month.requests)} requests \xB7 ${money(usage.totals.month.cost)}`, "base"]);
|
|
2124
2214
|
rows.push(separator());
|
|
2125
2215
|
const name = meta?.name ?? fallbackName;
|
|
@@ -2149,48 +2239,10 @@ function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
|
|
|
2149
2239
|
}
|
|
2150
2240
|
|
|
2151
2241
|
// src/sidebar/data.ts
|
|
2152
|
-
var
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
return new Promise((resolve, reject) => {
|
|
2157
|
-
const child = spawn2(bin, args, SPAWN_OPTIONS);
|
|
2158
|
-
const out = [];
|
|
2159
|
-
const err = [];
|
|
2160
|
-
let settled = false;
|
|
2161
|
-
const timer = setTimeout(() => {
|
|
2162
|
-
if (settled)
|
|
2163
|
-
return;
|
|
2164
|
-
settled = true;
|
|
2165
|
-
child.kill();
|
|
2166
|
-
reject(new Error(`${bin} ${args.join(" ")}: timed out after ${CHILD_TIMEOUT_MS}ms`));
|
|
2167
|
-
}, CHILD_TIMEOUT_MS);
|
|
2168
|
-
const done = (fn) => {
|
|
2169
|
-
if (settled)
|
|
2170
|
-
return;
|
|
2171
|
-
settled = true;
|
|
2172
|
-
clearTimeout(timer);
|
|
2173
|
-
fn();
|
|
2174
|
-
};
|
|
2175
|
-
child.stdout?.on("data", (d) => out.push(d));
|
|
2176
|
-
child.stderr?.on("data", (d) => err.push(d));
|
|
2177
|
-
child.on("error", (e) => done(() => reject(e)));
|
|
2178
|
-
child.on("close", (code) => done(() => code === 0 ? resolve(Buffer.concat(out).toString("utf8")) : reject(new Error(`${bin} ${args.join(" ")}: exit ${code} ${Buffer.concat(err).toString("utf8").slice(-200)}`))));
|
|
2179
|
-
});
|
|
2180
|
-
}
|
|
2181
|
-
async function runFirst(candidates, args) {
|
|
2182
|
-
let last;
|
|
2183
|
-
for (const bin of candidates) {
|
|
2184
|
-
try {
|
|
2185
|
-
return await run(bin, args);
|
|
2186
|
-
} catch (e) {
|
|
2187
|
-
last = e;
|
|
2188
|
-
if (e?.code !== "ENOENT")
|
|
2189
|
-
throw e;
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
throw last instanceof Error ? last : new Error(`${candidates[0]} not found`);
|
|
2193
|
-
}
|
|
2242
|
+
var SIDEBAR_OPTS = {
|
|
2243
|
+
spawnOptions: SPAWN_OPTIONS,
|
|
2244
|
+
timeoutMs: CHILD_TIMEOUT_MS
|
|
2245
|
+
};
|
|
2194
2246
|
function parseUsageJson(text) {
|
|
2195
2247
|
const raw = JSON.parse(text);
|
|
2196
2248
|
const win = (v) => {
|
|
@@ -2276,7 +2328,7 @@ async function loadMeta(plan = OC_PLAN_DEFAULT) {
|
|
|
2276
2328
|
return new Map(cached.rows ?? []);
|
|
2277
2329
|
}
|
|
2278
2330
|
} catch {}
|
|
2279
|
-
const meta = parseMpcJson(await
|
|
2331
|
+
const meta = parseMpcJson(await runMpc(metaArgs(plan), SIDEBAR_OPTS));
|
|
2280
2332
|
try {
|
|
2281
2333
|
const path = cachePath(plan);
|
|
2282
2334
|
await mkdir(dirname(path), {
|
|
@@ -2290,10 +2342,10 @@ async function loadMeta(plan = OC_PLAN_DEFAULT) {
|
|
|
2290
2342
|
return meta;
|
|
2291
2343
|
}
|
|
2292
2344
|
async function loadUsage() {
|
|
2293
|
-
return requireSnapshot(parseUsageJson(await
|
|
2345
|
+
return requireSnapshot(parseUsageJson(await runCmduse(["-1", "--json", "--plain"], SIDEBAR_OPTS)));
|
|
2294
2346
|
}
|
|
2295
2347
|
async function loadZen(plan = OC_PLAN_DEFAULT) {
|
|
2296
|
-
return parseZenJson(await
|
|
2348
|
+
return parseZenJson(await runOcuse(zenArgs(plan), SIDEBAR_OPTS));
|
|
2297
2349
|
}
|
|
2298
2350
|
|
|
2299
2351
|
// src/sidebar/usageDb.ts
|
|
@@ -2697,7 +2749,7 @@ var commandCodeTui = {
|
|
|
2697
2749
|
mode: "global",
|
|
2698
2750
|
priority: 10,
|
|
2699
2751
|
commands: [{
|
|
2700
|
-
id:
|
|
2752
|
+
id: CMD_USAGE_COMMAND_ID,
|
|
2701
2753
|
title: "Command Code usage",
|
|
2702
2754
|
group: "Command Code",
|
|
2703
2755
|
slash: {
|
|
@@ -2724,7 +2776,7 @@ var commandCodeTui = {
|
|
|
2724
2776
|
});
|
|
2725
2777
|
}
|
|
2726
2778
|
}],
|
|
2727
|
-
bindings: [
|
|
2779
|
+
bindings: [CMD_USAGE_COMMAND_ID]
|
|
2728
2780
|
}));
|
|
2729
2781
|
}
|
|
2730
2782
|
});
|
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.4",
|
|
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",
|