@jeffreyjyz/opencode-command-code 0.4.0 → 0.5.1
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/cli.d.ts +14 -0
- package/dist/constants/cli.d.ts +5 -0
- package/dist/constants/providers.d.ts +4 -0
- package/dist/index.js +68 -4
- package/dist/sidebar/rows.d.ts +24 -4
- package/dist/sidebar/zen.d.ts +12 -3
- package/dist/tui.js +64 -38
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* the homebrew prefix, so the bare name alone is not enough. `CMDUSE_BIN` wins
|
|
3
3
|
* when set — the dev-build hook the sibling cmduse consumers (mpc) honour too. */
|
|
4
4
|
export declare function cmduseCandidates(env?: Record<string, string | undefined>): string[];
|
|
5
|
+
/** Same lookup for mpc, the per-model price comparison: `MPC_BIN`
|
|
6
|
+
* wins when set (the `bun link` dev hook oc-cmd-compare installs),
|
|
7
|
+
* then the bun/homebrew fallbacks. */
|
|
8
|
+
export declare function mpcCandidates(env?: Record<string, string | undefined>): string[];
|
|
5
9
|
/** Shell-words split: whitespace-separated, with single/double quoting and
|
|
6
10
|
* backslash escapes ("--tz +05:30", arg='a b'). Unterminated quotes close at
|
|
7
11
|
* end of input. */
|
|
@@ -10,6 +14,10 @@ export declare function splitCliArgs(input: string): string[];
|
|
|
10
14
|
* pass-through flags, or a subcommand invocation ("plans",
|
|
11
15
|
* "--tz +05:30 daily") passed through untouched. */
|
|
12
16
|
export declare function buildCmduseArgs(arg: string): string[];
|
|
17
|
+
/** argv for an mpc invocation: mpc has no subcommands, so the arg
|
|
18
|
+
* string is split and passed straight through; an empty arg means
|
|
19
|
+
* the fewest-columns view (see MPC_DEFAULT_ARGS). */
|
|
20
|
+
export declare function buildMpcArgs(arg: string): string[];
|
|
13
21
|
export type RunOptions = {
|
|
14
22
|
signal?: AbortSignal;
|
|
15
23
|
/** Extra env for the child (CMD_API_KEY when the plugin resolved one). */
|
|
@@ -21,3 +29,9 @@ export type RunOptions = {
|
|
|
21
29
|
* homebrew locations; every candidate failing with ENOENT yields a single
|
|
22
30
|
* install-hint error. */
|
|
23
31
|
export declare function runCmduse(arg: string, opts?: RunOptions): Promise<string>;
|
|
32
|
+
/** Run mpc and return its plain-text stdout (the per-model price
|
|
33
|
+
* comparison). Same contract as runCmduse: `MPC_BIN` wins, then the
|
|
34
|
+
* fallback paths; every candidate failing with ENOENT yields a
|
|
35
|
+
* single install-hint error, and any other failure (a non-zero exit
|
|
36
|
+
* included) surfaces immediately with mpc's last stderr lines. */
|
|
37
|
+
export declare function runMpc(arg: string, opts?: RunOptions): Promise<string>;
|
package/dist/constants/cli.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { spawn } from "node:child_process";
|
|
2
2
|
/** Default invocation: one-shot dashboard, no colors / no live redraw. */
|
|
3
3
|
export declare const DEFAULT_ARGS: readonly string[];
|
|
4
|
+
/** Default mpc invocation: the fewest-columns view. mpc has no
|
|
5
|
+
* subcommands to fall back on, so an empty arg must mean the
|
|
6
|
+
* narrowest table — a bare tool call cannot dump a wide comparison
|
|
7
|
+
* into the model's context. */
|
|
8
|
+
export declare const MPC_DEFAULT_ARGS: readonly string[];
|
|
4
9
|
/** cmduse subcommands — when the arg starts with one, pass it through
|
|
5
10
|
* untouched; otherwise it is extra flags for the one-shot dashboard. */
|
|
6
11
|
export declare const SUBCOMMANDS: ReadonlySet<string>;
|
|
@@ -15,6 +15,10 @@ export declare const ZEN_PROVIDER_PREFIX = "opencode-zen";
|
|
|
15
15
|
export declare const TUI_ID = "command-code.tui";
|
|
16
16
|
/** The cmd_usage tool description, shared by the v1 and v2 halves. */
|
|
17
17
|
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
|
+
/** The mpc_prices tool description, shared by the v1 and v2 halves. */
|
|
19
|
+
export declare const MPC_TOOL_DESCRIPTION = "Per-model price comparison: what a model costs per request ($/1M input, output and cache rates) and per month (request allowance) on each Command Code (cc) and OpenCode Go (oc) plan side, plus ability, Tok/s, the cheaper-side verdict and running deals. Pass '--model <regex>' to filter rows, '--minimal' | '--medium' | '--detail' for table width, '--json' for the full structured rows, '--cc-plan' / '--oc-plan' to pick a plan, or '--usage' to project real usage.";
|
|
20
|
+
/** The mpc_prices `arg` description, shared by the v1 and v2 halves. */
|
|
21
|
+
export declare const MPC_TOOL_ARG_DESCRIPTION = "Optional mpc flags: '--model <regex>' filters rows, '--minimal' | '--medium' | '--detail' set table width, '--json' for full structured rows, '--cc-plan'/'--oc-plan' pick a plan, '--usage' projects real usage";
|
|
18
22
|
export type Lane = {
|
|
19
23
|
id: "command-code-anthropic" | "command-code-openai";
|
|
20
24
|
name: string;
|
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.
|
|
2417
|
+
version: "0.5.1",
|
|
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",
|
|
@@ -3233,6 +3233,7 @@ var MPC_BIN_CANDIDATES = [
|
|
|
3233
3233
|
|
|
3234
3234
|
// src/constants/cli.ts
|
|
3235
3235
|
var DEFAULT_ARGS = ["-1", "--plain"];
|
|
3236
|
+
var MPC_DEFAULT_ARGS = ["--minimal"];
|
|
3236
3237
|
var SUBCOMMANDS = new Set([
|
|
3237
3238
|
"plans",
|
|
3238
3239
|
"models",
|
|
@@ -3250,6 +3251,11 @@ function cmduseCandidates(env = process.env) {
|
|
|
3250
3251
|
return candidates.filter((bin) => Boolean(bin));
|
|
3251
3252
|
}
|
|
3252
3253
|
var CANDIDATES = cmduseCandidates();
|
|
3254
|
+
function mpcCandidates(env = process.env) {
|
|
3255
|
+
const candidates = [env.MPC_BIN, ...MPC_BIN_CANDIDATES];
|
|
3256
|
+
return candidates.filter((bin) => Boolean(bin));
|
|
3257
|
+
}
|
|
3258
|
+
var MPC_CANDIDATES = mpcCandidates();
|
|
3253
3259
|
function splitCliArgs(input) {
|
|
3254
3260
|
const out = [];
|
|
3255
3261
|
let cur = "";
|
|
@@ -3296,13 +3302,19 @@ function buildCmduseArgs(arg) {
|
|
|
3296
3302
|
return tokens;
|
|
3297
3303
|
return [...DEFAULT_ARGS, ...tokens];
|
|
3298
3304
|
}
|
|
3305
|
+
function buildMpcArgs(arg) {
|
|
3306
|
+
const tokens = splitCliArgs(arg);
|
|
3307
|
+
if (!tokens.length)
|
|
3308
|
+
return [...MPC_DEFAULT_ARGS];
|
|
3309
|
+
return tokens;
|
|
3310
|
+
}
|
|
3299
3311
|
function stderrTail(text) {
|
|
3300
3312
|
const lines = text.trimEnd().split(`
|
|
3301
3313
|
`);
|
|
3302
3314
|
return lines.slice(-3).join(`
|
|
3303
3315
|
`).slice(-400);
|
|
3304
3316
|
}
|
|
3305
|
-
function runOnce(bin, args, opts) {
|
|
3317
|
+
function runOnce(bin, args, opts, name = "cmduse") {
|
|
3306
3318
|
return new Promise((resolve, reject) => {
|
|
3307
3319
|
const child = spawn(bin, args, {
|
|
3308
3320
|
env: { ...process.env, ...opts.env ?? {} },
|
|
@@ -3322,13 +3334,13 @@ function runOnce(bin, args, opts) {
|
|
|
3322
3334
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
3323
3335
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
3324
3336
|
if (opts.signal?.aborted) {
|
|
3325
|
-
reject(new Error(
|
|
3337
|
+
reject(new Error(`${name} invocation aborted`));
|
|
3326
3338
|
} else if (code === 0) {
|
|
3327
3339
|
resolve(stdout);
|
|
3328
3340
|
} else {
|
|
3329
3341
|
const why = signal ? `killed by ${signal}` : `exit ${code}`;
|
|
3330
3342
|
const tail = stderrTail(Buffer.concat(err).toString("utf8"));
|
|
3331
|
-
reject(new Error(
|
|
3343
|
+
reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
3332
3344
|
${tail}` : ""}`));
|
|
3333
3345
|
}
|
|
3334
3346
|
});
|
|
@@ -3349,6 +3361,21 @@ async function runCmduse(arg, opts = {}) {
|
|
|
3349
3361
|
}
|
|
3350
3362
|
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 });
|
|
3351
3363
|
}
|
|
3364
|
+
async function runMpc(arg, opts = {}) {
|
|
3365
|
+
const args = buildMpcArgs(arg);
|
|
3366
|
+
const candidates = opts.candidates ?? MPC_CANDIDATES;
|
|
3367
|
+
let last;
|
|
3368
|
+
for (const bin of candidates) {
|
|
3369
|
+
try {
|
|
3370
|
+
return await runOnce(bin, args, opts, "mpc");
|
|
3371
|
+
} catch (e) {
|
|
3372
|
+
last = e;
|
|
3373
|
+
if (e?.code !== "ENOENT")
|
|
3374
|
+
throw e;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
throw new Error(`mpc binary not found (install: bun add -g @jeffreyjyz/mpc)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, { cause: last });
|
|
3378
|
+
}
|
|
3352
3379
|
|
|
3353
3380
|
// src/constants/models.ts
|
|
3354
3381
|
var MODEL_CAPABILITIES = {
|
|
@@ -3375,6 +3402,8 @@ var PLUGIN_ID = PROVIDER_ID_PREFIX;
|
|
|
3375
3402
|
var INTEGRATION_ID = PROVIDER_ID_PREFIX;
|
|
3376
3403
|
var INTEGRATION_NAME = "Command Code";
|
|
3377
3404
|
var 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').";
|
|
3405
|
+
var MPC_TOOL_DESCRIPTION = "Per-model price comparison: what a model costs per request ($/1M input, output and cache rates) and per month (request allowance) on each Command Code (cc) and OpenCode Go (oc) plan side, plus ability, Tok/s, the cheaper-side verdict and running deals. Pass '--model <regex>' to filter rows, '--minimal' | '--medium' | '--detail' for table width, '--json' for the full structured rows, '--cc-plan' / '--oc-plan' to pick a plan, or '--usage' to project real usage.";
|
|
3406
|
+
var MPC_TOOL_ARG_DESCRIPTION = "Optional mpc flags: '--model <regex>' filters rows, '--minimal' | '--medium' | '--detail' set table width, '--json' for full structured rows, '--cc-plan'/'--oc-plan' pick a plan, '--usage' projects real usage";
|
|
3378
3407
|
var LANES = [
|
|
3379
3408
|
{
|
|
3380
3409
|
id: "command-code-anthropic",
|
|
@@ -4889,6 +4918,29 @@ var commandCodeV2 = {
|
|
|
4889
4918
|
};
|
|
4890
4919
|
}
|
|
4891
4920
|
});
|
|
4921
|
+
editor.add({
|
|
4922
|
+
name: "mpc_prices",
|
|
4923
|
+
description: MPC_TOOL_DESCRIPTION,
|
|
4924
|
+
input: {
|
|
4925
|
+
type: "object",
|
|
4926
|
+
properties: {
|
|
4927
|
+
arg: {
|
|
4928
|
+
type: "string",
|
|
4929
|
+
description: MPC_TOOL_ARG_DESCRIPTION
|
|
4930
|
+
}
|
|
4931
|
+
},
|
|
4932
|
+
additionalProperties: false
|
|
4933
|
+
},
|
|
4934
|
+
async execute(input) {
|
|
4935
|
+
const arg = typeof input?.arg === "string" ? input.arg : "";
|
|
4936
|
+
const key = await credentialKey(ctx);
|
|
4937
|
+
return {
|
|
4938
|
+
content: await runMpc(arg, {
|
|
4939
|
+
env: key ? { CMD_API_KEY: key } : undefined
|
|
4940
|
+
})
|
|
4941
|
+
};
|
|
4942
|
+
}
|
|
4943
|
+
});
|
|
4892
4944
|
});
|
|
4893
4945
|
let hasConnection = false;
|
|
4894
4946
|
let connectionMs = 0;
|
|
@@ -5124,6 +5176,18 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5124
5176
|
env: { CMD_API_KEY: key }
|
|
5125
5177
|
});
|
|
5126
5178
|
}
|
|
5179
|
+
}),
|
|
5180
|
+
mpc_prices: tool({
|
|
5181
|
+
description: MPC_TOOL_DESCRIPTION,
|
|
5182
|
+
args: {
|
|
5183
|
+
arg: string2().optional().describe(MPC_TOOL_ARG_DESCRIPTION)
|
|
5184
|
+
},
|
|
5185
|
+
async execute(args) {
|
|
5186
|
+
const key = await resolveKey();
|
|
5187
|
+
return runMpc(args.arg ?? "", {
|
|
5188
|
+
env: { CMD_API_KEY: key }
|
|
5189
|
+
});
|
|
5190
|
+
}
|
|
5127
5191
|
})
|
|
5128
5192
|
}
|
|
5129
5193
|
};
|
package/dist/sidebar/rows.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface ModelMeta {
|
|
|
43
43
|
/** Cheapest plan that serves the model, from the generated catalog. */
|
|
44
44
|
minPlan?: string | null;
|
|
45
45
|
allowance?: number;
|
|
46
|
-
/** Unbounded on the CommandCode side: `allowance
|
|
46
|
+
/** Unbounded on the CommandCode side: read `free`, not `allowance`, which may still carry a catalog figure. */
|
|
47
47
|
free?: boolean;
|
|
48
48
|
rates?: {
|
|
49
49
|
input: number;
|
|
@@ -118,9 +118,11 @@ export interface Usage {
|
|
|
118
118
|
export declare function money(value: number): string;
|
|
119
119
|
/**
|
|
120
120
|
* A monthly allowance as it appears on the panel. A free/unbounded model has no
|
|
121
|
-
* budget to divide — mpc reports `free: true`
|
|
122
|
-
*
|
|
123
|
-
*
|
|
121
|
+
* budget to divide — mpc reports `free: true` — so it reads `∞` rather than the
|
|
122
|
+
* misleading `$0/mo` (0 is not a budget). `free` decides, so this holds whether
|
|
123
|
+
* the allowance is 0 (OpenCode Go's free rows) or a catalog figure
|
|
124
|
+
* (CommandCode's). Both sides call this, so a free CommandCode model and a free
|
|
125
|
+
* OpenCode Go one render alike.
|
|
124
126
|
*/
|
|
125
127
|
export declare function allowanceLabel(limit: number, free?: boolean): string;
|
|
126
128
|
/** Rate scale needs more precision than credit scale ($0.0036). */
|
|
@@ -128,6 +130,24 @@ export declare function rate(value: number): string;
|
|
|
128
130
|
export declare function count(value: number): string;
|
|
129
131
|
/** "3h 12m" / "1d 20h" / "<1m" for a reset epoch. */
|
|
130
132
|
export declare function until(epoch: number | undefined, now?: number): string;
|
|
133
|
+
/** A rolling window as two or three rows: the use line, then resets plus whatever
|
|
134
|
+
* needs flagging, then an optional `extra` continuation. The lead segment follows
|
|
135
|
+
* cmduse's importance order — over-cap flag, then the cap ETA, then the
|
|
136
|
+
* informational elapsed share — because the 37-column budget fits exactly one of
|
|
137
|
+
* them beside the reset countdown.
|
|
138
|
+
*
|
|
139
|
+
* Shared by both panels: CommandCode passes four arguments (output unchanged),
|
|
140
|
+
* the OpenCode Go/Zen side adds the window's request count as `extra`, so
|
|
141
|
+
* `LIMIT EXCEEDED`, the tone, `cap in …`, `elapsed` and `resets` all have one
|
|
142
|
+
* implementation. */
|
|
143
|
+
export declare function windowRows(label: string, w: WindowUsage | undefined, now: number, durSecs: number, extra?: string): SidebarRow[];
|
|
144
|
+
/**
|
|
145
|
+
* The two store-backed rows for the active model — its requests under `Usage
|
|
146
|
+
* (this model)`, then this conversation's own totals under `Session`. They need
|
|
147
|
+
* no network, so both panels keep them when the catalog is missing, and both
|
|
148
|
+
* families build them here rather than in two places.
|
|
149
|
+
*/
|
|
150
|
+
export declare function localUsageRows(usage?: ModelUsage, session?: ModelUsage): SidebarRow[];
|
|
131
151
|
/** Plan + rolling windows + period totals, from a cmduse snapshot. */
|
|
132
152
|
export declare function usageRows(usage: Usage | undefined, now?: number): SidebarRow[];
|
|
133
153
|
/** Active model's allowance, rates and benchmarks, from mpc's catalog; its
|
package/dist/sidebar/zen.d.ts
CHANGED
|
@@ -14,7 +14,15 @@ export interface ZenModel {
|
|
|
14
14
|
month: ZenTotals;
|
|
15
15
|
}
|
|
16
16
|
export interface ZenUsage {
|
|
17
|
+
/**
|
|
18
|
+
* Billing period start, epoch ms: `periodStart` on current ocuse, or
|
|
19
|
+
* `periodStartAt` once it publishes the bounds (the same instants cmduse
|
|
20
|
+
* 0.7.2+ carries).
|
|
21
|
+
*/
|
|
17
22
|
periodStart?: number;
|
|
23
|
+
/** Billing period end, epoch ms. Optional: older ocuse does not publish it
|
|
24
|
+
* and the Monthly row must then degrade to spend alone. */
|
|
25
|
+
periodEndAt?: number;
|
|
18
26
|
source?: string;
|
|
19
27
|
totals: {
|
|
20
28
|
fiveHour: ZenTotals;
|
|
@@ -28,6 +36,7 @@ export interface ZenUsage {
|
|
|
28
36
|
export declare function parseZenJson(text: string): ZenUsage;
|
|
29
37
|
/** The OpenCode Go / Zen panel: period windows, a rule, then the active model.
|
|
30
38
|
* Like `modelRows`, `fallbackName` (the session's own id) keeps the model block
|
|
31
|
-
* — and the local `
|
|
32
|
-
* unreachable; only the catalog-derived rows
|
|
33
|
-
|
|
39
|
+
* — and the local `Usage (this model)` / `Session` figures, which need no network
|
|
40
|
+
* — on screen when mpc's catalog is unreachable; only the catalog-derived rows
|
|
41
|
+
* then drop out. */
|
|
42
|
+
export declare function zenRows(usage: ZenUsage | undefined, meta: ModelMeta | undefined, modelUsage?: ModelUsage, session?: ModelUsage, fallbackName?: string): SidebarRow[];
|
package/dist/tui.js
CHANGED
|
@@ -26,6 +26,11 @@ function cmduseCandidates(env = process.env) {
|
|
|
26
26
|
return candidates.filter((bin) => Boolean(bin));
|
|
27
27
|
}
|
|
28
28
|
var CANDIDATES = cmduseCandidates();
|
|
29
|
+
function mpcCandidates(env = process.env) {
|
|
30
|
+
const candidates = [env.MPC_BIN, ...MPC_BIN_CANDIDATES];
|
|
31
|
+
return candidates.filter((bin) => Boolean(bin));
|
|
32
|
+
}
|
|
33
|
+
var MPC_CANDIDATES = mpcCandidates();
|
|
29
34
|
function splitCliArgs(input) {
|
|
30
35
|
const out = [];
|
|
31
36
|
let cur = "";
|
|
@@ -78,7 +83,7 @@ function stderrTail(text) {
|
|
|
78
83
|
return lines.slice(-3).join(`
|
|
79
84
|
`).slice(-400);
|
|
80
85
|
}
|
|
81
|
-
function runOnce(bin, args, opts) {
|
|
86
|
+
function runOnce(bin, args, opts, name = "cmduse") {
|
|
82
87
|
return new Promise((resolve, reject) => {
|
|
83
88
|
const child = spawn(bin, args, {
|
|
84
89
|
env: {
|
|
@@ -103,13 +108,13 @@ function runOnce(bin, args, opts) {
|
|
|
103
108
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
104
109
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
105
110
|
if (opts.signal?.aborted) {
|
|
106
|
-
reject(new Error(
|
|
111
|
+
reject(new Error(`${name} invocation aborted`));
|
|
107
112
|
} else if (code === 0) {
|
|
108
113
|
resolve(stdout);
|
|
109
114
|
} else {
|
|
110
115
|
const why = signal ? `killed by ${signal}` : `exit ${code}`;
|
|
111
116
|
const tail = stderrTail(Buffer.concat(err).toString("utf8"));
|
|
112
|
-
reject(new Error(
|
|
117
|
+
reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
113
118
|
${tail}` : ""}`));
|
|
114
119
|
}
|
|
115
120
|
});
|
|
@@ -616,7 +621,7 @@ function until(epoch, now = Date.now()) {
|
|
|
616
621
|
const days = Math.floor(hours / 24);
|
|
617
622
|
return hours % 24 ? `${days}d ${hours % 24}h` : `${days}d`;
|
|
618
623
|
}
|
|
619
|
-
function windowRows(label, w, now, durSecs) {
|
|
624
|
+
function windowRows(label, w, now, durSecs, extra) {
|
|
620
625
|
if (!w || typeof w.cap !== "number" || w.cap <= 0)
|
|
621
626
|
return [];
|
|
622
627
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
@@ -627,7 +632,23 @@ function windowRows(label, w, now, durSecs) {
|
|
|
627
632
|
const reset = until(w.resetAt, now);
|
|
628
633
|
const lead = w.exceeded ? "LIMIT EXCEEDED" : eta === undefined ? elapsed === undefined ? "" : `${elapsed} elapsed` : `cap in ${until(now + eta * 1000, now)}`;
|
|
629
634
|
const detail = [lead, reset ? `resets ${reset}` : ""].filter(Boolean).join(" \xB7 ");
|
|
630
|
-
return [[label, `${money(used)}/${money(w.cap)} (${pct.toFixed(1)}%)`, w.exceeded ? "crit" : pctTone(pct)], ...detail ? [["", detail, "muted"]] : []];
|
|
635
|
+
return [[label, `${money(used)}/${money(w.cap)} (${pct.toFixed(1)}%)`, w.exceeded ? "crit" : pctTone(pct)], ...detail ? [["", detail, "muted"]] : [], ...extra ? [["", extra, "muted"]] : []];
|
|
636
|
+
}
|
|
637
|
+
function localUsageRows(usage, session) {
|
|
638
|
+
const rows = [];
|
|
639
|
+
if (usage) {
|
|
640
|
+
rows.push(["Usage (this model)", `${count(usage.requests)} req`, "base"]);
|
|
641
|
+
const spend = usage.cost > 0 ? money(usage.cost) : "";
|
|
642
|
+
const share = usage.cost > 0 && session && session.cost > 0 ? `${Math.min(100, Math.round(usage.cost / session.cost * 100))}% of session` : "";
|
|
643
|
+
const detail = [spend, share].filter(Boolean).join(" \xB7 ");
|
|
644
|
+
if (detail)
|
|
645
|
+
rows.push(["", detail, "muted"]);
|
|
646
|
+
}
|
|
647
|
+
if (session) {
|
|
648
|
+
const spent = session.cost > 0 ? ` \xB7 ${money(session.cost)}` : "";
|
|
649
|
+
rows.push(["Session", `${count(session.requests)} req${spent}`, "base"]);
|
|
650
|
+
}
|
|
651
|
+
return rows;
|
|
631
652
|
}
|
|
632
653
|
function monthlyDetail(usage, now) {
|
|
633
654
|
const {
|
|
@@ -676,19 +697,7 @@ function modelRows(meta, usage, session, fallbackName) {
|
|
|
676
697
|
const name = meta?.name ?? fallbackName;
|
|
677
698
|
if (!name)
|
|
678
699
|
return [];
|
|
679
|
-
const rows = [["Model", clip(name, valueWidth("Model")), "strong", true]];
|
|
680
|
-
if (usage) {
|
|
681
|
-
rows.push(["Usage (this model)", `${count(usage.requests)} req`, "base"]);
|
|
682
|
-
const spend = usage.cost > 0 ? money(usage.cost) : "";
|
|
683
|
-
const share = usage.cost > 0 && session && session.cost > 0 ? `${Math.min(100, Math.round(usage.cost / session.cost * 100))}% of session` : "";
|
|
684
|
-
const detail = [spend, share].filter(Boolean).join(" \xB7 ");
|
|
685
|
-
if (detail)
|
|
686
|
-
rows.push(["", detail, "muted"]);
|
|
687
|
-
}
|
|
688
|
-
if (session) {
|
|
689
|
-
const spent = session.cost > 0 ? ` \xB7 ${money(session.cost)}` : "";
|
|
690
|
-
rows.push(["Session", `${count(session.requests)} req${spent}`, "base"]);
|
|
691
|
-
}
|
|
700
|
+
const rows = [["Model", clip(name, valueWidth("Model")), "strong", true], ...localUsageRows(usage, session)];
|
|
692
701
|
if (!meta)
|
|
693
702
|
return rows;
|
|
694
703
|
if (meta.tier)
|
|
@@ -2055,7 +2064,8 @@ function parseZenJson(text) {
|
|
|
2055
2064
|
}
|
|
2056
2065
|
const top = body.totals ?? {};
|
|
2057
2066
|
return {
|
|
2058
|
-
periodStart: typeof body.periodStart === "number" ? body.periodStart : undefined,
|
|
2067
|
+
periodStart: typeof body.periodStartAt === "number" ? body.periodStartAt : typeof body.periodStart === "number" ? body.periodStart : undefined,
|
|
2068
|
+
periodEndAt: typeof body.periodEndAt === "number" ? body.periodEndAt : undefined,
|
|
2059
2069
|
source: typeof body.source === "string" ? body.source : undefined,
|
|
2060
2070
|
totals: {
|
|
2061
2071
|
fiveHour: totals(top.fiveHour),
|
|
@@ -2065,38 +2075,53 @@ function parseZenJson(text) {
|
|
|
2065
2075
|
models
|
|
2066
2076
|
};
|
|
2067
2077
|
}
|
|
2068
|
-
function
|
|
2069
|
-
|
|
2078
|
+
function monthPeriod(usage) {
|
|
2079
|
+
const {
|
|
2080
|
+
periodStart,
|
|
2081
|
+
periodEndAt
|
|
2082
|
+
} = usage;
|
|
2083
|
+
if (typeof periodStart !== "number" || typeof periodEndAt !== "number")
|
|
2084
|
+
return;
|
|
2085
|
+
const durSecs = Math.round((periodEndAt - periodStart) / 1000);
|
|
2086
|
+
if (durSecs <= 0)
|
|
2087
|
+
return;
|
|
2088
|
+
return {
|
|
2089
|
+
resetAt: periodEndAt,
|
|
2090
|
+
durSecs
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
function zenWindow(label, spent, limit, share, now, durSecs, resetAt) {
|
|
2094
|
+
const cap = typeof limit === "number" && limit > 0 ? limit * share : undefined;
|
|
2095
|
+
if (cap === undefined) {
|
|
2070
2096
|
return [[label, `${money(spent.cost)} \xB7 ${count(spent.requests)} req`, "base"]];
|
|
2071
2097
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
}
|
|
2079
|
-
function zenRows(usage, meta, session, fallbackName) {
|
|
2098
|
+
return windowRows(label, {
|
|
2099
|
+
cap,
|
|
2100
|
+
used: spent.cost,
|
|
2101
|
+
resetAt,
|
|
2102
|
+
exceeded: spent.cost >= cap
|
|
2103
|
+
}, now, durSecs, spent.requests > 0 ? `${count(spent.requests)} req` : undefined);
|
|
2104
|
+
}
|
|
2105
|
+
function zenRows(usage, meta, modelUsage, session, fallbackName) {
|
|
2080
2106
|
if (!usage)
|
|
2081
2107
|
return [];
|
|
2108
|
+
const now = Date.now();
|
|
2082
2109
|
const oc = meta?.oc;
|
|
2083
2110
|
const rows = [["Plan", `${oc?.plan ?? "Go"} \xB7 limits per model`, "base"]];
|
|
2084
2111
|
const mine = meta ? usage.models.get(modelKey(meta.name)) : undefined;
|
|
2085
2112
|
const limit = oc?.allowance ?? mine?.limit;
|
|
2086
2113
|
const cap = mine ? limit : undefined;
|
|
2087
|
-
rows.push(...
|
|
2088
|
-
rows.push(...
|
|
2089
|
-
|
|
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));
|
|
2116
|
+
const period = monthPeriod(usage);
|
|
2117
|
+
rows.push(...zenWindow("Monthly", mine?.month ?? usage.totals.month, cap, GO_WINDOW_SHARE.month, now, period?.durSecs ?? 0, period?.resetAt));
|
|
2090
2118
|
rows.push(["Period", `${count(usage.totals.month.requests)} requests \xB7 ${money(usage.totals.month.cost)}`, "base"]);
|
|
2091
2119
|
rows.push(separator());
|
|
2092
2120
|
const name = meta?.name ?? fallbackName;
|
|
2093
2121
|
if (!name)
|
|
2094
2122
|
return rows;
|
|
2095
2123
|
rows.push(["Model", clip(name, valueWidth("Model")), "strong", true]);
|
|
2096
|
-
|
|
2097
|
-
const spent = session.cost > 0 ? ` \xB7 ${money(session.cost)}` : "";
|
|
2098
|
-
rows.push(["Session", `${count(session.requests)} req${spent}`, "base"]);
|
|
2099
|
-
}
|
|
2124
|
+
rows.push(...localUsageRows(modelUsage, session));
|
|
2100
2125
|
if (!meta)
|
|
2101
2126
|
return rows;
|
|
2102
2127
|
if (typeof limit === "number") {
|
|
@@ -2498,6 +2523,7 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2498
2523
|
refreshModelUsage(activeModelId());
|
|
2499
2524
|
} else if (kind === "go" || kind === "zen") {
|
|
2500
2525
|
setZen(await loadZen(loadPrefs().ocPlan));
|
|
2526
|
+
refreshModelUsage(activeModelId());
|
|
2501
2527
|
}
|
|
2502
2528
|
} catch {} finally {
|
|
2503
2529
|
inFlight = false;
|
|
@@ -2515,7 +2541,7 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2515
2541
|
createEffect(() => {
|
|
2516
2542
|
const id = activeModelId();
|
|
2517
2543
|
const session = sessionID();
|
|
2518
|
-
if (sessionKind(providerID())
|
|
2544
|
+
if (!shows(sessionKind(providerID())) || !id || !session)
|
|
2519
2545
|
return;
|
|
2520
2546
|
refreshModelUsage(id);
|
|
2521
2547
|
});
|
|
@@ -2535,7 +2561,7 @@ function useRows(activeModelId, providerID, sessionID) {
|
|
|
2535
2561
|
const found = id ? meta().get(modelKey(id)) ?? meta().get(id) : undefined;
|
|
2536
2562
|
const fallbackName = id ? id.split("/").pop() ?? id : undefined;
|
|
2537
2563
|
if (kind === "go" || kind === "zen") {
|
|
2538
|
-
return zenRows(zen(), found, sessionUsage(), fallbackName);
|
|
2564
|
+
return zenRows(zen(), found, usageFor(id), sessionUsage(), fallbackName);
|
|
2539
2565
|
}
|
|
2540
2566
|
const model = found ? {
|
|
2541
2567
|
...found,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffreyjyz/opencode-command-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|