@jeffreyjyz/opencode-command-code 0.5.3 → 0.5.7
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 +25 -2
- package/dist/constants/providers.d.ts +8 -0
- package/dist/constants/sidebar.d.ts +3 -2
- package/dist/index.js +104 -23
- package/dist/sidebar/data.d.ts +4 -3
- package/dist/sidebar/rows.d.ts +16 -1
- package/dist/sidebar/usageDb.d.ts +3 -3
- package/dist/sidebar/zen.d.ts +28 -0
- package/dist/tui.js +157 -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. */
|
|
@@ -18,20 +22,39 @@ export declare function buildCmduseArgs(arg: string): string[];
|
|
|
18
22
|
* string is split and passed straight through; an empty arg means
|
|
19
23
|
* the fewest-columns view (see MPC_DEFAULT_ARGS). */
|
|
20
24
|
export declare function buildMpcArgs(arg: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* The arg the ocuse_usage tool and the /ocuse-usage dialog pass on: the
|
|
27
|
+
* agent's (or slash) args when given, else `-1`. A bare ocuse is the live
|
|
28
|
+
* watch loop and would never return, so empty input never passes through.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ocuseArgs(arg: string | undefined): string;
|
|
21
31
|
export type RunOptions = {
|
|
22
32
|
signal?: AbortSignal;
|
|
23
33
|
/** Extra env for the child (CMD_API_KEY when the plugin resolved one). */
|
|
24
34
|
env?: Record<string, string>;
|
|
25
35
|
/** Overridable for tests. */
|
|
26
36
|
candidates?: readonly string[];
|
|
37
|
+
/** Extra spawn options. Excludes `stdio`: the callbacks below assume stdout
|
|
38
|
+
* and stderr are piped (the default here), so callers may only add fields
|
|
39
|
+
* that leave that contract intact — the sidebar passes `detached` (see
|
|
40
|
+
* constants/cli.ts; its own stdio equals the default). */
|
|
41
|
+
spawnOptions?: Omit<NonNullable<Parameters<typeof spawn>[2]>, "stdio">;
|
|
42
|
+
/** Kill the child after this many ms. The sidebar bounds each poll; the
|
|
43
|
+
* tool spawns leave it unset. */
|
|
44
|
+
timeoutMs?: number;
|
|
27
45
|
};
|
|
28
46
|
/** Run cmduse and return its plain-text stdout. Tries PATH, then common
|
|
29
47
|
* homebrew locations; every candidate failing with ENOENT yields a single
|
|
30
48
|
* install-hint error. */
|
|
31
|
-
export declare function runCmduse(arg: string, opts?: RunOptions): Promise<string>;
|
|
49
|
+
export declare function runCmduse(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
|
|
32
50
|
/** Run mpc and return its plain-text stdout (the per-model price
|
|
33
51
|
* comparison). Same contract as runCmduse: `MPC_BIN` wins, then the
|
|
34
52
|
* fallback paths; every candidate failing with ENOENT yields a
|
|
35
53
|
* single install-hint error, and any other failure (a non-zero exit
|
|
36
54
|
* included) surfaces immediately with mpc's last stderr lines. */
|
|
37
|
-
export declare function runMpc(arg: string, opts?: RunOptions): Promise<string>;
|
|
55
|
+
export declare function runMpc(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
|
|
56
|
+
/** Run ocuse and return its plain-text stdout (OpenCode Go/Zen usage). Same
|
|
57
|
+
* contract as runCmduse; ocuse ships in the cmd-usage crate, so its install
|
|
58
|
+
* hint is the same brew formula. A string arg is shell-split, an array used
|
|
59
|
+
* as-is. */
|
|
60
|
+
export declare function runOcuse(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
|
|
@@ -13,12 +13,20 @@ 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";
|
|
18
|
+
/** The v2 /ocuse-usage slash command id (registered and bound in tui.tsx). */
|
|
19
|
+
export declare const OCUSE_USAGE_COMMAND_ID = "command-code.ocuse-usage";
|
|
16
20
|
/** The cmd_usage tool description, shared by the v1 and v2 halves. */
|
|
17
21
|
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
22
|
/** The mpc_prices tool description, shared by the v1 and v2 halves. */
|
|
19
23
|
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
24
|
/** The mpc_prices `arg` description, shared by the v1 and v2 halves. */
|
|
21
25
|
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";
|
|
26
|
+
/** The ocuse_usage tool description, shared by the v1 and v2 halves. */
|
|
27
|
+
export declare const OCUSE_TOOL_DESCRIPTION = "Fetch live OpenCode Go/Zen usage: the account's window meters, per-model spend against limits, 5-hour & weekly windows, billing-period summary. Defaults to the one-shot dashboard; pass extra ocuse flags for more (e.g. '--plan go-plus', 'daily', '--json').";
|
|
28
|
+
/** The ocuse_usage `arg` description, shared by the v1 and v2 halves. */
|
|
29
|
+
export declare const OCUSE_TOOL_ARG_DESCRIPTION = "Optional: extra ocuse flags (e.g. '--plan go-plus', 'daily', '--json'); empty means the one-shot dashboard";
|
|
22
30
|
export type Lane = {
|
|
23
31
|
id: "command-code-anthropic" | "command-code-openai";
|
|
24
32
|
name: string;
|
|
@@ -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.7",
|
|
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 = "";
|
|
@@ -3308,6 +3318,9 @@ function buildMpcArgs(arg) {
|
|
|
3308
3318
|
return [...MPC_DEFAULT_ARGS];
|
|
3309
3319
|
return tokens;
|
|
3310
3320
|
}
|
|
3321
|
+
function ocuseArgs(arg) {
|
|
3322
|
+
return arg !== undefined && arg.trim() !== "" ? arg : "-1";
|
|
3323
|
+
}
|
|
3311
3324
|
function stderrTail(text) {
|
|
3312
3325
|
const lines = text.trimEnd().split(`
|
|
3313
3326
|
`);
|
|
@@ -3318,36 +3331,53 @@ function runOnce(bin, args, opts, name = "cmduse") {
|
|
|
3318
3331
|
return new Promise((resolve, reject) => {
|
|
3319
3332
|
const child = spawn(bin, args, {
|
|
3320
3333
|
env: { ...process.env, ...opts.env ?? {} },
|
|
3321
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3334
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3335
|
+
...opts.spawnOptions
|
|
3322
3336
|
});
|
|
3323
3337
|
const out = [];
|
|
3324
3338
|
const err = [];
|
|
3339
|
+
let settled = false;
|
|
3340
|
+
const timer = opts.timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
3341
|
+
if (settled)
|
|
3342
|
+
return;
|
|
3343
|
+
settled = true;
|
|
3344
|
+
child.kill();
|
|
3345
|
+
reject(new Error(`${name} ${args.join(" ")}: timed out after ${opts.timeoutMs}ms`));
|
|
3346
|
+
}, opts.timeoutMs);
|
|
3325
3347
|
const onAbort = () => child.kill("SIGTERM");
|
|
3326
3348
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3349
|
+
const settle = (fn) => {
|
|
3350
|
+
if (settled)
|
|
3351
|
+
return;
|
|
3352
|
+
settled = true;
|
|
3353
|
+
if (timer)
|
|
3354
|
+
clearTimeout(timer);
|
|
3355
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
3356
|
+
fn();
|
|
3357
|
+
};
|
|
3327
3358
|
child.stdout.on("data", (d) => out.push(d));
|
|
3328
3359
|
child.stderr.on("data", (d) => err.push(d));
|
|
3329
|
-
child.on("error", (e) =>
|
|
3330
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
3331
|
-
reject(e);
|
|
3332
|
-
});
|
|
3360
|
+
child.on("error", (e) => settle(() => reject(e)));
|
|
3333
3361
|
child.on("close", (code, signal) => {
|
|
3334
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
3335
3362
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
3336
3363
|
if (opts.signal?.aborted) {
|
|
3337
|
-
reject(new Error(`${name} invocation aborted`));
|
|
3364
|
+
settle(() => reject(new Error(`${name} invocation aborted`)));
|
|
3338
3365
|
} else if (code === 0) {
|
|
3339
|
-
resolve(stdout);
|
|
3366
|
+
settle(() => resolve(stdout));
|
|
3340
3367
|
} else {
|
|
3341
3368
|
const why = signal ? `killed by ${signal}` : `exit ${code}`;
|
|
3342
3369
|
const tail = stderrTail(Buffer.concat(err).toString("utf8"));
|
|
3343
|
-
reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
3344
|
-
${tail}` : ""}`));
|
|
3370
|
+
settle(() => reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
3371
|
+
${tail}` : ""}`)));
|
|
3345
3372
|
}
|
|
3346
3373
|
});
|
|
3347
3374
|
});
|
|
3348
3375
|
}
|
|
3376
|
+
function resolveArgs(arg, build) {
|
|
3377
|
+
return typeof arg === "string" ? build(arg) : [...arg];
|
|
3378
|
+
}
|
|
3349
3379
|
async function runCmduse(arg, opts = {}) {
|
|
3350
|
-
const args =
|
|
3380
|
+
const args = resolveArgs(arg, buildCmduseArgs);
|
|
3351
3381
|
const candidates = opts.candidates ?? CANDIDATES;
|
|
3352
3382
|
let last;
|
|
3353
3383
|
for (const bin of candidates) {
|
|
@@ -3362,7 +3392,7 @@ async function runCmduse(arg, opts = {}) {
|
|
|
3362
3392
|
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
3393
|
}
|
|
3364
3394
|
async function runMpc(arg, opts = {}) {
|
|
3365
|
-
const args =
|
|
3395
|
+
const args = resolveArgs(arg, buildMpcArgs);
|
|
3366
3396
|
const candidates = opts.candidates ?? MPC_CANDIDATES;
|
|
3367
3397
|
let last;
|
|
3368
3398
|
for (const bin of candidates) {
|
|
@@ -3376,6 +3406,21 @@ async function runMpc(arg, opts = {}) {
|
|
|
3376
3406
|
}
|
|
3377
3407
|
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
3408
|
}
|
|
3409
|
+
async function runOcuse(arg, opts = {}) {
|
|
3410
|
+
const args = resolveArgs(arg, splitCliArgs);
|
|
3411
|
+
const candidates = opts.candidates ?? OCUSE_CANDIDATES;
|
|
3412
|
+
let last;
|
|
3413
|
+
for (const bin of candidates) {
|
|
3414
|
+
try {
|
|
3415
|
+
return await runOnce(bin, args, opts, "ocuse");
|
|
3416
|
+
} catch (e) {
|
|
3417
|
+
last = e;
|
|
3418
|
+
if (e?.code !== "ENOENT")
|
|
3419
|
+
throw e;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
throw new Error(`ocuse binary not found (install: brew install JeffreyJYZ/tap/cmduse)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, { cause: last });
|
|
3423
|
+
}
|
|
3379
3424
|
|
|
3380
3425
|
// src/constants/models.ts
|
|
3381
3426
|
var MODEL_CAPABILITIES = {
|
|
@@ -3404,6 +3449,8 @@ var INTEGRATION_NAME = "Command Code";
|
|
|
3404
3449
|
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
3450
|
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
3451
|
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";
|
|
3452
|
+
var OCUSE_TOOL_DESCRIPTION = "Fetch live OpenCode Go/Zen usage: the account's window meters, per-model spend against limits, 5-hour & weekly windows, billing-period summary. Defaults to the one-shot dashboard; pass extra ocuse flags for more (e.g. '--plan go-plus', 'daily', '--json').";
|
|
3453
|
+
var OCUSE_TOOL_ARG_DESCRIPTION = "Optional: extra ocuse flags (e.g. '--plan go-plus', 'daily', '--json'); empty means the one-shot dashboard";
|
|
3407
3454
|
var LANES = [
|
|
3408
3455
|
{
|
|
3409
3456
|
id: "command-code-anthropic",
|
|
@@ -4941,6 +4988,26 @@ var commandCodeV2 = {
|
|
|
4941
4988
|
};
|
|
4942
4989
|
}
|
|
4943
4990
|
});
|
|
4991
|
+
editor.add({
|
|
4992
|
+
name: "ocuse_usage",
|
|
4993
|
+
description: OCUSE_TOOL_DESCRIPTION,
|
|
4994
|
+
input: {
|
|
4995
|
+
type: "object",
|
|
4996
|
+
properties: {
|
|
4997
|
+
arg: {
|
|
4998
|
+
type: "string",
|
|
4999
|
+
description: OCUSE_TOOL_ARG_DESCRIPTION
|
|
5000
|
+
}
|
|
5001
|
+
},
|
|
5002
|
+
additionalProperties: false
|
|
5003
|
+
},
|
|
5004
|
+
async execute(input) {
|
|
5005
|
+
const arg = typeof input?.arg === "string" ? input.arg : undefined;
|
|
5006
|
+
return {
|
|
5007
|
+
content: await runOcuse(ocuseArgs(arg))
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
5010
|
+
});
|
|
4944
5011
|
});
|
|
4945
5012
|
let hasConnection = false;
|
|
4946
5013
|
let connectionMs = 0;
|
|
@@ -5025,6 +5092,7 @@ var commandCodeV2 = {
|
|
|
5025
5092
|
|
|
5026
5093
|
// src/index.ts
|
|
5027
5094
|
var PROVIDER_NPM = `${package_default.name}@${package_default.version}`;
|
|
5095
|
+
var [ANTHROPIC, OPENAI] = LANES;
|
|
5028
5096
|
function tool(input) {
|
|
5029
5097
|
return input;
|
|
5030
5098
|
}
|
|
@@ -5071,7 +5139,7 @@ function toModelDefs(models, providerID, npm, interleaved = false) {
|
|
|
5071
5139
|
var CommandCodePlugin = async (_input) => {
|
|
5072
5140
|
return {
|
|
5073
5141
|
auth: {
|
|
5074
|
-
provider:
|
|
5142
|
+
provider: ANTHROPIC.id,
|
|
5075
5143
|
loader: async (getAuth) => {
|
|
5076
5144
|
const auth = await getAuth();
|
|
5077
5145
|
if (auth?.type !== "api") {
|
|
@@ -5101,7 +5169,7 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5101
5169
|
return {
|
|
5102
5170
|
type: "success",
|
|
5103
5171
|
key,
|
|
5104
|
-
provider:
|
|
5172
|
+
provider: ANTHROPIC.id
|
|
5105
5173
|
};
|
|
5106
5174
|
} catch {
|
|
5107
5175
|
return { type: "failed" };
|
|
@@ -5125,19 +5193,19 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5125
5193
|
console.warn("[command-code] model list unavailable:", e);
|
|
5126
5194
|
}
|
|
5127
5195
|
}
|
|
5128
|
-
const claudeDefs = split ? toModelDefs(split.claude,
|
|
5129
|
-
const openDefs = split ? toModelDefs(split.open,
|
|
5196
|
+
const claudeDefs = split ? toModelDefs(split.claude, ANTHROPIC.id, PROVIDER_NPM) : {};
|
|
5197
|
+
const openDefs = split ? toModelDefs(split.open, OPENAI.id, PROVIDER_NPM, {
|
|
5130
5198
|
field: "reasoning_content"
|
|
5131
5199
|
}) : {};
|
|
5132
|
-
const userAnthropic = existing(
|
|
5133
|
-
cfg.provider[
|
|
5200
|
+
const userAnthropic = existing(ANTHROPIC.id);
|
|
5201
|
+
cfg.provider[ANTHROPIC.id] = {
|
|
5134
5202
|
npm: userAnthropic.npm ?? PROVIDER_NPM,
|
|
5135
5203
|
name: userAnthropic.name ?? "Command Code (Anthropic)",
|
|
5136
5204
|
options: { baseURL: PROVIDER_BASE, ...userAnthropic.options },
|
|
5137
5205
|
models: { ...claudeDefs, ...userAnthropic.models }
|
|
5138
5206
|
};
|
|
5139
|
-
const userOpenai = existing(
|
|
5140
|
-
cfg.provider[
|
|
5207
|
+
const userOpenai = existing(OPENAI.id);
|
|
5208
|
+
cfg.provider[OPENAI.id] = {
|
|
5141
5209
|
npm: userOpenai.npm ?? PROVIDER_NPM,
|
|
5142
5210
|
name: userOpenai.name ?? "Command Code (OpenAI)",
|
|
5143
5211
|
options: {
|
|
@@ -5152,16 +5220,20 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5152
5220
|
description: "Show Command Code plan, credits, and usage windows",
|
|
5153
5221
|
template: "Call the cmd_usage tool and present its markdown output verbatim to the user. If the tool errors, tell the user to run /connect (Command Code (Anthropic)) and retry. $ARGUMENTS"
|
|
5154
5222
|
};
|
|
5223
|
+
cfg.command["ocuse-usage"] = {
|
|
5224
|
+
description: "Show OpenCode Go/Zen usage: account meters and per-model spend",
|
|
5225
|
+
template: "Call the ocuse_usage tool and present its markdown output verbatim to the user. If the tool errors, the Go key is missing or has no subscription \u2014 usage still shows where the store has it. $ARGUMENTS"
|
|
5226
|
+
};
|
|
5155
5227
|
},
|
|
5156
5228
|
provider: {
|
|
5157
|
-
id:
|
|
5229
|
+
id: ANTHROPIC.id,
|
|
5158
5230
|
models: async (_provider, ctx) => {
|
|
5159
5231
|
const key = await resolveKey(async () => {
|
|
5160
5232
|
const a = ctx.auth;
|
|
5161
5233
|
return a?.type === "api" && a.key ? { key: a.key } : undefined;
|
|
5162
5234
|
});
|
|
5163
5235
|
const split = await loadModels(key);
|
|
5164
|
-
return toModelDefs(split.claude,
|
|
5236
|
+
return toModelDefs(split.claude, ANTHROPIC.id, PROVIDER_NPM);
|
|
5165
5237
|
}
|
|
5166
5238
|
},
|
|
5167
5239
|
tool: {
|
|
@@ -5188,6 +5260,15 @@ var CommandCodePlugin = async (_input) => {
|
|
|
5188
5260
|
env: { CMD_API_KEY: key }
|
|
5189
5261
|
});
|
|
5190
5262
|
}
|
|
5263
|
+
}),
|
|
5264
|
+
ocuse_usage: tool({
|
|
5265
|
+
description: OCUSE_TOOL_DESCRIPTION,
|
|
5266
|
+
args: {
|
|
5267
|
+
arg: string2().optional().describe(OCUSE_TOOL_ARG_DESCRIPTION)
|
|
5268
|
+
},
|
|
5269
|
+
async execute(args) {
|
|
5270
|
+
return runOcuse(ocuseArgs(args.arg));
|
|
5271
|
+
}
|
|
5191
5272
|
})
|
|
5192
5273
|
}
|
|
5193
5274
|
};
|
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
|
@@ -87,6 +87,15 @@ export interface WindowUsage {
|
|
|
87
87
|
resetAt?: number;
|
|
88
88
|
/** The account API's own over-cap flag (cmduse passes it through). */
|
|
89
89
|
exceeded?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The account's own share of the limit, when its endpoint answered. That
|
|
92
|
+
* figure is the one the server gates on and prints on its console, so it —
|
|
93
|
+
* not `used / cap`, which this machine computes and which sits below the
|
|
94
|
+
* server's meter — sets the row's tone and reads as the share. The money
|
|
95
|
+
* pair stays this store's own, which is why the two need not divide to each
|
|
96
|
+
* other.
|
|
97
|
+
*/
|
|
98
|
+
sharePct?: number;
|
|
90
99
|
}
|
|
91
100
|
/** One model's usage for the account's current billing period. */
|
|
92
101
|
export interface ModelUsage {
|
|
@@ -140,7 +149,13 @@ export declare function until(epoch: number | undefined, now?: number): string;
|
|
|
140
149
|
* the OpenCode Go/Zen side adds the window's request count as `extra`, so
|
|
141
150
|
* `LIMIT EXCEEDED`, the tone, `cap in …`, `elapsed` and `resets` all have one
|
|
142
151
|
* implementation. */
|
|
143
|
-
export declare function windowRows(label: string, w: WindowUsage | undefined, now: number, durSecs: number, extra?: string
|
|
152
|
+
export declare function windowRows(label: string, w: WindowUsage | undefined, now: number, durSecs: number, extra?: string,
|
|
153
|
+
/** How the use line reads. The default is money (`$0.55/$14 (3.9%)`); the
|
|
154
|
+
* account's own meters publish their own share of the limit, which the
|
|
155
|
+
* formatter receives as `share` — the money pair beside it stays local, so
|
|
156
|
+
* the tone, `LIMIT EXCEEDED`, `cap in …`, `elapsed` and `resets` stay this
|
|
157
|
+
* one implementation for both panels. */
|
|
158
|
+
use?: (used: number, cap: number, share: number) => string): SidebarRow[];
|
|
144
159
|
/**
|
|
145
160
|
* The two store-backed rows for the active model — its requests under `Usage
|
|
146
161
|
* (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,15 @@ 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;
|
|
53
|
+
/**
|
|
54
|
+
* Why `account` is absent, when ocuse could name it: `HTTP 401
|
|
55
|
+
* api_key_expired`, `no Go key`, a transport fault (`ocuse` 0.7.14+). The
|
|
56
|
+
* panel shows it, because "estimated locally" alone cannot tell a missing key
|
|
57
|
+
* from an expired one — and only the expired one is something to act on.
|
|
58
|
+
*/
|
|
59
|
+
accountError?: string;
|
|
32
60
|
/** Keyed by canonical model key, the same normalisation the CC side matches on. */
|
|
33
61
|
models: Map<string, ZenModel>;
|
|
34
62
|
}
|
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,15 @@ 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
|
+
}
|
|
92
|
+
function ocuseArgs(arg) {
|
|
93
|
+
return arg !== undefined && arg.trim() !== "" ? arg : "-1";
|
|
94
|
+
}
|
|
80
95
|
function stderrTail(text) {
|
|
81
96
|
const lines = text.trimEnd().split(`
|
|
82
97
|
`);
|
|
@@ -90,38 +105,55 @@ function runOnce(bin, args, opts, name = "cmduse") {
|
|
|
90
105
|
...process.env,
|
|
91
106
|
...opts.env ?? {}
|
|
92
107
|
},
|
|
93
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
108
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
109
|
+
...opts.spawnOptions
|
|
94
110
|
});
|
|
95
111
|
const out = [];
|
|
96
112
|
const err = [];
|
|
113
|
+
let settled = false;
|
|
114
|
+
const timer = opts.timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
115
|
+
if (settled)
|
|
116
|
+
return;
|
|
117
|
+
settled = true;
|
|
118
|
+
child.kill();
|
|
119
|
+
reject(new Error(`${name} ${args.join(" ")}: timed out after ${opts.timeoutMs}ms`));
|
|
120
|
+
}, opts.timeoutMs);
|
|
97
121
|
const onAbort = () => child.kill("SIGTERM");
|
|
98
122
|
opts.signal?.addEventListener("abort", onAbort, {
|
|
99
123
|
once: true
|
|
100
124
|
});
|
|
125
|
+
const settle = (fn) => {
|
|
126
|
+
if (settled)
|
|
127
|
+
return;
|
|
128
|
+
settled = true;
|
|
129
|
+
if (timer)
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
132
|
+
fn();
|
|
133
|
+
};
|
|
101
134
|
child.stdout.on("data", (d) => out.push(d));
|
|
102
135
|
child.stderr.on("data", (d) => err.push(d));
|
|
103
|
-
child.on("error", (e) =>
|
|
104
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
105
|
-
reject(e);
|
|
106
|
-
});
|
|
136
|
+
child.on("error", (e) => settle(() => reject(e)));
|
|
107
137
|
child.on("close", (code, signal) => {
|
|
108
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
109
138
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
110
139
|
if (opts.signal?.aborted) {
|
|
111
|
-
reject(new Error(`${name} invocation aborted`));
|
|
140
|
+
settle(() => reject(new Error(`${name} invocation aborted`)));
|
|
112
141
|
} else if (code === 0) {
|
|
113
|
-
resolve(stdout);
|
|
142
|
+
settle(() => resolve(stdout));
|
|
114
143
|
} else {
|
|
115
144
|
const why = signal ? `killed by ${signal}` : `exit ${code}`;
|
|
116
145
|
const tail = stderrTail(Buffer.concat(err).toString("utf8"));
|
|
117
|
-
reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
118
|
-
${tail}` : ""}`));
|
|
146
|
+
settle(() => reject(new Error(`${name} ${args.join(" ")}: ${why}${tail ? `
|
|
147
|
+
${tail}` : ""}`)));
|
|
119
148
|
}
|
|
120
149
|
});
|
|
121
150
|
});
|
|
122
151
|
}
|
|
152
|
+
function resolveArgs(arg, build) {
|
|
153
|
+
return typeof arg === "string" ? build(arg) : [...arg];
|
|
154
|
+
}
|
|
123
155
|
async function runCmduse(arg, opts = {}) {
|
|
124
|
-
const args =
|
|
156
|
+
const args = resolveArgs(arg, buildCmduseArgs);
|
|
125
157
|
const candidates = opts.candidates ?? CANDIDATES;
|
|
126
158
|
let last;
|
|
127
159
|
for (const bin of candidates) {
|
|
@@ -137,6 +169,40 @@ async function runCmduse(arg, opts = {}) {
|
|
|
137
169
|
cause: last
|
|
138
170
|
});
|
|
139
171
|
}
|
|
172
|
+
async function runMpc(arg, opts = {}) {
|
|
173
|
+
const args = resolveArgs(arg, buildMpcArgs);
|
|
174
|
+
const candidates = opts.candidates ?? MPC_CANDIDATES;
|
|
175
|
+
let last;
|
|
176
|
+
for (const bin of candidates) {
|
|
177
|
+
try {
|
|
178
|
+
return await runOnce(bin, args, opts, "mpc");
|
|
179
|
+
} catch (e) {
|
|
180
|
+
last = e;
|
|
181
|
+
if (e?.code !== "ENOENT")
|
|
182
|
+
throw e;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`mpc binary not found (install: bun add -g @jeffreyjyz/mpc)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, {
|
|
186
|
+
cause: last
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function runOcuse(arg, opts = {}) {
|
|
190
|
+
const args = resolveArgs(arg, splitCliArgs);
|
|
191
|
+
const candidates = opts.candidates ?? OCUSE_CANDIDATES;
|
|
192
|
+
let last;
|
|
193
|
+
for (const bin of candidates) {
|
|
194
|
+
try {
|
|
195
|
+
return await runOnce(bin, args, opts, "ocuse");
|
|
196
|
+
} catch (e) {
|
|
197
|
+
last = e;
|
|
198
|
+
if (e?.code !== "ENOENT")
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`ocuse binary not found (install: brew install JeffreyJYZ/tap/cmduse)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, {
|
|
203
|
+
cause: last
|
|
204
|
+
});
|
|
205
|
+
}
|
|
140
206
|
|
|
141
207
|
// src/constants/providers.ts
|
|
142
208
|
var PROVIDER_ID_PREFIX = "command-code";
|
|
@@ -144,6 +210,8 @@ var GO_PROVIDER_ID = "opencode-go";
|
|
|
144
210
|
var ZEN_PROVIDER_ID = "opencode";
|
|
145
211
|
var ZEN_PROVIDER_PREFIX = "opencode-zen";
|
|
146
212
|
var TUI_ID = "command-code.tui";
|
|
213
|
+
var CMD_USAGE_COMMAND_ID = "command-code.cmd-usage";
|
|
214
|
+
var OCUSE_USAGE_COMMAND_ID = "command-code.ocuse-usage";
|
|
147
215
|
|
|
148
216
|
// src/sidebar/panel.tsx
|
|
149
217
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
@@ -621,18 +689,19 @@ function until(epoch, now = Date.now()) {
|
|
|
621
689
|
const days = Math.floor(hours / 24);
|
|
622
690
|
return hours % 24 ? `${days}d ${hours % 24}h` : `${days}d`;
|
|
623
691
|
}
|
|
624
|
-
function windowRows(label, w, now, durSecs, extra) {
|
|
692
|
+
function windowRows(label, w, now, durSecs, extra, use) {
|
|
625
693
|
if (!w || typeof w.cap !== "number" || w.cap <= 0)
|
|
626
694
|
return [];
|
|
627
695
|
const used = typeof w.used === "number" ? w.used : 0;
|
|
628
696
|
const pct = used / w.cap * 100;
|
|
697
|
+
const share = typeof w.sharePct === "number" ? w.sharePct : pct;
|
|
629
698
|
const nowSecs = Math.floor(now / 1000);
|
|
630
699
|
const elapsed = elapsedLabel(w.resetAt, durSecs, nowSecs);
|
|
631
700
|
const eta = paceEtaSecs(w.resetAt, durSecs, used, w.cap, nowSecs);
|
|
632
701
|
const reset = until(w.resetAt, now);
|
|
633
702
|
const lead = w.exceeded ? "LIMIT EXCEEDED" : eta === undefined ? elapsed === undefined ? "" : `${elapsed} elapsed` : `cap in ${until(now + eta * 1000, now)}`;
|
|
634
703
|
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(
|
|
704
|
+
return [[label, use ? use(used, w.cap, share) : `${money(used)}/${money(w.cap)} (${pct.toFixed(1)}%)`, w.exceeded ? "crit" : pctTone(share)], ...detail ? [["", detail, "muted"]] : [], ...extra ? [["", extra, "muted"]] : []];
|
|
636
705
|
}
|
|
637
706
|
function localUsageRows(usage, session) {
|
|
638
707
|
const rows = [];
|
|
@@ -2027,7 +2096,6 @@ function minPlan(id) {
|
|
|
2027
2096
|
}
|
|
2028
2097
|
|
|
2029
2098
|
// src/sidebar/data.ts
|
|
2030
|
-
import { spawn as spawn2 } from "child_process";
|
|
2031
2099
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2032
2100
|
import { homedir as homedir3 } from "os";
|
|
2033
2101
|
import { dirname, join as join3 } from "path";
|
|
@@ -2042,6 +2110,14 @@ function totals(value) {
|
|
|
2042
2110
|
tokens: num("tokens")
|
|
2043
2111
|
};
|
|
2044
2112
|
}
|
|
2113
|
+
function meter(value) {
|
|
2114
|
+
const v = value ?? {};
|
|
2115
|
+
return {
|
|
2116
|
+
status: typeof v.status === "string" ? v.status : "ok",
|
|
2117
|
+
percent: typeof v.percent === "number" ? v.percent : 0,
|
|
2118
|
+
resetsAt: typeof v.resetsAt === "number" ? v.resetsAt : undefined
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2045
2121
|
function parseZenJson(text) {
|
|
2046
2122
|
let body;
|
|
2047
2123
|
try {
|
|
@@ -2067,6 +2143,12 @@ function parseZenJson(text) {
|
|
|
2067
2143
|
periodStart: typeof body.periodStartAt === "number" ? body.periodStartAt : typeof body.periodStart === "number" ? body.periodStart : undefined,
|
|
2068
2144
|
periodEndAt: typeof body.periodEndAt === "number" ? body.periodEndAt : undefined,
|
|
2069
2145
|
source: typeof body.source === "string" ? body.source : undefined,
|
|
2146
|
+
account: body.account ? {
|
|
2147
|
+
rolling: meter(body.account.rolling),
|
|
2148
|
+
weekly: meter(body.account.weekly),
|
|
2149
|
+
monthly: meter(body.account.monthly)
|
|
2150
|
+
} : undefined,
|
|
2151
|
+
accountError: typeof body.accountError === "string" && body.accountError ? body.accountError : undefined,
|
|
2070
2152
|
totals: {
|
|
2071
2153
|
fiveHour: totals(top.fiveHour),
|
|
2072
2154
|
weekly: totals(top.weekly),
|
|
@@ -2107,19 +2189,41 @@ function zenWindow(label, spent, limit, share, now, durSecs, resetAt) {
|
|
|
2107
2189
|
exceeded: spent.cost >= cap
|
|
2108
2190
|
}, now, durSecs, spent.requests > 0 ? `${count(spent.requests)} req` : undefined);
|
|
2109
2191
|
}
|
|
2192
|
+
function zenMeterRows(label, meter, now, durSecs, local) {
|
|
2193
|
+
return windowRows(label, {
|
|
2194
|
+
cap: local?.cap ?? 100,
|
|
2195
|
+
used: local?.spent ?? meter.percent,
|
|
2196
|
+
resetAt: meter.resetsAt,
|
|
2197
|
+
sharePct: meter.percent,
|
|
2198
|
+
exceeded: meter.percent >= 100 || meter.status !== "ok"
|
|
2199
|
+
}, now, durSecs, undefined, (used, cap, share) => local ? `${money(used)}/${money(cap)} (${share.toFixed(1)}% of limit)` : `${share.toFixed(1)}% of limit`);
|
|
2200
|
+
}
|
|
2110
2201
|
function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
|
|
2111
2202
|
if (!usage)
|
|
2112
2203
|
return [];
|
|
2113
2204
|
const now = Date.now();
|
|
2114
2205
|
const oc = meta?.oc;
|
|
2115
2206
|
const rows = [["Plan", `${oc?.plan ?? "Go"} \xB7 limits per model`, "base"]];
|
|
2207
|
+
if (usage.accountError) {
|
|
2208
|
+
rows.push(["", clip(`usage API: ${usage.accountError}`, valueWidth("")), "warn"]);
|
|
2209
|
+
}
|
|
2116
2210
|
const mine = usage.models.get(modelKey(fallbackName ?? "")) ?? (meta ? usage.models.get(modelKey(meta.name)) : undefined);
|
|
2117
2211
|
const limit = oc?.allowance ?? mine?.limit;
|
|
2118
2212
|
const cap = mine ? limit : undefined;
|
|
2119
|
-
|
|
2120
|
-
|
|
2213
|
+
const windowMoney = (spent, share) => mine && typeof limit === "number" && limit > 0 ? {
|
|
2214
|
+
spent: spent.cost,
|
|
2215
|
+
cap: limit * share
|
|
2216
|
+
} : undefined;
|
|
2121
2217
|
const period = monthPeriod(usage);
|
|
2122
|
-
|
|
2218
|
+
if (usage.account) {
|
|
2219
|
+
rows.push(...zenMeterRows("5-hour", usage.account.rolling, now, FIVE_HOUR_SECS, mine ? windowMoney(mine.fiveHour, GO_WINDOW_SHARE.fiveHour) : undefined));
|
|
2220
|
+
rows.push(...zenMeterRows("Weekly", usage.account.weekly, now, WEEKLY_SECS, mine ? windowMoney(mine.weekly, GO_WINDOW_SHARE.weekly) : undefined));
|
|
2221
|
+
rows.push(...zenMeterRows("Monthly", usage.account.monthly, now, period?.durSecs ?? 0, mine ? windowMoney(mine.month, GO_WINDOW_SHARE.month) : undefined));
|
|
2222
|
+
} else {
|
|
2223
|
+
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)));
|
|
2224
|
+
rows.push(...zenWindow("Weekly", mine?.weekly ?? usage.totals.weekly, cap, GO_WINDOW_SHARE.weekly, now, WEEKLY_SECS, rollingReset(windows?.weekly, WEEKLY_SECS)));
|
|
2225
|
+
rows.push(...zenWindow("Monthly", mine?.month ?? usage.totals.month, cap, GO_WINDOW_SHARE.month, now, period?.durSecs ?? 0, period?.resetAt));
|
|
2226
|
+
}
|
|
2123
2227
|
rows.push(["Period", `${count(usage.totals.month.requests)} requests \xB7 ${money(usage.totals.month.cost)}`, "base"]);
|
|
2124
2228
|
rows.push(separator());
|
|
2125
2229
|
const name = meta?.name ?? fallbackName;
|
|
@@ -2149,48 +2253,10 @@ function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
|
|
|
2149
2253
|
}
|
|
2150
2254
|
|
|
2151
2255
|
// 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
|
-
}
|
|
2256
|
+
var SIDEBAR_OPTS = {
|
|
2257
|
+
spawnOptions: SPAWN_OPTIONS,
|
|
2258
|
+
timeoutMs: CHILD_TIMEOUT_MS
|
|
2259
|
+
};
|
|
2194
2260
|
function parseUsageJson(text) {
|
|
2195
2261
|
const raw = JSON.parse(text);
|
|
2196
2262
|
const win = (v) => {
|
|
@@ -2276,7 +2342,7 @@ async function loadMeta(plan = OC_PLAN_DEFAULT) {
|
|
|
2276
2342
|
return new Map(cached.rows ?? []);
|
|
2277
2343
|
}
|
|
2278
2344
|
} catch {}
|
|
2279
|
-
const meta = parseMpcJson(await
|
|
2345
|
+
const meta = parseMpcJson(await runMpc(metaArgs(plan), SIDEBAR_OPTS));
|
|
2280
2346
|
try {
|
|
2281
2347
|
const path = cachePath(plan);
|
|
2282
2348
|
await mkdir(dirname(path), {
|
|
@@ -2290,10 +2356,10 @@ async function loadMeta(plan = OC_PLAN_DEFAULT) {
|
|
|
2290
2356
|
return meta;
|
|
2291
2357
|
}
|
|
2292
2358
|
async function loadUsage() {
|
|
2293
|
-
return requireSnapshot(parseUsageJson(await
|
|
2359
|
+
return requireSnapshot(parseUsageJson(await runCmduse(["-1", "--json", "--plain"], SIDEBAR_OPTS)));
|
|
2294
2360
|
}
|
|
2295
2361
|
async function loadZen(plan = OC_PLAN_DEFAULT) {
|
|
2296
|
-
return parseZenJson(await
|
|
2362
|
+
return parseZenJson(await runOcuse(zenArgs(plan), SIDEBAR_OPTS));
|
|
2297
2363
|
}
|
|
2298
2364
|
|
|
2299
2365
|
// src/sidebar/usageDb.ts
|
|
@@ -2697,7 +2763,7 @@ var commandCodeTui = {
|
|
|
2697
2763
|
mode: "global",
|
|
2698
2764
|
priority: 10,
|
|
2699
2765
|
commands: [{
|
|
2700
|
-
id:
|
|
2766
|
+
id: CMD_USAGE_COMMAND_ID,
|
|
2701
2767
|
title: "Command Code usage",
|
|
2702
2768
|
group: "Command Code",
|
|
2703
2769
|
slash: {
|
|
@@ -2723,8 +2789,35 @@ var commandCodeTui = {
|
|
|
2723
2789
|
message: text.trimEnd()
|
|
2724
2790
|
});
|
|
2725
2791
|
}
|
|
2792
|
+
}, {
|
|
2793
|
+
id: OCUSE_USAGE_COMMAND_ID,
|
|
2794
|
+
title: "OpenCode Go/Zen usage",
|
|
2795
|
+
group: "Command Code",
|
|
2796
|
+
slash: {
|
|
2797
|
+
name: "ocuse-usage",
|
|
2798
|
+
arguments: true
|
|
2799
|
+
},
|
|
2800
|
+
enabled: () => true,
|
|
2801
|
+
suggested: true,
|
|
2802
|
+
run: async (input) => {
|
|
2803
|
+
let text;
|
|
2804
|
+
try {
|
|
2805
|
+
text = await runOcuse(ocuseArgs(input));
|
|
2806
|
+
} catch (error) {
|
|
2807
|
+
ctx.ui.toast.show({
|
|
2808
|
+
title: "ocuse-usage failed",
|
|
2809
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2810
|
+
variant: "error"
|
|
2811
|
+
});
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
await ctx.ui.dialog.alert({
|
|
2815
|
+
title: "OpenCode Go/Zen usage",
|
|
2816
|
+
message: text.trimEnd()
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2726
2819
|
}],
|
|
2727
|
-
bindings: [
|
|
2820
|
+
bindings: [CMD_USAGE_COMMAND_ID, OCUSE_USAGE_COMMAND_ID]
|
|
2728
2821
|
}));
|
|
2729
2822
|
}
|
|
2730
2823
|
});
|
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.7",
|
|
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",
|