@jeffreyjyz/opencode-command-code 0.5.20 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Add the plugin to `opencode.json` (v2 hosts use `plugins`; v1's singular `plugin
25
25
 
26
26
  ```json
27
27
  {
28
- "plugins": ["@jeffreyjyz/opencode-command-code"]
28
+ "plugins": ["@jeffreyjyz/opencode-command-code"]
29
29
  }
30
30
  ```
31
31
 
@@ -67,12 +67,12 @@ Usage comes from `cmduse` polled every 5 seconds, read from opencode's message s
67
67
 
68
68
  Each command passes its arguments through to the CLI that owns it, and each has an agent-facing tool of the same name, so the model can fetch the same text without you typing a slash command.
69
69
 
70
- | Command | Tool | Spawns |
71
- |---|---|---|
72
- | `/cmd-usage` | `cmd_usage` | `cmduse` — plan, credits, windows, billing period; `plans` for the plan table |
73
- | `/ocuse-usage` | `ocuse_usage` | `ocuse` — OpenCode Go/Zen meters and per-model spend; empty args mean `-1` |
74
- | `/arkuse-usage` | `arkuse_usage` | `arkuse` — Volcengine ARK Agent Plan meters in AFP; empty args mean `-1` |
75
- | — | `mpc_prices` | `mpc` — per-model price comparison across Command Code and OpenCode Go plans |
70
+ | Command | Tool | Spawns |
71
+ | --------------- | -------------- | ----------------------------------------------------------------------------- |
72
+ | `/cmd-usage` | `cmd_usage` | `cmduse` — plan, credits, windows, billing period; `plans` for the plan table |
73
+ | `/ocuse-usage` | `ocuse_usage` | `ocuse` — OpenCode Go/Zen meters and per-model spend; empty args mean `-1` |
74
+ | `/arkuse-usage` | `arkuse_usage` | `arkuse` — Volcengine ARK Agent Plan meters in AFP; empty args mean `-1` |
75
+ | — | `mpc_prices` | `mpc` — per-model price comparison across Command Code and OpenCode Go plans |
76
76
 
77
77
  A bare `ocuse` or `arkuse` is a live watch loop that would never return to the dialog, so an empty argument means the one-shot dashboard.
78
78
 
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.20",
2417
+ version: "0.5.22",
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",
@@ -2426,7 +2426,8 @@ var package_default = {
2426
2426
  scripts: {
2427
2427
  build: "rm -rf dist && bun build src/index.ts --outdir dist --target bun --external @opencode-ai/plugin --external @opencode/plugin --external @opencode/plugin/tui && bun scripts/build-tui.ts && tsc -p tsconfig.build.json",
2428
2428
  test: "bun test",
2429
- typecheck: "tsc --noEmit",
2429
+ lint: "biome lint --write .",
2430
+ typecheck: "bun check",
2430
2431
  "build:tui": "bun scripts/build-tui.ts",
2431
2432
  "extract:catalog": "bun scripts/extract-catalog.ts"
2432
2433
  },
@@ -2445,7 +2446,6 @@ var package_default = {
2445
2446
  },
2446
2447
  files: [
2447
2448
  "dist",
2448
- "tui.js",
2449
2449
  "LICENSE"
2450
2450
  ],
2451
2451
  keywords: [
@@ -3361,7 +3361,13 @@ function runOnce(bin, args, opts, name = "cmduse") {
3361
3361
  if (settled)
3362
3362
  return;
3363
3363
  settled = true;
3364
- child.kill();
3364
+ child.stdout.removeAllListeners("data");
3365
+ child.stderr.removeAllListeners("data");
3366
+ child.removeAllListeners("error");
3367
+ child.removeAllListeners("close");
3368
+ child.kill("SIGTERM");
3369
+ const killTimer = setTimeout(() => child.kill("SIGKILL"), 3000);
3370
+ killTimer.unref();
3365
3371
  reject(new Error(`${name} ${args.join(" ")}: timed out after ${opts.timeoutMs}ms`));
3366
3372
  }, opts.timeoutMs);
3367
3373
  const onAbort = () => child.kill("SIGTERM");
@@ -3373,6 +3379,10 @@ function runOnce(bin, args, opts, name = "cmduse") {
3373
3379
  if (timer)
3374
3380
  clearTimeout(timer);
3375
3381
  opts.signal?.removeEventListener("abort", onAbort);
3382
+ child.stdout.removeAllListeners("data");
3383
+ child.stderr.removeAllListeners("data");
3384
+ child.removeAllListeners("error");
3385
+ child.removeAllListeners("close");
3376
3386
  fn();
3377
3387
  };
3378
3388
  child.stdout.on("data", (d) => out.push(d));
@@ -3615,9 +3625,7 @@ var gating_default = {
3615
3625
  },
3616
3626
  plans: {
3617
3627
  "individual-go": {
3618
- allowedCategories: [
3619
- "opensource"
3620
- ],
3628
+ allowedCategories: ["opensource"],
3621
3629
  blockedModels: [
3622
3630
  "vercel-ai-gateway:meta/muse-spark-1.2",
3623
3631
  "vercel-ai-gateway:meta/muse-spark-1.3",
@@ -3631,9 +3639,7 @@ var gating_default = {
3631
3639
  ]
3632
3640
  },
3633
3641
  "individual-go-v1": {
3634
- allowedCategories: [
3635
- "opensource"
3636
- ],
3642
+ allowedCategories: ["opensource"],
3637
3643
  blockedModels: [
3638
3644
  "vercel-ai-gateway:meta/muse-spark-1.2",
3639
3645
  "vercel-ai-gateway:meta/muse-spark-1.3",
@@ -3647,16 +3653,11 @@ var gating_default = {
3647
3653
  ]
3648
3654
  },
3649
3655
  "individual-goat": {
3650
- allowedCategories: [
3651
- "opensource"
3652
- ],
3656
+ allowedCategories: ["opensource"],
3653
3657
  blockedModels: []
3654
3658
  },
3655
3659
  "individual-pro": {
3656
- allowedCategories: [
3657
- "premium",
3658
- "opensource"
3659
- ],
3660
+ allowedCategories: ["premium", "opensource"],
3660
3661
  blockedModels: [
3661
3662
  "anthropic:claude-fable-5-1",
3662
3663
  "anthropic:claude-fable-5",
@@ -3672,10 +3673,7 @@ var gating_default = {
3672
3673
  ]
3673
3674
  },
3674
3675
  "individual-pro-v1": {
3675
- allowedCategories: [
3676
- "premium",
3677
- "opensource"
3678
- ],
3676
+ allowedCategories: ["premium", "opensource"],
3679
3677
  blockedModels: [
3680
3678
  "anthropic:claude-fable-5-1",
3681
3679
  "anthropic:claude-fable-5",
@@ -3691,33 +3689,19 @@ var gating_default = {
3691
3689
  ]
3692
3690
  },
3693
3691
  "individual-provider": {
3694
- allowedCategories: [
3695
- "premium",
3696
- "opensource"
3697
- ],
3698
- blockedModels: [
3699
- "openai:gpt-6.1-sol"
3700
- ]
3692
+ allowedCategories: ["premium", "opensource"],
3693
+ blockedModels: ["openai:gpt-6.1-sol"]
3701
3694
  },
3702
3695
  "individual-max": {
3703
- allowedCategories: [
3704
- "premium",
3705
- "opensource"
3706
- ],
3696
+ allowedCategories: ["premium", "opensource"],
3707
3697
  blockedModels: []
3708
3698
  },
3709
3699
  "individual-ultra": {
3710
- allowedCategories: [
3711
- "premium",
3712
- "opensource"
3713
- ],
3700
+ allowedCategories: ["premium", "opensource"],
3714
3701
  blockedModels: []
3715
3702
  },
3716
3703
  "teams-pro": {
3717
- allowedCategories: [
3718
- "premium",
3719
- "opensource"
3720
- ],
3704
+ allowedCategories: ["premium", "opensource"],
3721
3705
  blockedModels: []
3722
3706
  }
3723
3707
  },
@@ -4046,12 +4030,7 @@ function sseDecoder(onEvent) {
4046
4030
  var text = (v) => typeof v === "string" ? v : "";
4047
4031
  var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
4048
4032
  var rec = (v) => v && typeof v === "object" ? v : {};
4049
- var TOOL_FINISH = new Set([
4050
- "tool_use",
4051
- "tool_calls",
4052
- "tool-call",
4053
- "function_call"
4054
- ]);
4033
+ var TOOL_FINISH = new Set(["tool_use", "tool_calls", "tool-call", "function_call"]);
4055
4034
  var LENGTH_FINISH = new Set([
4056
4035
  "length",
4057
4036
  "max_tokens",
@@ -4100,9 +4079,7 @@ function anthropicReducer() {
4100
4079
  const open = (index, kind) => {
4101
4080
  const id = `${kind === "text" ? "text" : "reasoning"}-${index}`;
4102
4081
  blocks.set(index, { type: kind, id });
4103
- return [
4104
- { type: kind === "text" ? "text-start" : "reasoning-start", id }
4105
- ];
4082
+ return [{ type: kind === "text" ? "text-start" : "reasoning-start", id }];
4106
4083
  };
4107
4084
  return {
4108
4085
  feed(event) {
@@ -4143,19 +4120,13 @@ function anthropicReducer() {
4143
4120
  if (textDelta) {
4144
4121
  const parts = block?.type === "text" ? [] : open(index, "text");
4145
4122
  const id = blocks.get(index)?.id ?? `text-${index}`;
4146
- return [
4147
- ...parts,
4148
- { type: "text-delta", id, delta: textDelta }
4149
- ];
4123
+ return [...parts, { type: "text-delta", id, delta: textDelta }];
4150
4124
  }
4151
4125
  const thinking = text(delta.thinking);
4152
4126
  if (thinking) {
4153
4127
  const parts = block?.type === "thinking" ? [] : open(index, "thinking");
4154
4128
  const id = blocks.get(index)?.id ?? `reasoning-${index}`;
4155
- return [
4156
- ...parts,
4157
- { type: "reasoning-delta", id, delta: thinking }
4158
- ];
4129
+ return [...parts, { type: "reasoning-delta", id, delta: thinking }];
4159
4130
  }
4160
4131
  const partial = text(delta.partial_json);
4161
4132
  if (partial && block?.type === "tool_use") {
@@ -4382,11 +4353,7 @@ function openaiReducer() {
4382
4353
  return parts;
4383
4354
  },
4384
4355
  close() {
4385
- const parts = [
4386
- ...closeReasoning(),
4387
- ...closeText(),
4388
- ...flushTools()
4389
- ];
4356
+ const parts = [...closeReasoning(), ...closeText(), ...flushTools()];
4390
4357
  if (pending && !finished) {
4391
4358
  finished = true;
4392
4359
  parts.push({
@@ -4914,6 +4881,9 @@ async function credentialKey(ctx) {
4914
4881
  return;
4915
4882
  }
4916
4883
  }
4884
+ var v2Controller;
4885
+ var v2FirstRefresh;
4886
+ var v2Timer;
4917
4887
  var commandCodeV2 = {
4918
4888
  id: PLUGIN_ID,
4919
4889
  async setup(ctx) {
@@ -4953,7 +4923,7 @@ var commandCodeV2 = {
4953
4923
  }
4954
4924
  const seed = staticSeedModels();
4955
4925
  const cached = await readModelsCache();
4956
- if (cached) {
4926
+ if (cached && typeof cached.fetchedAt === "number" && Date.now() - cached.fetchedAt < MODELS_CACHE_TTL_MS) {
4957
4927
  seed["command-code-anthropic"] = mergeModels(seed["command-code-anthropic"], cached.claude.map((m) => toV2Model(m, LANES[0])));
4958
4928
  seed["command-code-openai"] = mergeModels(seed["command-code-openai"], cached.open.map((m) => toV2Model(m, LANES[1])));
4959
4929
  }
@@ -5085,7 +5055,14 @@ var commandCodeV2 = {
5085
5055
  if (!localKey && !hasConnection) {
5086
5056
  console.warn(`[command-code] no API key found. Run /connect and choose "${INTEGRATION_NAME}", or \`cmd login\` (~/.commandcode/auth.json), or set CMD_API_KEY.`);
5087
5057
  }
5058
+ if (v2Controller)
5059
+ v2Controller.abort();
5060
+ if (v2FirstRefresh)
5061
+ clearTimeout(v2FirstRefresh);
5062
+ if (v2Timer)
5063
+ clearInterval(v2Timer);
5088
5064
  const controller = new AbortController;
5065
+ v2Controller = controller;
5089
5066
  let refreshing = false;
5090
5067
  let appliedClaude = cached?.claude ?? [];
5091
5068
  let appliedOpen = cached?.open ?? [];
@@ -5137,12 +5114,18 @@ var commandCodeV2 = {
5137
5114
  canTransform = true;
5138
5115
  }
5139
5116
  };
5140
- const firstRefresh = setTimeout(() => void refresh(), 3000);
5141
- const timer = setInterval(() => void refresh(), 30 * 60 * 1000);
5117
+ v2FirstRefresh = setTimeout(() => void refresh(), 3000);
5118
+ v2Timer = setInterval(() => void refresh(), 30 * 60 * 1000);
5142
5119
  return () => {
5143
5120
  controller.abort();
5144
- clearTimeout(firstRefresh);
5145
- clearInterval(timer);
5121
+ if (v2FirstRefresh)
5122
+ clearTimeout(v2FirstRefresh);
5123
+ if (v2Timer)
5124
+ clearInterval(v2Timer);
5125
+ if (v2Controller === controller)
5126
+ v2Controller = undefined;
5127
+ v2FirstRefresh = undefined;
5128
+ v2Timer = undefined;
5146
5129
  };
5147
5130
  }
5148
5131
  };
@@ -11,12 +11,7 @@ export type Tone = "base" | "muted" | "strong" | "ok" | "warn" | "crit";
11
11
  * A row. `headline` marks the two rows whose label names the thing the panel is
12
12
  * about (the plan, the active model); the panel colours their labels apart.
13
13
  */
14
- export type SidebarRow = [
15
- label: string,
16
- value: string,
17
- tone?: Tone,
18
- headline?: true
19
- ];
14
+ export type SidebarRow = [label: string, value: string, tone?: Tone, headline?: true];
20
15
  /** Headroom severity: comfortable to 70%, tight to 90%, over after that. */
21
16
  export declare function pctTone(pct: number): Tone;
22
17
  /**
package/dist/tui.js CHANGED
@@ -124,7 +124,13 @@ function runOnce(bin, args, opts, name = "cmduse") {
124
124
  if (settled)
125
125
  return;
126
126
  settled = true;
127
- child.kill();
127
+ child.stdout.removeAllListeners("data");
128
+ child.stderr.removeAllListeners("data");
129
+ child.removeAllListeners("error");
130
+ child.removeAllListeners("close");
131
+ child.kill("SIGTERM");
132
+ const killTimer = setTimeout(() => child.kill("SIGKILL"), 3000);
133
+ killTimer.unref();
128
134
  reject(new Error(`${name} ${args.join(" ")}: timed out after ${opts.timeoutMs}ms`));
129
135
  }, opts.timeoutMs);
130
136
  const onAbort = () => child.kill("SIGTERM");
@@ -138,6 +144,10 @@ function runOnce(bin, args, opts, name = "cmduse") {
138
144
  if (timer)
139
145
  clearTimeout(timer);
140
146
  opts.signal?.removeEventListener("abort", onAbort);
147
+ child.stdout.removeAllListeners("data");
148
+ child.stderr.removeAllListeners("data");
149
+ child.removeAllListeners("error");
150
+ child.removeAllListeners("close");
141
151
  fn();
142
152
  };
143
153
  child.stdout.on("data", (d) => out.push(d));
@@ -345,9 +355,7 @@ var gating_default = {
345
355
  },
346
356
  plans: {
347
357
  "individual-go": {
348
- allowedCategories: [
349
- "opensource"
350
- ],
358
+ allowedCategories: ["opensource"],
351
359
  blockedModels: [
352
360
  "vercel-ai-gateway:meta/muse-spark-1.2",
353
361
  "vercel-ai-gateway:meta/muse-spark-1.3",
@@ -361,9 +369,7 @@ var gating_default = {
361
369
  ]
362
370
  },
363
371
  "individual-go-v1": {
364
- allowedCategories: [
365
- "opensource"
366
- ],
372
+ allowedCategories: ["opensource"],
367
373
  blockedModels: [
368
374
  "vercel-ai-gateway:meta/muse-spark-1.2",
369
375
  "vercel-ai-gateway:meta/muse-spark-1.3",
@@ -377,16 +383,11 @@ var gating_default = {
377
383
  ]
378
384
  },
379
385
  "individual-goat": {
380
- allowedCategories: [
381
- "opensource"
382
- ],
386
+ allowedCategories: ["opensource"],
383
387
  blockedModels: []
384
388
  },
385
389
  "individual-pro": {
386
- allowedCategories: [
387
- "premium",
388
- "opensource"
389
- ],
390
+ allowedCategories: ["premium", "opensource"],
390
391
  blockedModels: [
391
392
  "anthropic:claude-fable-5-1",
392
393
  "anthropic:claude-fable-5",
@@ -402,10 +403,7 @@ var gating_default = {
402
403
  ]
403
404
  },
404
405
  "individual-pro-v1": {
405
- allowedCategories: [
406
- "premium",
407
- "opensource"
408
- ],
406
+ allowedCategories: ["premium", "opensource"],
409
407
  blockedModels: [
410
408
  "anthropic:claude-fable-5-1",
411
409
  "anthropic:claude-fable-5",
@@ -421,33 +419,19 @@ var gating_default = {
421
419
  ]
422
420
  },
423
421
  "individual-provider": {
424
- allowedCategories: [
425
- "premium",
426
- "opensource"
427
- ],
428
- blockedModels: [
429
- "openai:gpt-6.1-sol"
430
- ]
422
+ allowedCategories: ["premium", "opensource"],
423
+ blockedModels: ["openai:gpt-6.1-sol"]
431
424
  },
432
425
  "individual-max": {
433
- allowedCategories: [
434
- "premium",
435
- "opensource"
436
- ],
426
+ allowedCategories: ["premium", "opensource"],
437
427
  blockedModels: []
438
428
  },
439
429
  "individual-ultra": {
440
- allowedCategories: [
441
- "premium",
442
- "opensource"
443
- ],
430
+ allowedCategories: ["premium", "opensource"],
444
431
  blockedModels: []
445
432
  },
446
433
  "teams-pro": {
447
- allowedCategories: [
448
- "premium",
449
- "opensource"
450
- ],
434
+ allowedCategories: ["premium", "opensource"],
451
435
  blockedModels: []
452
436
  }
453
437
  },
@@ -2589,12 +2573,14 @@ async function loadZen(plan = OC_PLAN_DEFAULT) {
2589
2573
  }
2590
2574
  var arkCache;
2591
2575
  async function loadArk() {
2576
+ const key = "default";
2592
2577
  const now = Date.now();
2593
- if (arkCache && now - arkCache.at < ARK_SNAPSHOT_TTL_MS) {
2578
+ if (arkCache && arkCache.key === key && now - arkCache.at < ARK_SNAPSHOT_TTL_MS) {
2594
2579
  return arkCache.usage;
2595
2580
  }
2596
2581
  const usage = parseArkJson(await runArkuse("-1 --json", SIDEBAR_OPTS));
2597
2582
  arkCache = {
2583
+ key,
2598
2584
  at: now,
2599
2585
  usage
2600
2586
  };
@@ -3022,9 +3008,13 @@ function PanelV2(props) {
3022
3008
  colors: () => themed(hostColors(ctx.theme))
3023
3009
  });
3024
3010
  }
3011
+ var tuiSetupSeen = new WeakSet;
3025
3012
  var commandCodeTui = {
3026
3013
  id: TUI_ID,
3027
3014
  setup(ctx) {
3015
+ if (tuiSetupSeen.has(ctx))
3016
+ return;
3017
+ tuiSetupSeen.add(ctx);
3028
3018
  ctx.ui.slot({
3029
3019
  append: "sidebar.content",
3030
3020
  render: (input) => _$createComponent2(PanelV2, {
package/dist/v2.d.ts CHANGED
@@ -29,11 +29,5 @@ type CredentialContext = {
29
29
  * back to CMD_API_KEY / ~/.commandcode/auth.json. Resolved per call — a
30
30
  * /connect mid-session must be picked up. */
31
31
  export declare function credentialKey(ctx: CredentialContext): Promise<string | undefined>;
32
- /**
33
- * opencode validates the plugin *by decoding* the default export against its
34
- * own `Plugin` interface (identity `define` on both sides), so the package is
35
- * only needed for types. Keeps the runtime dependency surface to opencode's
36
- * own rewrite list (@opentui/*, solid-js).
37
- */
38
32
  export declare const commandCodeV2: PluginNs.Plugin;
39
33
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffreyjyz/opencode-command-code",
3
- "version": "0.5.20",
3
+ "version": "0.5.22",
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",
@@ -12,7 +12,8 @@
12
12
  "scripts": {
13
13
  "build": "rm -rf dist && bun build src/index.ts --outdir dist --target bun --external @opencode-ai/plugin --external @opencode/plugin --external @opencode/plugin/tui && bun scripts/build-tui.ts && tsc -p tsconfig.build.json",
14
14
  "test": "bun test",
15
- "typecheck": "tsc --noEmit",
15
+ "lint": "biome lint --write .",
16
+ "typecheck": "bun check",
16
17
  "build:tui": "bun scripts/build-tui.ts",
17
18
  "extract:catalog": "bun scripts/extract-catalog.ts"
18
19
  },
@@ -31,7 +32,6 @@
31
32
  },
32
33
  "files": [
33
34
  "dist",
34
- "tui.js",
35
35
  "LICENSE"
36
36
  ],
37
37
  "keywords": [
package/tui.js DELETED
@@ -1,5 +0,0 @@
1
- // Entry shim for local/directory plugin loading: opencode resolves the TUI
2
- // half as a literal `tui.js` beside the package root (package `exports`
3
- // are only consulted for npm-installed plugins). Keep this file in sync with
4
- // the built ./tui output.
5
- export { default } from "./dist/tui.js";