@jeffreyjyz/opencode-command-code 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Jeffrey JYZ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,20 @@
1
+ import { canonicalizeModelId, PLAN_RULES } from "./gating";
2
+
3
+ export { canonicalizeModelId, PLAN_RULES };
4
+ export type PlanLike = {
5
+ planId: string;
6
+ purchasedCredits: number;
7
+ freeCredits: number;
8
+ };
9
+ export declare function evaluateModelAccess(
10
+ model: string,
11
+ plan: PlanLike,
12
+ ): {
13
+ allowed: boolean;
14
+ };
15
+ /** Filter a live model list down to what the plan allows. */
16
+ export declare function filterByPlan<
17
+ T extends {
18
+ id: string;
19
+ },
20
+ >(models: T[], plan: PlanLike): T[];
package/dist/api.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ export declare function authHeaders(key: string): Record<string, string>;
2
+ export declare function getJson(
3
+ path: string,
4
+ key: string,
5
+ timeoutMs?: number,
6
+ ): Promise<unknown>;
7
+ export type Whoami = {
8
+ success: boolean;
9
+ user?: {
10
+ name?: string;
11
+ userName?: string;
12
+ };
13
+ };
14
+ export type SubData = {
15
+ status: string;
16
+ planId: string;
17
+ currentPeriodStart?: string;
18
+ currentPeriodEnd?: string;
19
+ };
20
+ export type Window = {
21
+ used: number;
22
+ cap: number;
23
+ exceeded?: boolean;
24
+ resetAt?: number;
25
+ };
26
+ export type Credits = {
27
+ credits: {
28
+ monthlyCredits: number;
29
+ purchasedCredits?: number;
30
+ freeCredits?: number;
31
+ };
32
+ windowLimits?: {
33
+ fiveHour?: Window;
34
+ weekly?: Window;
35
+ };
36
+ };
37
+ export type Summary = {
38
+ totalCount?: number;
39
+ totalCost?: number;
40
+ successRate?: number;
41
+ totalTokensIn?: number;
42
+ totalTokensOut?: number;
43
+ };
44
+ export type ProviderModelsResp = {
45
+ data: Array<{
46
+ id: string;
47
+ name?: string;
48
+ context_length?: number;
49
+ owned_by?: string;
50
+ }>;
51
+ };
52
+ export declare function whoami(key: string): Promise<Whoami>;
53
+ export declare function subscriptions(key: string): Promise<SubData>;
54
+ export declare function credits(key: string): Promise<Credits>;
55
+ export declare function usageSummary(key: string): Promise<Summary>;
56
+ export declare function providerModels(
57
+ key: string,
58
+ ): Promise<ProviderModelsResp>;
59
+ export declare function getPlanInfo(key: string): Promise<SubData>;
@@ -0,0 +1,18 @@
1
+ export declare const OPEN_SOURCE: "opensource";
2
+ export declare const PREMIUM: "premium";
3
+ export type Category = "opensource" | "premium";
4
+ /** model id -> category (models absent from this table have unknown category) */
5
+ export declare const MODEL_CATEGORIES: Record<string, Category>;
6
+ /** planId -> access rules; plan ids absent from this table get everything */
7
+ export declare const PLAN_RULES: Record<
8
+ string,
9
+ {
10
+ allowedCategories: Category[];
11
+ blockedModels: string[];
12
+ }
13
+ >;
14
+ /** canonical known model ids (lowercase compare) */
15
+ export declare const KNOWN_MODELS: string[];
16
+ /** deprecated/aliased model id -> canonical id */
17
+ export declare const MODEL_ALIASES: Record<string, string>;
18
+ export declare function canonicalizeModelId(model: string): string;
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const CommandCodePlugin: Plugin;
3
+ export default CommandCodePlugin;
package/dist/index.js ADDED
@@ -0,0 +1,698 @@
1
+ // @bun
2
+ // src/index.ts
3
+ import { tool } from "@opencode-ai/plugin";
4
+
5
+ // src/api.ts
6
+ var API_BASE = "https://api.commandcode.ai";
7
+ function authHeaders(key) {
8
+ return { Authorization: `Bearer ${key}`, Accept: "application/json" };
9
+ }
10
+ async function getJson(path, key, timeoutMs = 1e4) {
11
+ const resp = await fetch(`${API_BASE}${path}`, {
12
+ headers: authHeaders(key),
13
+ signal: AbortSignal.timeout(timeoutMs)
14
+ });
15
+ const body = await resp.json().catch(() => null);
16
+ if (!resp.ok) {
17
+ const msg = (body !== null && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : undefined) ?? resp.statusText;
18
+ throw new Error(`${path}: HTTP ${resp.status} \u2014 ${msg}`);
19
+ }
20
+ return body;
21
+ }
22
+ async function whoami(key) {
23
+ return getJson("/alpha/whoami", key);
24
+ }
25
+ async function subscriptions(key) {
26
+ const r = await getJson("/alpha/billing/subscriptions", key);
27
+ return {
28
+ status: r.data?.status ?? "none",
29
+ planId: r.data?.planId ?? "free",
30
+ currentPeriodStart: r.data?.currentPeriodStart,
31
+ currentPeriodEnd: r.data?.currentPeriodEnd
32
+ };
33
+ }
34
+ async function credits(key) {
35
+ const r = await getJson("/alpha/billing/credits", key);
36
+ return {
37
+ credits: {
38
+ monthlyCredits: r.credits?.monthlyCredits ?? 0,
39
+ purchasedCredits: r.credits?.purchasedCredits ?? 0,
40
+ freeCredits: r.credits?.freeCredits ?? 0
41
+ },
42
+ windowLimits: r.windowLimits
43
+ };
44
+ }
45
+ async function usageSummary(key) {
46
+ return getJson("/alpha/usage/summary", key);
47
+ }
48
+ async function providerModels(key) {
49
+ return getJson("/provider/v1/models", key, 15000);
50
+ }
51
+ async function getPlanInfo(key) {
52
+ return subscriptions(key);
53
+ }
54
+
55
+ // src/key.ts
56
+ import { readFile } from "fs/promises";
57
+ import { homedir } from "os";
58
+ async function resolveKey(getAuth) {
59
+ const env = process.env.CMD_API_KEY;
60
+ if (env)
61
+ return env;
62
+ try {
63
+ const auth = await getAuth?.();
64
+ if (auth?.key)
65
+ return auth.key;
66
+ } catch {}
67
+ try {
68
+ const text = await readFile(`${homedir()}/.commandcode/auth.json`, "utf8");
69
+ const v = JSON.parse(text);
70
+ if (typeof v.apiKey === "string" && v.apiKey)
71
+ return v.apiKey;
72
+ } catch {}
73
+ throw new Error("No Command Code API key found. Run /connect, choose Command Code, and paste your key (or `cmd login` in a terminal, or set CMD_API_KEY).");
74
+ }
75
+
76
+ // src/gating.ts
77
+ var MODEL_CATEGORIES = {
78
+ "claude-sonnet-5": "premium",
79
+ "claude-sonnet-4-6": "premium",
80
+ "claude-fable-5": "premium",
81
+ "claude-opus-5": "premium",
82
+ "claude-opus-4-8": "premium",
83
+ "claude-opus-4-7": "premium",
84
+ "claude-haiku-4-5-20251001": "premium",
85
+ "gpt-5.6-sol": "opensource",
86
+ "gpt-5.6-terra": "premium",
87
+ "gpt-5.6-luna": "opensource",
88
+ "gpt-5.5": "premium",
89
+ "gpt-5.4": "premium",
90
+ "gpt-5.3-codex": "premium",
91
+ "gpt-5.4-mini": "premium",
92
+ "google/gemini-3.5-flash": "premium",
93
+ "google/gemini-3.1-flash-lite": "premium",
94
+ "sakana/fugu-ultra": "premium",
95
+ "meta/muse-spark-1.1": "premium",
96
+ "meta/muse-spark-1.2": "opensource",
97
+ "meta/muse-spark-1.2-contributor": "opensource",
98
+ "xai/grok-4.5": "opensource",
99
+ "xai/grok-4.6": "opensource",
100
+ "google/gemini-3.7-flash": "opensource",
101
+ "z-ai/glm-5.3-flash": "opensource",
102
+ "tencent/hy4-preview": "opensource",
103
+ "tencent/hy3-paid": "opensource",
104
+ "tencent/Hy3": "opensource",
105
+ "MiniMaxAI/MiniMax-M3-Free": "opensource",
106
+ "MiniMaxAI/MiniMax-M3": "opensource",
107
+ "MiniMaxAI/MiniMax-M2.7": "opensource",
108
+ "minimax/minimax-m3-free": "opensource",
109
+ "minimax/minimax-m2.7-free": "opensource",
110
+ "MiniMaxAI/MiniMax-M2.5": "opensource",
111
+ "deepseek/deepseek-v4-pro": "opensource",
112
+ "deepseek/deepseek-v4-flash": "opensource",
113
+ "moonshotai/Kimi-K3": "opensource",
114
+ "moonshotai/Kimi-K2.7-Code": "opensource",
115
+ "moonshotai/Kimi-K2.7-Code-Highspeed": "opensource",
116
+ "moonshotai/Kimi-K2.6": "opensource",
117
+ "moonshotai/Kimi-K2.5": "opensource",
118
+ "zai-org/GLM-5.3": "opensource",
119
+ "zai-org/GLM-5.2": "opensource",
120
+ "zai-org/GLM-5.2-Fast": "opensource",
121
+ "zai-org/GLM-5.1": "opensource",
122
+ "zai-org/GLM-5": "opensource",
123
+ "xiaomi/mimo-v2.5-pro": "opensource",
124
+ "xiaomi/mimo-v2.5": "opensource",
125
+ "Qwen/Qwen3.6-Max-Preview": "opensource",
126
+ "Qwen/Qwen3.6-Plus": "opensource",
127
+ "Qwen/Qwen3.7-Max": "opensource",
128
+ "Qwen/Qwen3.7-Plus": "opensource",
129
+ "Qwen/Qwen3.8-Max": "opensource",
130
+ "Qwen/Qwen3.8-27B": "opensource",
131
+ "Qwen/Qwen3.8-Flash": "opensource",
132
+ "Qwen/Qwen3.7-Flash": "opensource",
133
+ "stepfun/Step-3.7-Flash": "opensource",
134
+ "stepfun/Step-3.5-Flash": "opensource",
135
+ "nvidia/nemotron-3-ultra-550b-a55b": "opensource",
136
+ "thinkingmachines/inkling": "opensource"
137
+ };
138
+ var PLAN_RULES = {
139
+ "individual-go": {
140
+ allowedCategories: ["opensource"],
141
+ blockedModels: [
142
+ "vercel-ai-gateway:meta/muse-spark-1.2",
143
+ "vercel-ai-gateway:xai/grok-4.6",
144
+ "openrouter:google/gemini-3.7-flash",
145
+ "vercel-ai-gateway:gpt-5.6-sol"
146
+ ]
147
+ },
148
+ "individual-goat": {
149
+ allowedCategories: ["opensource"],
150
+ blockedModels: []
151
+ },
152
+ "individual-pro": {
153
+ allowedCategories: ["premium", "opensource"],
154
+ blockedModels: [
155
+ "anthropic:claude-fable-5",
156
+ "anthropic:claude-opus-5",
157
+ "anthropic:claude-opus-4-8",
158
+ "anthropic:claude-opus-4-7",
159
+ "anthropic:claude-opus-4-6",
160
+ "anthropic:claude-opus-4-5-20251101",
161
+ "vercel-ai-gateway:sakana/fugu-ultra"
162
+ ]
163
+ },
164
+ "individual-pro-v1": {
165
+ allowedCategories: ["premium", "opensource"],
166
+ blockedModels: [
167
+ "anthropic:claude-fable-5",
168
+ "anthropic:claude-opus-5",
169
+ "anthropic:claude-opus-4-8",
170
+ "anthropic:claude-opus-4-7",
171
+ "anthropic:claude-opus-4-6",
172
+ "anthropic:claude-opus-4-5-20251101",
173
+ "vercel-ai-gateway:sakana/fugu-ultra"
174
+ ]
175
+ },
176
+ "individual-provider": {
177
+ allowedCategories: ["premium", "opensource"],
178
+ blockedModels: []
179
+ },
180
+ "individual-max": {
181
+ allowedCategories: ["premium", "opensource"],
182
+ blockedModels: []
183
+ },
184
+ "individual-ultra": {
185
+ allowedCategories: ["premium", "opensource"],
186
+ blockedModels: []
187
+ },
188
+ "teams-pro": {
189
+ allowedCategories: ["premium", "opensource"],
190
+ blockedModels: []
191
+ }
192
+ };
193
+ var KNOWN_MODELS = [
194
+ "claude-sonnet-4-6",
195
+ "claude-fable-5",
196
+ "claude-opus-5",
197
+ "claude-opus-4-8",
198
+ "claude-opus-4-7",
199
+ "claude-haiku-4-5-20251001",
200
+ "gpt-5.6-sol",
201
+ "gpt-5.6-terra",
202
+ "gpt-5.6-luna",
203
+ "gpt-5.5",
204
+ "gpt-5.4",
205
+ "gpt-5.3-codex",
206
+ "gpt-5.4-mini",
207
+ "moonshotai/Kimi-K3",
208
+ "thinkingmachines/inkling",
209
+ "thinkingmachines/inkling-small",
210
+ "deepseek/deepseek-v4-pro",
211
+ "deepseek/deepseek-v4-flash",
212
+ "deepseek/deepseek-v4-flash-vision-exp",
213
+ "moonshotai/Kimi-K2.7-Code",
214
+ "moonshotai/Kimi-K2.7-Code-Highspeed",
215
+ "moonshotai/Kimi-K2.6",
216
+ "moonshotai/Kimi-K2.5",
217
+ "zai-org/GLM-5.3",
218
+ "z-ai/glm-5.3-flash",
219
+ "zai-org/GLM-5.2",
220
+ "zai-org/GLM-5.2-Fast",
221
+ "zai-org/GLM-5.1",
222
+ "zai-org/GLM-5",
223
+ "xiaomi/mimo-v2.5-pro",
224
+ "xiaomi/mimo-v2.5",
225
+ "stepfun/Step-3.7-Flash",
226
+ "stepfun/Step-3.5-Flash",
227
+ "tencent/hy4-preview",
228
+ "tencent/hy3-paid",
229
+ "tencent/Hy3",
230
+ "google/gemini-3.7-flash",
231
+ "google/gemini-3.6-flash",
232
+ "google/gemini-3.5-flash",
233
+ "google/gemini-3.5-flash-lite",
234
+ "google/gemini-3.1-flash-lite",
235
+ "sakana/fugu-ultra",
236
+ "xai/grok-4.5",
237
+ "xai/grok-4.6",
238
+ "meta/muse-spark-1.1",
239
+ "meta/muse-spark-1.2",
240
+ "meta/muse-spark-1.2-contributor",
241
+ "nvidia/nemotron-3-ultra-550b-a55b",
242
+ "poolside/laguna-s-2.1-free",
243
+ "inclusionai/ling-3.0-flash-free",
244
+ "minimax/minimax-m3-free",
245
+ "minimax/minimax-m2.7-free",
246
+ "claude-sonnet-5",
247
+ "MiniMaxAI/MiniMax-M3-Free",
248
+ "MiniMaxAI/MiniMax-M3",
249
+ "MiniMaxAI/MiniMax-M2.7",
250
+ "MiniMaxAI/MiniMax-M2.5",
251
+ "Qwen/Qwen3.6-Max-Preview",
252
+ "Qwen/Qwen3.6-Plus",
253
+ "Qwen/Qwen3.7-Max",
254
+ "Qwen/Qwen3.7-Plus",
255
+ "Qwen/Qwen3.8-Max",
256
+ "Qwen/Qwen3.8-27B",
257
+ "Qwen/Qwen3.8-Flash",
258
+ "Qwen/Qwen3.7-Flash"
259
+ ];
260
+ var MODEL_ALIASES = {
261
+ "claude-sonnet-4-20250514": "claude-sonnet-4-6",
262
+ "claude-sonnet-4-5-20250929": "claude-sonnet-4-6",
263
+ "claude-opus-4-5-20251101": "claude-opus-4-7",
264
+ "claude-opus-4-6": "claude-opus-4-7",
265
+ "claude-haiku-4-5": "claude-haiku-4-5-20251001"
266
+ };
267
+ function findKnown(s) {
268
+ const k = s.toLowerCase();
269
+ return KNOWN_MODELS.find((m) => m.toLowerCase() === k);
270
+ }
271
+ function canonicalizeModelId(model) {
272
+ const stripDate = (s) => s.replace(/[-@]\d{8}$/, "");
273
+ const direct = findKnown(model);
274
+ if (direct)
275
+ return direct;
276
+ const aliased = MODEL_ALIASES[model.toLowerCase()];
277
+ if (aliased)
278
+ return findKnown(aliased) ?? model;
279
+ return findKnown(stripDate(model)) ?? model;
280
+ }
281
+
282
+ // src/access.ts
283
+ var HARD_BLOCKED = {
284
+ "individual-goat": [
285
+ "meta/muse-spark-1.1",
286
+ "google/gemini-3.5-flash",
287
+ "google/gemini-3.6-flash",
288
+ "google/gemini-3.5-flash-lite",
289
+ "google/gemini-3.1-flash-lite"
290
+ ],
291
+ "individual-go": [
292
+ "meta/muse-spark-1.1",
293
+ "google/gemini-3.5-flash",
294
+ "google/gemini-3.6-flash",
295
+ "google/gemini-3.5-flash-lite",
296
+ "google/gemini-3.1-flash-lite",
297
+ "meta/muse-spark-1.2",
298
+ "meta/muse-spark-1.2-contributor",
299
+ "meta/muse-spark-1.3",
300
+ "meta/muse-spark-1.3-contributor"
301
+ ]
302
+ };
303
+ function evaluateModelAccess(model, plan) {
304
+ if (plan.purchasedCredits > 0 || plan.freeCredits > 0)
305
+ return { allowed: true };
306
+ if (!plan.planId)
307
+ return { allowed: true };
308
+ const canonical = canonicalizeModelId(model);
309
+ if ((HARD_BLOCKED[plan.planId] ?? []).some((m) => m.toLowerCase() === canonical.toLowerCase())) {
310
+ return { allowed: false };
311
+ }
312
+ const rules = PLAN_RULES[plan.planId];
313
+ if (!rules)
314
+ return { allowed: true };
315
+ const category = MODEL_CATEGORIES[canonical] ?? siblingCategory(canonical);
316
+ if (!category)
317
+ return { allowed: true };
318
+ const blocked = rules.blockedModels.some((b) => {
319
+ const bare = b.includes(":") ? b.slice(b.indexOf(":") + 1) : b;
320
+ return bare.toLowerCase() === canonical.toLowerCase();
321
+ });
322
+ if (blocked)
323
+ return { allowed: false };
324
+ if (!rules.allowedCategories.includes(category))
325
+ return { allowed: false };
326
+ return { allowed: true };
327
+ }
328
+ function filterByPlan(models, plan) {
329
+ return models.filter((m) => evaluateModelAccess(m.id, plan).allowed);
330
+ }
331
+ var KNOWN_KEYS = Object.keys(MODEL_CATEGORIES);
332
+ function siblingCategory(model) {
333
+ const lower = model.toLowerCase();
334
+ let best;
335
+ let bestLen = 0;
336
+ for (const k of KNOWN_KEYS) {
337
+ if (lower.startsWith(k.toLowerCase()) && k.length > bestLen) {
338
+ best = k;
339
+ bestLen = k.length;
340
+ }
341
+ }
342
+ return best ? MODEL_CATEGORIES[best] : undefined;
343
+ }
344
+
345
+ // src/models.ts
346
+ var CACHE_TTL_MS = 5 * 60 * 1000;
347
+ var cache = null;
348
+ function isClaude(id) {
349
+ const bare = id.includes("/") ? id.slice(id.lastIndexOf("/") + 1) : id;
350
+ return bare.toLowerCase().startsWith("claude");
351
+ }
352
+ function splitModels(models) {
353
+ const claude = [];
354
+ const open = [];
355
+ for (const m of models)
356
+ (isClaude(m.id) ? claude : open).push(m);
357
+ return { claude, open };
358
+ }
359
+ async function loadModels(key, opts) {
360
+ let models;
361
+ let source;
362
+ if (cache && Date.now() - cache.at < CACHE_TTL_MS) {
363
+ models = cache.models;
364
+ source = "cache";
365
+ } else {
366
+ try {
367
+ const resp = await providerModels(key);
368
+ models = (resp.data ?? []).map((m) => ({
369
+ id: m.id,
370
+ name: m.name ?? m.id,
371
+ contextLength: m.context_length ?? 0
372
+ }));
373
+ cache = { at: Date.now(), models };
374
+ source = "live";
375
+ } catch {
376
+ if (cache) {
377
+ models = cache.models;
378
+ source = "stale";
379
+ } else {
380
+ throw new Error("Could not fetch Command Code model list (https://api.commandcode.ai/provider/v1/models). Check network/API key.");
381
+ }
382
+ }
383
+ }
384
+ let plan = { planId: "", purchasedCredits: 0, freeCredits: 0 };
385
+ let gatedOut = [];
386
+ if (!opts?.skipPlan) {
387
+ try {
388
+ const sub = await getPlanInfo(key);
389
+ const cr = await credits(key);
390
+ plan = {
391
+ planId: sub.planId,
392
+ purchasedCredits: cr.credits.purchasedCredits ?? 0,
393
+ freeCredits: cr.credits.freeCredits ?? 0
394
+ };
395
+ } catch {}
396
+ }
397
+ const allowed = filterByPlan(models, plan);
398
+ const allowedIds = new Set(allowed.map((m) => m.id));
399
+ gatedOut = models.filter((m) => !allowedIds.has(m.id)).map((m) => m.id);
400
+ const { claude, open } = splitModels(allowed);
401
+ return { claude, open, gatedOut, source };
402
+ }
403
+
404
+ // src/usage.ts
405
+ var BAR_WIDTH = 12;
406
+ function money(v) {
407
+ return `$${v.toFixed(2)}`;
408
+ }
409
+ function compact(n) {
410
+ if (n >= 1e6)
411
+ return `${(n / 1e6).toFixed(1)}M`;
412
+ if (n >= 1000)
413
+ return `${(n / 1000).toFixed(1)}K`;
414
+ return `${n}`;
415
+ }
416
+ function bar(used, cap) {
417
+ const pct = cap > 0 ? Math.min(Math.max(used / cap, 0), 1) : 0;
418
+ const filled = Math.round(pct * BAR_WIDTH);
419
+ return "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
420
+ }
421
+ function pctStr(used, cap) {
422
+ return cap > 0 ? `${Math.round(used / cap * 100)}%` : "\u2014";
423
+ }
424
+ function relTime(resetAtMs, nowSecs) {
425
+ if (resetAtMs === undefined)
426
+ return "unknown";
427
+ const resetSecs = Math.floor(resetAtMs / 1000);
428
+ if (resetSecs <= nowSecs)
429
+ return "resetting\u2026";
430
+ const diff = resetSecs - nowSecs;
431
+ const d = Math.floor(diff / 86400);
432
+ const h = Math.floor(diff % 86400 / 3600);
433
+ const m = Math.floor(diff % 3600 / 60);
434
+ if (d > 0)
435
+ return `${d}d ${h}h`;
436
+ if (h > 0)
437
+ return `${h}h ${m}m`;
438
+ if (m > 0)
439
+ return `${m}m`;
440
+ return "<1m";
441
+ }
442
+ function parseIsoUtc(s) {
443
+ const m = s.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)Z?/);
444
+ if (!m)
445
+ return;
446
+ const [, y, mo, d, h, mi, sec] = m;
447
+ return Date.UTC(+y, +mo - 1, +d, +h, +mi, Math.floor(+sec));
448
+ }
449
+ var PLAN_CAPS = {
450
+ "individual-go": 10,
451
+ "individual-goat": 70,
452
+ "individual-pro": 80,
453
+ "individual-provider": 0,
454
+ "individual-max": 150,
455
+ "individual-ultra": 0,
456
+ "teams-pro": 40
457
+ };
458
+ function planMonthlyCap(planId) {
459
+ if (planId.includes("max-20"))
460
+ return 300;
461
+ const cap = PLAN_CAPS[planId];
462
+ return cap && cap > 0 ? cap : undefined;
463
+ }
464
+ function planName(planId) {
465
+ const id = planId.toLowerCase();
466
+ if (id.includes("max-20"))
467
+ return "Max 20x";
468
+ if (id.includes("max"))
469
+ return "Max 10x";
470
+ if (id.includes("goat"))
471
+ return "GOAT";
472
+ if (id.includes("provider"))
473
+ return "Provider";
474
+ if (id.includes("ultra"))
475
+ return "Ultra";
476
+ if (id.includes("team"))
477
+ return "Team Pro";
478
+ if (id.includes("pro"))
479
+ return "Pro";
480
+ if (id.includes("go"))
481
+ return "Go";
482
+ return "Free";
483
+ }
484
+ function windowLine(label, w, nowSecs) {
485
+ const status = w.exceeded ? " \xB7 **LIMIT EXCEEDED**" : "";
486
+ return `**${label}** \`${bar(w.used, w.cap)}\` ${money(w.used)} of ${money(w.cap)} (${pctStr(w.used, w.cap)}) \xB7 resets in ${relTime(w.resetAt, nowSecs)}${status}
487
+ `;
488
+ }
489
+ function plansTable(current) {
490
+ const plans = [
491
+ ["go", "Go", "$1", "$10", "$3", "$6"],
492
+ ["goat", "GOAT", "$10", "$70", "$14", "$35"],
493
+ ["pro", "Pro", "$20", "$80", "$16", "$40"],
494
+ ["provider", "Provider", "$15", "PAYG", "\u2014", "\u2014"],
495
+ ["max-10", "Max 10x", "$100", "$150", "$45", "$90"],
496
+ ["max-20", "Max 20x", "$200", "$300", "$90", "$180"],
497
+ ["team-pro", "Team Pro", "$40", "$40", "$12", "$24"]
498
+ ];
499
+ const cur = current.toLowerCase();
500
+ let out = `| Plan | Price | Credits/mo | 5-hour | Weekly |
501
+ |---|---|---|---|---|
502
+ `;
503
+ for (const [id, name, price, monthly, h5, wk] of plans) {
504
+ const mark = cur.includes(id) ? "**" : "";
505
+ out += `| ${mark}${name}${mark} | ${price}/mo | ${monthly} | ${h5} | ${wk} |
506
+ `;
507
+ }
508
+ out += "\nWindows throttle only included monthly credits; on-demand (`/extra`) credits are never throttled.\n";
509
+ return out;
510
+ }
511
+ async function renderUsage(key) {
512
+ const nowSecs = Math.floor(Date.now() / 1000);
513
+ const [sub, cr, summary] = await Promise.all([
514
+ subscriptions(key),
515
+ credits(key),
516
+ usageSummary(key).catch(() => {
517
+ return;
518
+ })
519
+ ]);
520
+ const lines = [];
521
+ lines.push(`## Command Code \u2014 ${planName(sub.planId)} (${sub.status})
522
+ `);
523
+ if (sub.currentPeriodEnd)
524
+ lines.push(`Billing period ends \`${sub.currentPeriodEnd.slice(0, 10)}\`
525
+ `);
526
+ const cap = planMonthlyCap(sub.planId);
527
+ if (cap) {
528
+ lines.push(`**Credits:** ${money(cr.credits.monthlyCredits)} / ${money(cap)} monthly \xB7 ${money(cr.credits.purchasedCredits ?? 0)} purchased \xB7 ${money(cr.credits.freeCredits ?? 0)} free
529
+ `);
530
+ } else {
531
+ lines.push(`**Credits remaining:** ${money(cr.credits.monthlyCredits)} monthly \xB7 ${money(cr.credits.purchasedCredits ?? 0)} purchased \xB7 ${money(cr.credits.freeCredits ?? 0)} free
532
+ `);
533
+ }
534
+ lines.push("### Usage windows");
535
+ if (cap) {
536
+ const used = Math.min(Math.max(cap - cr.credits.monthlyCredits, 0), cap);
537
+ const resetAt = sub.currentPeriodEnd ? parseIsoUtc(sub.currentPeriodEnd) : undefined;
538
+ lines.push(windowLine("Monthly", { used, cap, resetAt }, nowSecs));
539
+ }
540
+ const five = cr.windowLimits?.fiveHour;
541
+ const weekly = cr.windowLimits?.weekly;
542
+ if (five)
543
+ lines.push(windowLine("5-hour", five, nowSecs));
544
+ if (weekly)
545
+ lines.push(windowLine("Weekly", weekly, nowSecs));
546
+ if (!five && !weekly)
547
+ lines.push(`No rolling windows on this plan (pay-as-you-go credits only).
548
+ `);
549
+ if (summary) {
550
+ lines.push("### This billing period");
551
+ lines.push("| Metric | Value |", "|---|---|");
552
+ lines.push(`| Requests | ${summary.totalCount ?? "\u2014"} |`);
553
+ lines.push(`| Cost | ${summary.totalCost !== undefined ? money(summary.totalCost) : "\u2014"} |`);
554
+ lines.push(`| Tokens in / out | ${summary.totalTokensIn !== undefined ? compact(summary.totalTokensIn) : "\u2014"} / ${summary.totalTokensOut !== undefined ? compact(summary.totalTokensOut) : "\u2014"} |`);
555
+ lines.push(`| Success rate | ${summary.successRate !== undefined ? `${Math.round(summary.successRate)}%` : "\u2014"} |`);
556
+ lines.push("");
557
+ }
558
+ lines.push("### All plans");
559
+ lines.push(plansTable(sub.planId));
560
+ return lines.join(`
561
+ `);
562
+ }
563
+
564
+ // src/index.ts
565
+ var PROVIDER_BASE = "https://api.commandcode.ai/provider/v1";
566
+ function toModelDefs(models, providerID, npm) {
567
+ return Object.fromEntries(models.map((m) => [
568
+ m.id,
569
+ {
570
+ id: m.id,
571
+ providerID,
572
+ api: { id: m.id, url: PROVIDER_BASE, npm },
573
+ name: m.name,
574
+ capabilities: {
575
+ temperature: true,
576
+ reasoning: true,
577
+ attachment: false,
578
+ toolcall: true,
579
+ interleaved: false,
580
+ input: { text: true, audio: false, image: false, video: false, pdf: false },
581
+ output: { text: true, audio: false, image: false, video: false, pdf: false }
582
+ },
583
+ cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
584
+ limit: { context: m.contextLength || 128000, output: 32000 },
585
+ status: "active",
586
+ options: {},
587
+ headers: {},
588
+ release_date: ""
589
+ }
590
+ ]));
591
+ }
592
+ var CommandCodePlugin = async (_input) => {
593
+ return {
594
+ auth: {
595
+ provider: "command-code",
596
+ loader: async (getAuth) => {
597
+ const auth = await getAuth();
598
+ if (auth?.type !== "api") {
599
+ throw new Error("No API key available. Please run '/connect' and choose Command Code.");
600
+ }
601
+ return { apiKey: auth.key, baseURL: PROVIDER_BASE };
602
+ },
603
+ methods: [
604
+ {
605
+ type: "api",
606
+ label: "Command Code API key",
607
+ prompts: [
608
+ {
609
+ type: "text",
610
+ key: "apiKey",
611
+ message: "Command Code API key (create at commandcode.ai/settings/keys)"
612
+ }
613
+ ],
614
+ async authorize(inputs) {
615
+ const key = inputs?.apiKey?.trim();
616
+ if (!key)
617
+ return { type: "failed" };
618
+ try {
619
+ const me = await whoami(key);
620
+ if (!me.success)
621
+ return { type: "failed" };
622
+ return { type: "success", key, provider: "command-code" };
623
+ } catch {
624
+ return { type: "failed" };
625
+ }
626
+ }
627
+ }
628
+ ]
629
+ },
630
+ config: async (cfg) => {
631
+ cfg.provider ??= {};
632
+ const existing = (id) => cfg.provider?.[id] ?? {};
633
+ let split;
634
+ try {
635
+ split = await loadModels(await resolveKey());
636
+ } catch {}
637
+ const claudeDefs = split ? toModelDefs(split.claude, "command-code", "@ai-sdk/anthropic") : {};
638
+ const openDefs = split ? toModelDefs(split.open, "command-code-open", "@ai-sdk/openai-compatible") : {};
639
+ const userCc = existing("command-code");
640
+ cfg.provider["command-code"] = {
641
+ npm: userCc.options ? undefined : "@ai-sdk/anthropic",
642
+ name: "Command Code",
643
+ options: { baseURL: PROVIDER_BASE, ...userCc.options },
644
+ models: { ...claudeDefs, ...userCc.models }
645
+ };
646
+ const userOpen = existing("command-code-open");
647
+ let openKey;
648
+ try {
649
+ openKey = await resolveKey();
650
+ } catch {}
651
+ cfg.provider["command-code-open"] = {
652
+ npm: "@ai-sdk/openai-compatible",
653
+ name: userOpen.options ? undefined : "Command Code (Open)",
654
+ options: {
655
+ baseURL: PROVIDER_BASE,
656
+ ...openKey ? { apiKey: openKey } : {},
657
+ ...userOpen.options
658
+ },
659
+ models: { ...openDefs, ...userOpen.models }
660
+ };
661
+ cfg.command ??= {};
662
+ cfg.command["cmd-usage"] = {
663
+ description: "Show Command Code plan, credits, and usage windows",
664
+ 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) and retry. $ARGUMENTS"
665
+ };
666
+ },
667
+ provider: {
668
+ id: "command-code",
669
+ models: async (_provider, ctx) => {
670
+ const key = await resolveKey(async () => {
671
+ const a = ctx.auth;
672
+ return a?.type === "api" && a.key ? { key: a.key } : undefined;
673
+ });
674
+ const split = await loadModels(key);
675
+ return toModelDefs(split.claude, "command-code", "@ai-sdk/anthropic");
676
+ }
677
+ },
678
+ tool: {
679
+ cmd_usage: tool({
680
+ 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.",
681
+ args: {
682
+ arg: tool.schema.string().optional().describe("Optional: 'plans' for the plan table only")
683
+ },
684
+ async execute(args) {
685
+ if (args.arg === "plans")
686
+ return plansTable("");
687
+ const key = await resolveKey();
688
+ return renderUsage(key);
689
+ }
690
+ })
691
+ }
692
+ };
693
+ };
694
+ var src_default = CommandCodePlugin;
695
+ export {
696
+ CommandCodePlugin,
697
+ src_default as default
698
+ };
package/dist/key.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare function resolveKey(
2
+ getAuth?: () => Promise<
3
+ | {
4
+ key: string;
5
+ }
6
+ | undefined
7
+ >,
8
+ ): Promise<string>;
@@ -0,0 +1,23 @@
1
+ export type CmdModel = {
2
+ id: string;
3
+ name: string;
4
+ contextLength: number;
5
+ };
6
+ export type ModelSplit = {
7
+ claude: CmdModel[];
8
+ open: CmdModel[];
9
+ gatedOut: string[];
10
+ source: "live" | "cache" | "stale";
11
+ };
12
+ export declare function isClaude(id: string): boolean;
13
+ export declare function splitModels(models: CmdModel[]): {
14
+ claude: CmdModel[];
15
+ open: CmdModel[];
16
+ };
17
+ /** Fetch live model list, apply plan gating, split by wire protocol. */
18
+ export declare function loadModels(
19
+ key: string,
20
+ opts?: {
21
+ skipPlan?: boolean;
22
+ },
23
+ ): Promise<ModelSplit>;
@@ -0,0 +1,21 @@
1
+ import { type Window } from "./api";
2
+ export declare function money(v: number): string;
3
+ export declare function compact(n: number): string;
4
+ export declare function bar(used: number, cap: number): string;
5
+ export declare function pctStr(used: number, cap: number): string;
6
+ export declare function relTime(
7
+ resetAtMs: number | undefined,
8
+ nowSecs: number,
9
+ ): string;
10
+ /** ISO date → epoch ms (UTC, no offset handling — off by hours at most). */
11
+ export declare function parseIsoUtc(s: string): number | undefined;
12
+ export declare function planMonthlyCap(planId: string): number | undefined;
13
+ export declare function planName(planId: string): string;
14
+ export declare function windowLine(
15
+ label: string,
16
+ w: Window,
17
+ nowSecs: number,
18
+ ): string;
19
+ export declare function plansTable(current: string): string;
20
+ /** Fetch billing data in parallel and render the markdown dashboard. */
21
+ export declare function renderUsage(key: string): Promise<string>;
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@jeffreyjyz/opencode-command-code",
3
+ "version": "0.1.0",
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.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "bun build src/index.ts --outdir dist --target bun --external @opencode-ai/plugin --external @opencode-ai/sdk && tsc --emitDeclarationOnly --declaration --outDir dist",
10
+ "test": "bun test",
11
+ "extract": "bun run scripts/extract-gating.ts",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@opencode-ai/plugin": "*"
16
+ },
17
+ "devDependencies": {
18
+ "@types/bun": "latest",
19
+ "typescript": "^5"
20
+ },
21
+ "files": ["dist", "README.md", "LICENSE"],
22
+ "keywords": ["opencode", "command-code", "plugin", "provider", "ai-sdk"],
23
+ "license": "MIT",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ }
27
+ }