@jeffreyjyz/opencode-command-code 0.2.9 → 0.3.0

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.
@@ -0,0 +1,22 @@
1
+ import type { LanguageModelV3 } from "@ai-sdk/provider";
2
+ import { type WireLane } from "./wire";
3
+ export declare const DEFAULT_BASE_URL = "https://api.commandcode.ai";
4
+ export interface CommandCodeProviderOptions {
5
+ /** Bearer key; the v2 host passes the resolved connection credential here. */
6
+ apiKey?: string;
7
+ baseURL?: string;
8
+ /** Extra headers (host-supplied), merged last. */
9
+ headers?: Record<string, string>;
10
+ /** Reasoning effort the host wants (`providerOptions.commandcode.reasoning`). */
11
+ reasoningEffort?: string;
12
+ /** Injected for tests. */
13
+ fetch?: typeof fetch;
14
+ }
15
+ export interface CommandCodeProvider {
16
+ languageModel(modelId: string): LanguageModelV3;
17
+ }
18
+ /** Claude models use the Anthropic wire; everything else chat completions. */
19
+ export declare function laneOf(modelId: string): WireLane;
20
+ /** True when the model advertises image input; the caller passes the table in. */
21
+ export type ImagesFor = (modelId: string) => boolean;
22
+ export declare function createCommandCode(options?: CommandCodeProviderOptions, images?: ImagesFor): CommandCodeProvider;
@@ -0,0 +1,21 @@
1
+ import type { LanguageModelV3FinishReason, LanguageModelV3StreamPart, LanguageModelV3Usage } from "@ai-sdk/provider";
2
+ type Part = LanguageModelV3StreamPart;
3
+ /** Split an SSE byte stream into JSON events, tolerating partial chunks. */
4
+ export declare function sseDecoder(onEvent: (event: Record<string, unknown>) => void): (text: string) => void;
5
+ /** Wire stop reason → unified v3 finish reason; unknown reasons completed. */
6
+ export declare function finishReason(raw: string | undefined): LanguageModelV3FinishReason;
7
+ /** Anthropic `message_start`/`message_delta` usage → v3 usage. */
8
+ export declare function anthropicUsage(raw: Record<string, unknown>): LanguageModelV3Usage;
9
+ /** OpenAI usage → v3 usage (`prompt_tokens_details.cached_tokens` is cache read). */
10
+ export declare function openaiUsage(raw: Record<string, unknown>): LanguageModelV3Usage;
11
+ export interface Reducer {
12
+ /** Feed one parsed SSE event; returns the parts it produced. */
13
+ feed(event: Record<string, unknown>): Part[];
14
+ /** Close open parts when the transport ends (abort/error). */
15
+ close(): Part[];
16
+ }
17
+ /** Anthropic Messages reducer. */
18
+ export declare function anthropicReducer(): Reducer;
19
+ /** OpenAI Chat Completions reducer. */
20
+ export declare function openaiReducer(): Reducer;
21
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { LanguageModelV3Prompt } from "@ai-sdk/provider";
2
+ export type WireLane = "anthropic" | "openai";
3
+ /** The subset of tools we serialize (V3 FunctionTool already matches). */
4
+ export interface WireTool {
5
+ name: string;
6
+ description?: string;
7
+ inputSchema: unknown;
8
+ }
9
+ export interface WireOptions {
10
+ maxOutputTokens?: number;
11
+ temperature?: number;
12
+ tools?: WireTool[];
13
+ /** Wire extension both gates honour (`reasoning_effort`). */
14
+ reasoningEffort?: string;
15
+ /** Whether the model advertises image input (catalog-derived). */
16
+ images: boolean;
17
+ }
18
+ /** Anthropic body: POST /provider/v1/messages. */
19
+ export declare function anthropicBody(model: string, prompt: LanguageModelV3Prompt, options: WireOptions): Record<string, unknown>;
20
+ /** OpenAI body: POST /provider/v1/chat/completions. */
21
+ export declare function openaiBody(model: string, prompt: LanguageModelV3Prompt, options: WireOptions): Record<string, unknown>;
22
+ export declare function requestBody(lane: WireLane, model: string, prompt: LanguageModelV3Prompt, options: WireOptions): Record<string, unknown>;
@@ -0,0 +1,10 @@
1
+ /** 5-hour window length in seconds. Mirrors cmduse_core::FIVE_HOUR_SECS. */
2
+ export declare const FIVE_HOUR_SECS: number;
3
+ /** Weekly window length in seconds. Mirrors cmduse_core::WEEKLY_SECS. */
4
+ export declare const WEEKLY_SECS: number;
5
+ /**
6
+ * Percentage of a rolling window that has elapsed, or undefined when the
7
+ * window has not started (or has no reset time). `nowSecs` is whole seconds —
8
+ * the same unit cmduse-core's vectors pin.
9
+ */
10
+ export declare function elapsedPct(resetAtMs: number | undefined, durSecs: number, nowSecs: number): number | undefined;
package/dist/tui.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { RGBA } from "@opentui/core";
3
- import { Plugin } from "@opencode/plugin/tui";
3
+ import type { Plugin as TuiPluginNs } from "@opencode/plugin/tui";
4
4
  interface V1Api {
5
5
  slots: {
6
6
  register: (config: {
@@ -31,10 +31,10 @@ interface V1Api {
31
31
  }
32
32
  /** v1 half — snake_case slot map registered through `api.slots`. */
33
33
  export declare const tui: (api: V1Api) => Promise<void>;
34
- export declare const commandCodeTui: Plugin.Definition;
34
+ export declare const commandCodeTui: TuiPluginNs.Definition;
35
35
  declare const _default: {
36
36
  tui: (api: V1Api) => Promise<void>;
37
37
  id: string;
38
- setup: (context: Plugin.Context) => Promise<Plugin.Cleanup | void> | Plugin.Cleanup | void;
38
+ setup: (context: TuiPluginNs.Context) => Promise<TuiPluginNs.Cleanup | void> | TuiPluginNs.Cleanup | void;
39
39
  };
40
40
  export default _default;
package/dist/tui.js CHANGED
@@ -8,7 +8,6 @@ import { createComponent as _$createComponent } from "@opentui/solid";
8
8
  import { createTextNode as _$createTextNode } from "@opentui/solid";
9
9
  import { insertNode as _$insertNode } from "@opentui/solid";
10
10
  import { createElement as _$createElement } from "@opentui/solid";
11
- import { Plugin } from "@opencode/plugin/tui";
12
11
  import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
13
12
 
14
13
  // src/cli.ts
@@ -389,6 +388,22 @@ function canonicalizeModelId(model) {
389
388
  return findKnown(stripDate(model)) ?? model;
390
389
  }
391
390
 
391
+ // src/sidebar/windows.ts
392
+ var FIVE_HOUR_SECS = 5 * 3600;
393
+ var WEEKLY_SECS = 7 * 86400;
394
+ function elapsedPct(resetAtMs, durSecs, nowSecs) {
395
+ if (typeof resetAtMs !== "number" || !Number.isFinite(resetAtMs))
396
+ return;
397
+ const resetSecs = Math.floor(resetAtMs / 1000);
398
+ if (resetSecs < durSecs)
399
+ return;
400
+ const start = resetSecs - durSecs;
401
+ if (nowSecs < start)
402
+ return;
403
+ const pct = (nowSecs - start) / durSecs * 100;
404
+ return Math.round(Math.min(100, Math.max(0, pct)));
405
+ }
406
+
392
407
  // src/sidebar/rows.ts
393
408
  var TIER_DISPLAY = {
394
409
  opensource: "open source",
@@ -425,13 +440,15 @@ function until(epoch, now = Date.now()) {
425
440
  const days = Math.floor(hours / 24);
426
441
  return hours % 24 ? `${days}d ${hours % 24}h` : `${days}d`;
427
442
  }
428
- function windowRow(label, w, now) {
443
+ function windowRow(label, w, now, durSecs) {
429
444
  if (!w || typeof w.cap !== "number" || w.cap <= 0)
430
445
  return;
431
446
  const used = typeof w.used === "number" ? w.used : 0;
432
447
  const pct = Math.round(used / w.cap * 100);
448
+ const elapsed = elapsedPct(w.resetAt, durSecs, Math.floor(now / 1000));
433
449
  const reset = until(w.resetAt, now);
434
- return [label, `${money(used)} / ${money(w.cap)} (${pct}%)${reset ? ` \xB7 resets ${reset}` : ""}`];
450
+ const parts = [`${money(used)} / ${money(w.cap)} (${pct}%)`, ...elapsed === undefined ? [] : [`${elapsed}% elapsed`], ...reset ? [`resets ${reset}`] : []];
451
+ return [label, parts.join(" \xB7 ")];
435
452
  }
436
453
  function usageRows(usage, now = Date.now()) {
437
454
  if (!usage)
@@ -446,10 +463,10 @@ function usageRows(usage, now = Date.now()) {
446
463
  const pct = usage.monthlyCap > 0 ? Math.round(used / usage.monthlyCap * 100) : 0;
447
464
  rows.push(["Monthly", `${money(used)} / ${money(usage.monthlyCap)} (${pct}%)`]);
448
465
  }
449
- const five = windowRow("5-hour", usage.fiveHour, now);
466
+ const five = windowRow("5-hour", usage.fiveHour, now, FIVE_HOUR_SECS);
450
467
  if (five)
451
468
  rows.push(five);
452
- const week = windowRow("Weekly", usage.weekly, now);
469
+ const week = windowRow("Weekly", usage.weekly, now, WEEKLY_SECS);
453
470
  if (week)
454
471
  rows.push(week);
455
472
  if (typeof usage.requests === "number" || typeof usage.cost === "number") {
@@ -468,7 +485,7 @@ function modelRows(meta, usage) {
468
485
  const rows = [["Model", meta.name]];
469
486
  if (usage) {
470
487
  const spent = usage.cost > 0 ? ` \xB7 ${money(usage.cost)}` : "";
471
- rows.push(["Usage", `${count(usage.requests)} req${spent}`]);
488
+ rows.push(["Usage (this model)", `${count(usage.requests)} req${spent}`]);
472
489
  }
473
490
  if (meta.tier)
474
491
  rows.push(["Tier", TIER_DISPLAY[meta.tier]]);
@@ -764,7 +781,7 @@ function PanelV2(props) {
764
781
  muted: () => ctx.theme.text.subdued
765
782
  });
766
783
  }
767
- var commandCodeTui = Plugin.define({
784
+ var commandCodeTui = {
768
785
  id: ID,
769
786
  setup(ctx) {
770
787
  ctx.ui.slot({
@@ -815,7 +832,7 @@ var commandCodeTui = Plugin.define({
815
832
  }
816
833
  });
817
834
  }
818
- });
835
+ };
819
836
  var tui_default = {
820
837
  ...commandCodeTui,
821
838
  tui
package/dist/v2.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Plugin } from "@opencode/plugin";
1
+ import type { Plugin as PluginNs } from "@opencode/plugin";
2
2
  import { type CmdModel } from "./models";
3
3
  export declare const PLUGIN_ID = "command-code";
4
4
  export declare const PROVIDER_BASE = "https://api.commandcode.ai/provider/v1";
@@ -15,9 +15,10 @@ type Lane = {
15
15
  reasoningField?: string;
16
16
  };
17
17
  export type { Lane };
18
- /** API/known model → v2 Model.Info. Every Command Code model shares
19
- * capabilities and pricing (subscription credits, not per-token); only the
20
- * open lane needs the reasoning round-trip field. */
18
+ 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').";
19
+ /** API/known model → v2 Model.Info. Capabilities, variants and $/1M cost all
20
+ * come from the generated catalog (the listing API publishes none of them);
21
+ * only the open lane needs the reasoning round-trip field. */
21
22
  export declare function toV2Model(m: CmdModel, lane: Lane): unknown;
22
23
  /** Instant static seed from gating.json's known model ids, split by wire lane.
23
24
  * Replaced by the gated live list once the background fetch lands; also the
@@ -37,4 +38,10 @@ type CredentialContext = {
37
38
  * back to CMD_API_KEY / ~/.commandcode/auth.json. Resolved per call — a
38
39
  * /connect mid-session must be picked up. */
39
40
  export declare function credentialKey(ctx: CredentialContext): Promise<string | undefined>;
40
- export declare const commandCodeV2: Plugin.Plugin;
41
+ /**
42
+ * opencode validates the plugin *by decoding* the default export against its
43
+ * own `Plugin` interface (identity `define` on both sides), so the package is
44
+ * only needed for types. Keeps the runtime dependency surface to opencode's
45
+ * own rewrite list (@opentui/*, solid-js).
46
+ */
47
+ export declare const commandCodeV2: PluginNs.Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffreyjyz/opencode-command-code",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
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",
@@ -13,18 +13,21 @@
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
15
  "typecheck": "tsc --noEmit",
16
- "build:tui": "bun scripts/build-tui.ts"
16
+ "build:tui": "bun scripts/build-tui.ts",
17
+ "extract:catalog": "bun scripts/extract-catalog.ts"
17
18
  },
18
19
  "dependencies": {
19
- "@opencode-ai/plugin": "*",
20
- "@opencode/plugin": "^2.0.11",
21
20
  "@opentui/core": "^0.5.4",
22
21
  "@opentui/solid": "^0.5.4",
23
- "solid-js": "1.9.12"
22
+ "solid-js": "1.9.12",
23
+ "zod": "4.1.8"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/bun": "latest",
27
- "typescript": "^5"
27
+ "typescript": "^5",
28
+ "@opencode-ai/plugin": "*",
29
+ "@opencode/plugin": "^2.0.11",
30
+ "@ai-sdk/provider": "3.0.8"
28
31
  },
29
32
  "files": [
30
33
  "dist",