@jeffreyjyz/opencode-command-code 0.5.16 → 0.5.19

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 CHANGED
@@ -10,6 +10,9 @@ export declare function mpcCandidates(env?: Record<string, string | undefined>):
10
10
  /** Same lookup for ocuse, the OpenCode Go/Zen reader shipped in the cmd-usage
11
11
  * crate: `OCUSE_BIN` wins when set, then the homebrew fallbacks. */
12
12
  export declare function ocuseCandidates(env?: Record<string, string | undefined>): string[];
13
+ /** Same lookup for arkuse, the Volcengine ARK Agent Plan reader in the same
14
+ * crate: `ARKUSE_BIN` wins when set, then the homebrew fallbacks. */
15
+ export declare function arkuseCandidates(env?: Record<string, string | undefined>): string[];
13
16
  /** Shell-words split: whitespace-separated, with single/double quoting and
14
17
  * backslash escapes ("--tz +05:30", arg='a b'). Unterminated quotes close at
15
18
  * end of input. */
@@ -28,6 +31,10 @@ export declare function buildMpcArgs(arg: string): string[];
28
31
  * watch loop and would never return, so empty input never passes through.
29
32
  */
30
33
  export declare function ocuseArgs(arg: string | undefined): string;
34
+ /** The arg the arkuse_usage tool and the /arkuse-usage dialog pass on: the
35
+ * agent's (or slash) args when given, else `-1`. A bare arkuse is the live
36
+ * watch loop and would never return. */
37
+ export declare function arkuseArgs(arg: string | undefined): string;
31
38
  export type RunOptions = {
32
39
  signal?: AbortSignal;
33
40
  /** Extra env for the child (CMD_API_KEY when the plugin resolved one). */
@@ -58,3 +65,7 @@ export declare function runMpc(arg: string | readonly string[], opts?: RunOption
58
65
  * hint is the same brew formula. A string arg is shell-split, an array used
59
66
  * as-is. */
60
67
  export declare function runOcuse(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
68
+ /** Run arkuse and return its plain-text stdout (Volcengine ARK usage). Same
69
+ * contract as runCmduse; arkuse ships in the cmd-usage crate, so its install
70
+ * hint is the same brew formula. */
71
+ export declare function runArkuse(arg: string | readonly string[], opts?: RunOptions): Promise<string>;
@@ -4,3 +4,5 @@ export declare const CMDUSE_BIN_CANDIDATES: readonly string[];
4
4
  export declare const MPC_BIN_CANDIDATES: readonly string[];
5
5
  /** Fallback lookup paths for ocuse (OCUSE_BIN wins when set). */
6
6
  export declare const OCUSE_BIN_CANDIDATES: readonly string[];
7
+ /** Fallback lookup paths for arkuse (ARKUSE_BIN wins when set). */
8
+ export declare const ARKUSE_BIN_CANDIDATES: readonly string[];
@@ -11,12 +11,16 @@ export declare const GO_PROVIDER_ID = "opencode-go";
11
11
  /** OpenCode Zen provider id and its `opencode-zen` alias prefix. */
12
12
  export declare const ZEN_PROVIDER_ID = "opencode";
13
13
  export declare const ZEN_PROVIDER_PREFIX = "opencode-zen";
14
+ /** Volcengine ARK Agent Plan provider id (what opencode records for its rows). */
15
+ export declare const ARK_PROVIDER_ID = "agent-plan";
14
16
  /** TUI plugin id (stable contract; test/tui.test.ts pins it). */
15
17
  export declare const TUI_ID = "command-code.tui";
16
18
  /** The v2 /cmd-usage slash command id (registered and bound in tui.tsx). */
17
19
  export declare const CMD_USAGE_COMMAND_ID = "command-code.cmd-usage";
18
20
  /** The v2 /ocuse-usage slash command id (registered and bound in tui.tsx). */
19
21
  export declare const OCUSE_USAGE_COMMAND_ID = "command-code.ocuse-usage";
22
+ /** The v2 /arkuse-usage slash command id (registered and bound in tui.tsx). */
23
+ export declare const ARKUSE_USAGE_COMMAND_ID = "command-code.arkuse-usage";
20
24
  /** The cmd_usage tool description, shared by the v1 and v2 halves. */
21
25
  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').";
22
26
  /** The mpc_prices tool description, shared by the v1 and v2 halves. */
@@ -27,6 +31,10 @@ export declare const MPC_TOOL_ARG_DESCRIPTION = "Optional mpc flags: '--model <r
27
31
  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
32
  /** The ocuse_usage `arg` description, shared by the v1 and v2 halves. */
29
33
  export declare const OCUSE_TOOL_ARG_DESCRIPTION = "Optional: extra ocuse flags (e.g. '--plan go-plus', 'daily', '--json'); empty means the one-shot dashboard";
34
+ /** The arkuse_usage tool description, shared by the v1 and v2 halves. */
35
+ export declare const ARKUSE_TOOL_DESCRIPTION = "Fetch live Volcengine ARK Agent Plan usage in AFP: the plan's 5-hour/weekly/monthly meters from arkcli, this machine's per-model AFP, and the reconciliation between them. Defaults to the one-shot dashboard; pass extra arkuse flags for more (e.g. 'daily', '--plan medium', '--json').";
36
+ /** The arkuse_usage `arg` description, shared by the v1 and v2 halves. */
37
+ export declare const ARKUSE_TOOL_ARG_DESCRIPTION = "Optional: extra arkuse flags (e.g. '--plan medium', '--profile <name>', 'daily', '--json'); empty means the one-shot dashboard";
30
38
  export type Lane = {
31
39
  id: "command-code-anthropic" | "command-code-openai";
32
40
  name: string;
@@ -11,3 +11,8 @@ export declare const MODELS_CACHE_TTL_MS: number;
11
11
  export declare const CATALOG_CACHE_TTL_MS: number;
12
12
  /** Re-read window for the sidebar colour prefs file (src/sidebar/prefs.ts). */
13
13
  export declare const PREFS_CACHE_MS = 5000;
14
+ /** Process-lifetime cache for the ARK sidebar snapshot: every `arkuse` run
15
+ * shells out to `arkcli` for the plan meters, so polling it at the 5s cadence
16
+ * would be one control-plane round-trip per tick. A minute keeps the meters
17
+ * fresh enough while the store-backed rows still update on the poll. */
18
+ export declare const ARK_SNAPSHOT_TTL_MS = 60000;
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.16",
2417
+ version: "0.5.19",
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",
@@ -3242,6 +3242,11 @@ var OCUSE_BIN_CANDIDATES = [
3242
3242
  "/opt/homebrew/bin/ocuse",
3243
3243
  "/usr/local/bin/ocuse"
3244
3244
  ];
3245
+ var ARKUSE_BIN_CANDIDATES = [
3246
+ "arkuse",
3247
+ "/opt/homebrew/bin/arkuse",
3248
+ "/usr/local/bin/arkuse"
3249
+ ];
3245
3250
 
3246
3251
  // src/constants/cli.ts
3247
3252
  var DEFAULT_ARGS = ["-1", "--plain"];
@@ -3273,6 +3278,11 @@ function ocuseCandidates(env = process.env) {
3273
3278
  return candidates.filter((bin) => Boolean(bin));
3274
3279
  }
3275
3280
  var OCUSE_CANDIDATES = ocuseCandidates();
3281
+ function arkuseCandidates(env = process.env) {
3282
+ const candidates = [env.ARKUSE_BIN, ...ARKUSE_BIN_CANDIDATES];
3283
+ return candidates.filter((bin) => Boolean(bin));
3284
+ }
3285
+ var ARKUSE_CANDIDATES = arkuseCandidates();
3276
3286
  function splitCliArgs(input) {
3277
3287
  const out = [];
3278
3288
  let cur = "";
@@ -3328,6 +3338,9 @@ function buildMpcArgs(arg) {
3328
3338
  function ocuseArgs(arg) {
3329
3339
  return arg !== undefined && arg.trim() !== "" ? arg : "-1";
3330
3340
  }
3341
+ function arkuseArgs(arg) {
3342
+ return arg !== undefined && arg.trim() !== "" ? arg : "-1";
3343
+ }
3331
3344
  function stderrTail(text) {
3332
3345
  const lines = text.trimEnd().split(`
3333
3346
  `);
@@ -3428,6 +3441,21 @@ async function runOcuse(arg, opts = {}) {
3428
3441
  }
3429
3442
  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 });
3430
3443
  }
3444
+ async function runArkuse(arg, opts = {}) {
3445
+ const args = resolveArgs(arg, splitCliArgs);
3446
+ const candidates = opts.candidates ?? ARKUSE_CANDIDATES;
3447
+ let last;
3448
+ for (const bin of candidates) {
3449
+ try {
3450
+ return await runOnce(bin, args, opts, "arkuse");
3451
+ } catch (e) {
3452
+ last = e;
3453
+ if (e?.code !== "ENOENT")
3454
+ throw e;
3455
+ }
3456
+ }
3457
+ throw new Error(`arkuse binary not found (install: brew install JeffreyJYZ/tap/cmduse)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, { cause: last });
3458
+ }
3431
3459
 
3432
3460
  // src/constants/models.ts
3433
3461
  var MODEL_CAPABILITIES = {
@@ -3458,6 +3486,8 @@ var MPC_TOOL_DESCRIPTION = "Per-model price comparison: what a model costs per r
3458
3486
  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";
3459
3487
  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').";
3460
3488
  var OCUSE_TOOL_ARG_DESCRIPTION = "Optional: extra ocuse flags (e.g. '--plan go-plus', 'daily', '--json'); empty means the one-shot dashboard";
3489
+ var ARKUSE_TOOL_DESCRIPTION = "Fetch live Volcengine ARK Agent Plan usage in AFP: the plan's 5-hour/weekly/monthly meters from arkcli, this machine's per-model AFP, and the reconciliation between them. Defaults to the one-shot dashboard; pass extra arkuse flags for more (e.g. 'daily', '--plan medium', '--json').";
3490
+ var ARKUSE_TOOL_ARG_DESCRIPTION = "Optional: extra arkuse flags (e.g. '--plan medium', '--profile <name>', 'daily', '--json'); empty means the one-shot dashboard";
3461
3491
  var LANES = [
3462
3492
  {
3463
3493
  id: "command-code-anthropic",
@@ -5015,6 +5045,26 @@ var commandCodeV2 = {
5015
5045
  };
5016
5046
  }
5017
5047
  });
5048
+ editor.add({
5049
+ name: "arkuse_usage",
5050
+ description: ARKUSE_TOOL_DESCRIPTION,
5051
+ input: {
5052
+ type: "object",
5053
+ properties: {
5054
+ arg: {
5055
+ type: "string",
5056
+ description: ARKUSE_TOOL_ARG_DESCRIPTION
5057
+ }
5058
+ },
5059
+ additionalProperties: false
5060
+ },
5061
+ async execute(input) {
5062
+ const arg = typeof input?.arg === "string" ? input.arg : undefined;
5063
+ return {
5064
+ content: await runArkuse(arkuseArgs(arg))
5065
+ };
5066
+ }
5067
+ });
5018
5068
  });
5019
5069
  let hasConnection = false;
5020
5070
  let connectionMs = 0;
@@ -5231,6 +5281,10 @@ var CommandCodePlugin = async (_input) => {
5231
5281
  description: "Show OpenCode Go/Zen usage: account meters and per-model spend",
5232
5282
  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"
5233
5283
  };
5284
+ cfg.command["arkuse-usage"] = {
5285
+ description: "Show Volcengine ARK Agent Plan usage: AFP meters per model",
5286
+ template: "Call the arkuse_usage tool and present its markdown output verbatim to the user. If the tool errors, arkcli or its agent-plan profile is missing \u2014 usage still shows where the store has it. $ARGUMENTS"
5287
+ };
5234
5288
  },
5235
5289
  provider: {
5236
5290
  id: ANTHROPIC.id,
@@ -5276,6 +5330,15 @@ var CommandCodePlugin = async (_input) => {
5276
5330
  async execute(args) {
5277
5331
  return runOcuse(ocuseArgs(args.arg));
5278
5332
  }
5333
+ }),
5334
+ arkuse_usage: tool({
5335
+ description: ARKUSE_TOOL_DESCRIPTION,
5336
+ args: {
5337
+ arg: string2().optional().describe(ARKUSE_TOOL_ARG_DESCRIPTION)
5338
+ },
5339
+ async execute(args) {
5340
+ return runArkuse(arkuseArgs(args.arg));
5341
+ }
5279
5342
  })
5280
5343
  }
5281
5344
  };
@@ -0,0 +1,72 @@
1
+ import { type SidebarRow } from "./rows";
2
+ export interface ArkTotals {
3
+ afp: number;
4
+ requests: number;
5
+ tokensIn: number;
6
+ tokensOut: number;
7
+ cacheRead: number;
8
+ }
9
+ /** One of the plan's own meters (`arkuse`'s `account` block). Every field is
10
+ * optional-ish by parsing: a malformed meter must not raise a cap flag. */
11
+ export interface ArkMeter {
12
+ status: string;
13
+ percent: number;
14
+ used: number;
15
+ total: number;
16
+ resetAt?: number;
17
+ }
18
+ export interface ArkModel {
19
+ id: string;
20
+ coef: number;
21
+ fiveHour: ArkTotals;
22
+ weekly: ArkTotals;
23
+ month: ArkTotals;
24
+ }
25
+ /** This store's own view of one window (`arkuse`'s `windows` block): AFP against
26
+ * the plan's published quota, the percentage that yields, and the boundary the
27
+ * row counts down to. Present even when no meter answered, which is what keeps
28
+ * a panel row from degrading to AFP alone. */
29
+ export interface ArkWindow {
30
+ quota?: number;
31
+ percent?: number;
32
+ resetAt?: number;
33
+ }
34
+ export interface ArkUsage {
35
+ plan: string;
36
+ profile?: string;
37
+ periodStart?: number;
38
+ periodEndAt?: number;
39
+ summary: ArkTotals & {
40
+ yuan?: number;
41
+ };
42
+ fiveHour: ArkTotals;
43
+ weekly: ArkTotals;
44
+ month: ArkTotals;
45
+ daily?: ArkTotals;
46
+ windows?: {
47
+ fiveHour?: ArkWindow;
48
+ weekly?: ArkWindow;
49
+ monthly?: ArkWindow;
50
+ };
51
+ account?: {
52
+ fiveHour: ArkMeter;
53
+ daily?: ArkMeter;
54
+ weekly: ArkMeter;
55
+ monthly: ArkMeter;
56
+ };
57
+ accountError?: string;
58
+ afpPerRequest?: number;
59
+ reconciledPct?: number;
60
+ /** Keyed by model id, the same key the session's model id uses. */
61
+ models: Map<string, ArkModel>;
62
+ }
63
+ /** Parse `arkuse -1 --json`. A malformed body reads as an empty snapshot, never
64
+ * as a fabricated plan. */
65
+ export declare function parseArkJson(text: string): ArkUsage;
66
+ /**
67
+ * The ARK panel: plan, the window meters, a rule, then the active model's own
68
+ * AFP for the period. `fallbackName` is the session's model id, which is the
69
+ * key `arkuse` uses, so the model block renders even when nothing else knows the
70
+ * name.
71
+ */
72
+ export declare function arkRows(usage: ArkUsage | undefined, fallbackName?: string): SidebarRow[];
@@ -1,4 +1,5 @@
1
1
  import { type OcPlan } from "../constants/sidebar";
2
+ import { type ArkUsage } from "./ark";
2
3
  import { type ModelMeta, type Usage } from "./rows";
3
4
  import { type ZenUsage } from "./zen";
4
5
  /** Parse `cmduse -1 --json` into the usage snapshot. */
@@ -39,3 +40,10 @@ export declare function loadUsage(): Promise<Usage>;
39
40
  * caller then omits the panel.
40
41
  */
41
42
  export declare function loadZen(plan?: OcPlan): Promise<ZenUsage>;
43
+ /**
44
+ * One Volcengine ARK snapshot from `arkuse -1 --json`: the plan's own meters
45
+ * (through `arkcli`) plus this store's AFP per model. Cached for a minute
46
+ * because the spawn costs a control-plane round-trip; throws when `arkuse` is
47
+ * missing or fails, and the caller then keeps the last snapshot.
48
+ */
49
+ export declare function loadArk(): Promise<ArkUsage>;
@@ -1,3 +1,4 @@
1
+ import { type ArkUsage } from "./ark";
1
2
  import { type ModelMeta, type ModelUsage, type SidebarRow, type Usage } from "./rows";
2
3
  import { type ZenUsage } from "./zen";
3
4
  /**
@@ -6,7 +7,7 @@ import { type ZenUsage } from "./zen";
6
7
  * resolved the new session's model; it must not be read as someone else's, or
7
8
  * the panel blanks and refetches on every tab change.
8
9
  */
9
- export type SessionKind = "ours" | "go" | "zen" | "other" | "unknown";
10
+ export type SessionKind = "ours" | "go" | "zen" | "ark" | "other" | "unknown";
10
11
  export declare function sessionKind(providerID: string | undefined): SessionKind;
11
12
  /** The section's title, per family. */
12
13
  export declare function panelTitle(kind: SessionKind): string;
@@ -40,4 +41,8 @@ export declare function seededMeta(): Map<string, ModelMeta>;
40
41
  export declare function rememberZen(usage: ZenUsage): void;
41
42
  /** The remembered Go/Zen snapshot, or undefined on the first panel. */
42
43
  export declare function seededZen(): ZenUsage | undefined;
44
+ /** Keep an ARK snapshot for the next panel that needs it. */
45
+ export declare function rememberArk(usage: ArkUsage): void;
46
+ /** The remembered ARK snapshot, or undefined on the first panel. */
47
+ export declare function seededArk(): ArkUsage | undefined;
43
48
  export declare function useRows(activeModelId: () => string | undefined, providerID: () => string | undefined, sessionID: () => string | undefined): import("solid-js").Accessor<SidebarRow[]>;
package/dist/tui.js CHANGED
@@ -11,6 +11,7 @@ import { join } from "path";
11
11
  var CMDUSE_BIN_CANDIDATES = ["cmduse", "/opt/homebrew/bin/cmduse", "/usr/local/bin/cmduse"];
12
12
  var MPC_BIN_CANDIDATES = ["mpc", join(homedir(), ".bun/bin/mpc"), "/opt/homebrew/bin/mpc"];
13
13
  var OCUSE_BIN_CANDIDATES = ["ocuse", "/opt/homebrew/bin/ocuse", "/usr/local/bin/ocuse"];
14
+ var ARKUSE_BIN_CANDIDATES = ["arkuse", "/opt/homebrew/bin/arkuse", "/usr/local/bin/arkuse"];
14
15
 
15
16
  // src/constants/cli.ts
16
17
  var DEFAULT_ARGS = ["-1", "--plain"];
@@ -37,6 +38,11 @@ function ocuseCandidates(env = process.env) {
37
38
  return candidates.filter((bin) => Boolean(bin));
38
39
  }
39
40
  var OCUSE_CANDIDATES = ocuseCandidates();
41
+ function arkuseCandidates(env = process.env) {
42
+ const candidates = [env.ARKUSE_BIN, ...ARKUSE_BIN_CANDIDATES];
43
+ return candidates.filter((bin) => Boolean(bin));
44
+ }
45
+ var ARKUSE_CANDIDATES = arkuseCandidates();
40
46
  function splitCliArgs(input) {
41
47
  const out = [];
42
48
  let cur = "";
@@ -92,6 +98,9 @@ function buildMpcArgs(arg) {
92
98
  function ocuseArgs(arg) {
93
99
  return arg !== undefined && arg.trim() !== "" ? arg : "-1";
94
100
  }
101
+ function arkuseArgs(arg) {
102
+ return arg !== undefined && arg.trim() !== "" ? arg : "-1";
103
+ }
95
104
  function stderrTail(text) {
96
105
  const lines = text.trimEnd().split(`
97
106
  `);
@@ -203,15 +212,34 @@ async function runOcuse(arg, opts = {}) {
203
212
  cause: last
204
213
  });
205
214
  }
215
+ async function runArkuse(arg, opts = {}) {
216
+ const args = resolveArgs(arg, splitCliArgs);
217
+ const candidates = opts.candidates ?? ARKUSE_CANDIDATES;
218
+ let last;
219
+ for (const bin of candidates) {
220
+ try {
221
+ return await runOnce(bin, args, opts, "arkuse");
222
+ } catch (e) {
223
+ last = e;
224
+ if (e?.code !== "ENOENT")
225
+ throw e;
226
+ }
227
+ }
228
+ throw new Error(`arkuse binary not found (install: brew install JeffreyJYZ/tap/cmduse)${last instanceof Error && last.message !== last.name ? ` \u2014 ${last.message}` : ""}`, {
229
+ cause: last
230
+ });
231
+ }
206
232
 
207
233
  // src/constants/providers.ts
208
234
  var PROVIDER_ID_PREFIX = "command-code";
209
235
  var GO_PROVIDER_ID = "opencode-go";
210
236
  var ZEN_PROVIDER_ID = "opencode";
211
237
  var ZEN_PROVIDER_PREFIX = "opencode-zen";
238
+ var ARK_PROVIDER_ID = "agent-plan";
212
239
  var TUI_ID = "command-code.tui";
213
240
  var CMD_USAGE_COMMAND_ID = "command-code.cmd-usage";
214
241
  var OCUSE_USAGE_COMMAND_ID = "command-code.ocuse-usage";
242
+ var ARKUSE_USAGE_COMMAND_ID = "command-code.arkuse-usage";
215
243
 
216
244
  // src/sidebar/panel.tsx
217
245
  import { createComponent as _$createComponent } from "@opentui/solid";
@@ -926,6 +954,7 @@ var CHILD_TIMEOUT_MS = 30000;
926
954
  var MODELS_CACHE_TTL_MS = 5 * 60 * 1000;
927
955
  var CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
928
956
  var PREFS_CACHE_MS = 5000;
957
+ var ARK_SNAPSHOT_TTL_MS = 60000;
929
958
 
930
959
  // src/sidebar/prefs.ts
931
960
  var cached;
@@ -2095,13 +2124,196 @@ function minPlan(id) {
2095
2124
  return MODEL_CATALOG[id]?.minPlan ?? null;
2096
2125
  }
2097
2126
 
2127
+ // src/sidebar/ark.ts
2128
+ function totals(value) {
2129
+ const v = value ?? {};
2130
+ const num = (key) => typeof v[key] === "number" ? v[key] : 0;
2131
+ return {
2132
+ afp: num("afp"),
2133
+ requests: num("requests"),
2134
+ tokensIn: num("tokensIn"),
2135
+ tokensOut: num("tokensOut"),
2136
+ cacheRead: num("cacheRead")
2137
+ };
2138
+ }
2139
+ function meter(value) {
2140
+ const v = value && typeof value === "object" ? value : {};
2141
+ return {
2142
+ status: typeof v.status === "string" ? v.status : "ok",
2143
+ percent: typeof v.percent === "number" ? v.percent : 0,
2144
+ used: typeof v.used === "number" ? v.used : 0,
2145
+ total: typeof v.total === "number" ? v.total : 0,
2146
+ resetAt: typeof v.resetAt === "number" ? v.resetAt : undefined
2147
+ };
2148
+ }
2149
+ function storeWindow(value) {
2150
+ if (!value || typeof value !== "object")
2151
+ return;
2152
+ const v = value;
2153
+ return {
2154
+ quota: typeof v.quota === "number" ? v.quota : undefined,
2155
+ percent: typeof v.percent === "number" ? v.percent : undefined,
2156
+ resetAt: typeof v.resetAt === "number" ? v.resetAt : undefined
2157
+ };
2158
+ }
2159
+ function parseArkJson(text) {
2160
+ let body;
2161
+ try {
2162
+ body = JSON.parse(text);
2163
+ } catch {
2164
+ body = {};
2165
+ }
2166
+ const account = body.account ?? {};
2167
+ const models = new Map;
2168
+ const rawModels = body.models ?? {};
2169
+ for (const [id, raw] of Object.entries(rawModels)) {
2170
+ const m = raw ?? {};
2171
+ models.set(id, {
2172
+ id,
2173
+ coef: typeof m.coef === "number" ? m.coef : 0,
2174
+ fiveHour: totals(m.fiveHour),
2175
+ weekly: totals(m.weekly),
2176
+ month: totals(m.month)
2177
+ });
2178
+ }
2179
+ const summary = body.summary ?? {};
2180
+ const windows = body.windows ?? {};
2181
+ return {
2182
+ plan: typeof body.plan === "string" ? body.plan : "medium",
2183
+ profile: typeof body.profile === "string" ? body.profile : undefined,
2184
+ periodStart: typeof body.periodStart === "number" ? body.periodStart : undefined,
2185
+ periodEndAt: typeof body.periodEndAt === "number" ? body.periodEndAt : undefined,
2186
+ summary: {
2187
+ ...totals(summary),
2188
+ yuan: typeof summary.yuan === "number" ? summary.yuan : undefined
2189
+ },
2190
+ fiveHour: totals(body.fiveHour),
2191
+ weekly: totals(body.weekly),
2192
+ month: totals(body.monthly),
2193
+ daily: body.daily ? totals(body.daily) : undefined,
2194
+ windows: body.windows ? {
2195
+ fiveHour: storeWindow(windows.fiveHour),
2196
+ weekly: storeWindow(windows.weekly),
2197
+ monthly: storeWindow(windows.monthly)
2198
+ } : undefined,
2199
+ account: body.account ? {
2200
+ fiveHour: meter(account.fiveHour),
2201
+ daily: account.daily ? meter(account.daily) : undefined,
2202
+ weekly: meter(account.weekly),
2203
+ monthly: meter(account.monthly)
2204
+ } : undefined,
2205
+ accountError: typeof body.accountError === "string" && body.accountError ? body.accountError : undefined,
2206
+ afpPerRequest: typeof body.afpPerRequest === "number" ? body.afpPerRequest : undefined,
2207
+ reconciledPct: typeof body.reconciledPct === "number" ? body.reconciledPct : undefined,
2208
+ models
2209
+ };
2210
+ }
2211
+ function monthPeriod(usage) {
2212
+ const {
2213
+ periodStart,
2214
+ periodEndAt
2215
+ } = usage;
2216
+ if (typeof periodStart !== "number" || typeof periodEndAt !== "number")
2217
+ return;
2218
+ const durSecs = Math.round((periodEndAt - periodStart) / 1000);
2219
+ if (durSecs <= 0)
2220
+ return;
2221
+ return {
2222
+ resetAt: periodEndAt,
2223
+ durSecs
2224
+ };
2225
+ }
2226
+ function afp(value) {
2227
+ return `${grouped(Math.round(value))} AFP`;
2228
+ }
2229
+ function grouped(value) {
2230
+ return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
2231
+ }
2232
+ function brief(value) {
2233
+ const rounded = Math.round(value);
2234
+ const trim = (n) => n.toFixed(1).replace(/\.0$/, "");
2235
+ if (rounded >= 1e6)
2236
+ return `${trim(rounded / 1e6)}M`;
2237
+ if (rounded >= 1000)
2238
+ return `${trim(rounded / 1000)}K`;
2239
+ return String(rounded);
2240
+ }
2241
+ function meterRows(label, meter, local, now, durSecs) {
2242
+ const pct = meter.percent;
2243
+ const pctText = Number.isInteger(pct) ? String(pct) : pct.toFixed(1);
2244
+ return windowRows(label, {
2245
+ cap: meter.total > 0 ? meter.total : undefined,
2246
+ used: meter.used,
2247
+ resetAt: meter.resetAt,
2248
+ sharePct: pct,
2249
+ exceeded: pct >= 100 || meter.status !== "ok"
2250
+ }, now, durSecs, `${afp(local.afp)} local${local.requests > 0 ? ` \xB7 ${count(local.requests)} req` : ""}`, (used, cap) => `${brief(used)}/${brief(cap)} (${pctText}%)`);
2251
+ }
2252
+ function localRows(label, totals, window, now, durSecs) {
2253
+ const quota = window?.quota;
2254
+ const percent = window?.percent;
2255
+ if (typeof quota !== "number" || quota <= 0 || typeof percent !== "number") {
2256
+ const detail = totals.requests > 0 ? ` \xB7 ${count(totals.requests)} req` : "";
2257
+ return [[label, `${afp(totals.afp)}${detail}`, "base"]];
2258
+ }
2259
+ const pctText = percent.toFixed(1);
2260
+ return windowRows(label, {
2261
+ cap: quota,
2262
+ used: totals.afp,
2263
+ resetAt: window?.resetAt,
2264
+ sharePct: percent,
2265
+ exceeded: percent >= 100
2266
+ }, now, durSecs, `${count(totals.requests)} req`, (used, cap) => `${brief(used)}/${brief(cap)} (${pctText}% est.)`);
2267
+ }
2268
+ function arkRows(usage, fallbackName) {
2269
+ if (!usage)
2270
+ return [];
2271
+ const now = Date.now();
2272
+ const rows = [["Plan", clip(`${usage.plan} \xB7 AFP plan${usage.profile ? ` \xB7 ${usage.profile}` : ""}`, valueWidth("Plan")), "base", true]];
2273
+ if (usage.accountError) {
2274
+ rows.push(["", clip(`usage API: ${usage.accountError}`, valueWidth("")), "warn"]);
2275
+ }
2276
+ const period = monthPeriod(usage);
2277
+ if (usage.account) {
2278
+ const account = usage.account;
2279
+ rows.push(...meterRows("5-hour", account.fiveHour, usage.fiveHour, now, FIVE_HOUR_SECS));
2280
+ if (account.daily) {
2281
+ rows.push(...meterRows("Daily", account.daily, usage.daily ?? usage.month, now, 86400));
2282
+ }
2283
+ rows.push(...meterRows("Weekly", account.weekly, usage.weekly, now, WEEKLY_SECS));
2284
+ rows.push(...meterRows("Monthly", account.monthly, usage.month, now, period?.durSecs ?? 0));
2285
+ } else {
2286
+ const windows = usage.windows;
2287
+ rows.push(...localRows("5-hour", usage.fiveHour, windows?.fiveHour, now, FIVE_HOUR_SECS));
2288
+ rows.push(...localRows("Weekly", usage.weekly, windows?.weekly, now, WEEKLY_SECS));
2289
+ rows.push(...localRows("Monthly", usage.month, windows?.monthly, now, period?.durSecs ?? 0));
2290
+ }
2291
+ const yuan = typeof usage.summary.yuan === "number" ? ` (${money(usage.summary.yuan)})` : "";
2292
+ rows.push(["Period", clip(`${count(usage.summary.requests)} requests \xB7 ${afp(usage.summary.afp)}${yuan}`, valueWidth("Period")), "base"]);
2293
+ if (typeof usage.afpPerRequest === "number" && usage.afpPerRequest > 0) {
2294
+ const drift = typeof usage.reconciledPct === "number" ? ` \xB7 ${usage.reconciledPct >= 0 ? "+" : ""}${usage.reconciledPct.toFixed(1)}% vs meter` : "";
2295
+ rows.push(["", clip(`${usage.afpPerRequest.toFixed(0)} AFP/request${drift}`, valueWidth("")), "muted"]);
2296
+ }
2297
+ rows.push(separator());
2298
+ if (!fallbackName)
2299
+ return rows;
2300
+ const model = usage.models.get(fallbackName);
2301
+ rows.push(["Model", clip(fallbackName, valueWidth("Model")), "strong", true]);
2302
+ rows.push(["AFP (this model)", model ? clip(`${afp(model.month.afp)} \xB7 ${count(model.month.requests)} req`, valueWidth("AFP (this model)")) : "no usage in this period", "base"]);
2303
+ if (model && usage.summary.afp > 0) {
2304
+ const share = model.month.afp / usage.summary.afp * 100;
2305
+ rows.push(["", clip(`${share.toFixed(1)}% of the period \xB7 coef ${model.coef.toFixed(2)}`, Math.min(ROW_WIDTH, valueWidth(""))), "muted"]);
2306
+ }
2307
+ return rows;
2308
+ }
2309
+
2098
2310
  // src/sidebar/data.ts
2099
2311
  import { mkdir, readFile, writeFile } from "fs/promises";
2100
2312
  import { homedir as homedir3 } from "os";
2101
2313
  import { dirname, join as join3 } from "path";
2102
2314
 
2103
2315
  // src/sidebar/zen.ts
2104
- function totals(value) {
2316
+ function totals2(value) {
2105
2317
  const v = value ?? {};
2106
2318
  const num = (key) => typeof v[key] === "number" ? v[key] : 0;
2107
2319
  return {
@@ -2110,7 +2322,7 @@ function totals(value) {
2110
2322
  tokens: num("tokens")
2111
2323
  };
2112
2324
  }
2113
- function meter(value) {
2325
+ function meter2(value) {
2114
2326
  const v = value ?? {};
2115
2327
  return {
2116
2328
  status: typeof v.status === "string" ? v.status : "ok",
@@ -2133,9 +2345,9 @@ function parseZenJson(text) {
2133
2345
  id: raw.id,
2134
2346
  provider: typeof raw.provider === "string" ? raw.provider : "",
2135
2347
  limit: typeof raw.limit === "number" ? raw.limit : undefined,
2136
- fiveHour: totals(raw.fiveHour),
2137
- weekly: totals(raw.weekly),
2138
- month: totals(raw.month)
2348
+ fiveHour: totals2(raw.fiveHour),
2349
+ weekly: totals2(raw.weekly),
2350
+ month: totals2(raw.month)
2139
2351
  });
2140
2352
  }
2141
2353
  const top = body.totals ?? {};
@@ -2144,20 +2356,20 @@ function parseZenJson(text) {
2144
2356
  periodEndAt: typeof body.periodEndAt === "number" ? body.periodEndAt : undefined,
2145
2357
  source: typeof body.source === "string" ? body.source : undefined,
2146
2358
  account: body.account ? {
2147
- rolling: meter(body.account.rolling),
2148
- weekly: meter(body.account.weekly),
2149
- monthly: meter(body.account.monthly)
2359
+ rolling: meter2(body.account.rolling),
2360
+ weekly: meter2(body.account.weekly),
2361
+ monthly: meter2(body.account.monthly)
2150
2362
  } : undefined,
2151
2363
  accountError: typeof body.accountError === "string" && body.accountError ? body.accountError : undefined,
2152
2364
  totals: {
2153
- fiveHour: totals(top.fiveHour),
2154
- weekly: totals(top.weekly),
2155
- month: totals(top.month)
2365
+ fiveHour: totals2(top.fiveHour),
2366
+ weekly: totals2(top.weekly),
2367
+ month: totals2(top.month)
2156
2368
  },
2157
2369
  models
2158
2370
  };
2159
2371
  }
2160
- function monthPeriod(usage) {
2372
+ function monthPeriod2(usage) {
2161
2373
  const {
2162
2374
  periodStart,
2163
2375
  periodEndAt
@@ -2213,7 +2425,7 @@ function zenRows(usage, meta, modelUsage, session, fallbackName, windows) {
2213
2425
  const mine = usage.models.get(modelKey(fallbackName ?? "")) ?? (meta ? usage.models.get(modelKey(meta.name)) : undefined);
2214
2426
  const limit = oc?.allowance ?? mine?.limit;
2215
2427
  const cap = mine ? limit : undefined;
2216
- const period = monthPeriod(usage);
2428
+ const period = monthPeriod2(usage);
2217
2429
  if (usage.account) {
2218
2430
  rows.push(...zenMeterRows("5-hour", usage.account.rolling, now, FIVE_HOUR_SECS, usage.totals.fiveHour.cost));
2219
2431
  rows.push(...zenMeterRows("Weekly", usage.account.weekly, now, WEEKLY_SECS, usage.totals.weekly.cost));
@@ -2370,6 +2582,19 @@ async function loadUsage() {
2370
2582
  async function loadZen(plan = OC_PLAN_DEFAULT) {
2371
2583
  return parseZenJson(await runOcuse(zenArgs(plan), SIDEBAR_OPTS));
2372
2584
  }
2585
+ var arkCache;
2586
+ async function loadArk() {
2587
+ const now = Date.now();
2588
+ if (arkCache && now - arkCache.at < ARK_SNAPSHOT_TTL_MS) {
2589
+ return arkCache.usage;
2590
+ }
2591
+ const usage = parseArkJson(await runArkuse("-1 --json", SIDEBAR_OPTS));
2592
+ arkCache = {
2593
+ at: now,
2594
+ usage
2595
+ };
2596
+ return usage;
2597
+ }
2373
2598
 
2374
2599
  // src/sidebar/usageDb.ts
2375
2600
  import { Database } from "bun:sqlite";
@@ -2561,16 +2786,20 @@ function sessionKind(providerID) {
2561
2786
  return "go";
2562
2787
  if (providerID === ZEN_PROVIDER_ID || providerID.startsWith(ZEN_PROVIDER_PREFIX))
2563
2788
  return "zen";
2789
+ if (providerID.startsWith(ARK_PROVIDER_ID))
2790
+ return "ark";
2564
2791
  return "other";
2565
2792
  }
2566
2793
  function shows(kind) {
2567
- return kind === "ours" || kind === "go" || kind === "zen";
2794
+ return kind === "ours" || kind === "go" || kind === "zen" || kind === "ark";
2568
2795
  }
2569
2796
  function panelTitle(kind) {
2570
2797
  if (kind === "ours")
2571
2798
  return "Command Code";
2572
2799
  if (kind === "zen")
2573
2800
  return "OpenCode Zen";
2801
+ if (kind === "ark")
2802
+ return "Volcengine ARK";
2574
2803
  return "OpenCode Go";
2575
2804
  }
2576
2805
  var lastSnapshot;
@@ -2613,9 +2842,17 @@ function rememberZen(usage) {
2613
2842
  function seededZen() {
2614
2843
  return lastZen;
2615
2844
  }
2845
+ var lastArk;
2846
+ function rememberArk(usage) {
2847
+ lastArk = usage;
2848
+ }
2849
+ function seededArk() {
2850
+ return lastArk;
2851
+ }
2616
2852
  function useRows(activeModelId, providerID, sessionID) {
2617
2853
  const [usage, setUsage] = createSignal(seededRows());
2618
2854
  const [zen, setZen] = createSignal(seededZen());
2855
+ const [ark, setArk] = createSignal(seededArk());
2619
2856
  const [meta, setMeta] = createSignal(seededMeta());
2620
2857
  const [modelUsage, setModelUsage] = createSignal(lastModelUsage);
2621
2858
  const [sessionUsage, setSessionUsage] = createSignal();
@@ -2671,6 +2908,10 @@ function useRows(activeModelId, providerID, sessionID) {
2671
2908
  setZen(snapshot);
2672
2909
  refreshModelUsage(activeModelId());
2673
2910
  refreshModelWindows(activeModelId());
2911
+ } else if (kind === "ark") {
2912
+ const snapshot = await loadArk();
2913
+ rememberArk(snapshot);
2914
+ setArk(snapshot);
2674
2915
  }
2675
2916
  } catch {} finally {
2676
2917
  inFlight = false;
@@ -2715,6 +2956,9 @@ function useRows(activeModelId, providerID, sessionID) {
2715
2956
  if (kind === "go" || kind === "zen") {
2716
2957
  return zenRows(zen(), found, usageFor(id), sessionUsage(), fallbackName, modelWindows());
2717
2958
  }
2959
+ if (kind === "ark") {
2960
+ return arkRows(ark(), fallbackName);
2961
+ }
2718
2962
  const model = found ? {
2719
2963
  ...found,
2720
2964
  tier: found.tier ?? (id ? tierFor(id) : undefined),
@@ -2834,8 +3078,35 @@ var commandCodeTui = {
2834
3078
  message: text.trimEnd()
2835
3079
  });
2836
3080
  }
3081
+ }, {
3082
+ id: ARKUSE_USAGE_COMMAND_ID,
3083
+ title: "Volcengine ARK usage",
3084
+ group: "Command Code",
3085
+ slash: {
3086
+ name: "arkuse-usage",
3087
+ arguments: true
3088
+ },
3089
+ enabled: () => true,
3090
+ suggested: true,
3091
+ run: async (input) => {
3092
+ let text;
3093
+ try {
3094
+ text = await runArkuse(arkuseArgs(input));
3095
+ } catch (error) {
3096
+ ctx.ui.toast.show({
3097
+ title: "arkuse-usage failed",
3098
+ message: error instanceof Error ? error.message : String(error),
3099
+ variant: "error"
3100
+ });
3101
+ return;
3102
+ }
3103
+ await ctx.ui.dialog.alert({
3104
+ title: "Volcengine ARK usage",
3105
+ message: text.trimEnd()
3106
+ });
3107
+ }
2837
3108
  }],
2838
- bindings: [CMD_USAGE_COMMAND_ID, OCUSE_USAGE_COMMAND_ID]
3109
+ bindings: [CMD_USAGE_COMMAND_ID, OCUSE_USAGE_COMMAND_ID, ARKUSE_USAGE_COMMAND_ID]
2839
3110
  }));
2840
3111
  }
2841
3112
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffreyjyz/opencode-command-code",
3
- "version": "0.5.16",
3
+ "version": "0.5.19",
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",