@gururea/opencode-commandcode-provider 0.3.0 → 0.4.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/dist/cli.js DELETED
@@ -1,763 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/cli.ts
4
- import { readFileSync as readFileSync2 } from "fs";
5
- import { homedir } from "os";
6
- import { dirname as dirname2, join } from "path";
7
- import { fileURLToPath } from "url";
8
-
9
- // src/api-key.ts
10
- import { existsSync, readFileSync } from "fs";
11
- var PROVIDER_ID = "commandcode";
12
- function isRecord(value) {
13
- return typeof value === "object" && value !== null && !Array.isArray(value);
14
- }
15
- function authKey(record) {
16
- const type = record.type;
17
- if (type === "api") return typeof record.key === "string" ? record.key : void 0;
18
- if (type === "oauth") return typeof record.access === "string" ? record.access : void 0;
19
- if (type === "wellknown") {
20
- return typeof record.token === "string" ? record.token : typeof record.key === "string" ? record.key : void 0;
21
- }
22
- return void 0;
23
- }
24
- function credentialFromAuthJson(raw) {
25
- try {
26
- const parsed = JSON.parse(raw);
27
- if (!isRecord(parsed)) return void 0;
28
- const entry = parsed[PROVIDER_ID];
29
- if (!isRecord(entry)) return void 0;
30
- return authKey(entry);
31
- } catch {
32
- return void 0;
33
- }
34
- }
35
- function defaultAuthPath() {
36
- return `${process.env.HOME ?? ""}/.local/share/opencode/auth.json`;
37
- }
38
- function readCommandCodeKey(options = {}) {
39
- const env = options.env ?? process.env;
40
- if (env.COMMAND_CODE_API_KEY) return env.COMMAND_CODE_API_KEY;
41
- if (env.COMMANDCODE_API_KEY) return env.COMMANDCODE_API_KEY;
42
- const authContent = options.authContent ?? env.OPENCODE_AUTH_CONTENT;
43
- if (authContent) {
44
- const fromContent = credentialFromAuthJson(authContent);
45
- if (fromContent) return fromContent;
46
- }
47
- const authPath = options.authPath ?? defaultAuthPath();
48
- try {
49
- if (!existsSync(authPath)) return void 0;
50
- const raw = readFileSync(authPath, "utf-8");
51
- return credentialFromAuthJson(raw);
52
- } catch {
53
- return void 0;
54
- }
55
- }
56
-
57
- // src/catalog.ts
58
- var FALLBACK_MODELS = [
59
- { id: "claude-sonnet-5", name: "Claude Sonnet 5", contextLength: 1e6 },
60
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", contextLength: 1e6 },
61
- { id: "claude-fable-5", name: "Claude Fable 5", contextLength: 1e6 },
62
- { id: "claude-opus-5", name: "Claude Opus 5", contextLength: 1e6 },
63
- { id: "claude-opus-4-8", name: "Claude Opus 4.8", contextLength: 1e6 },
64
- { id: "claude-opus-4-7", name: "Claude Opus 4.7", contextLength: 1e6 },
65
- { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", contextLength: 2e5 },
66
- { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextLength: 105e4 },
67
- { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextLength: 105e4 },
68
- { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextLength: 105e4 },
69
- { id: "gpt-5.5", name: "GPT-5.5", contextLength: 4e5 },
70
- { id: "gpt-5.4", name: "GPT-5.4", contextLength: 4e5 },
71
- { id: "gpt-5.3-codex", name: "GPT-5.3 Codex", contextLength: 4e5 },
72
- { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", contextLength: 4e5 },
73
- { id: "deepseek/deepseek-v4-pro", name: "DeepSeek V4 Pro (latest)", contextLength: 1e6 },
74
- { id: "deepseek/deepseek-v4-flash", name: "DeepSeek V4 Flash (latest)", contextLength: 1e6 },
75
- { id: "deepseek/deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision (exp)", contextLength: 1e6 },
76
- { id: "moonshotai/Kimi-K3", name: "Kimi K3", contextLength: 1e6 },
77
- { id: "moonshotai/Kimi-K2.7-Code", name: "Kimi K2.7 Code", contextLength: 256e3 },
78
- { id: "moonshotai/Kimi-K2.7-Code-Highspeed", name: "Kimi K2.7 Code HighSpeed", contextLength: 262e3 },
79
- { id: "moonshotai/Kimi-K2.6", name: "Kimi K2.6", contextLength: 256e3 },
80
- { id: "moonshotai/Kimi-K2.5", name: "Kimi K2.5", contextLength: 256e3 },
81
- { id: "z-ai/glm-5.3-flash", name: "GLM-5.3 Flash", contextLength: 1048576 },
82
- { id: "zai-org/GLM-5.3", name: "GLM-5.3", contextLength: 1e6 },
83
- { id: "zai-org/GLM-5.2", name: "GLM-5.2", contextLength: 1e6 },
84
- { id: "zai-org/GLM-5.2-Fast", name: "GLM-5.2 Fast", contextLength: 1e6 },
85
- { id: "zai-org/GLM-5.1", name: "GLM-5.1", contextLength: 2e5 },
86
- { id: "zai-org/GLM-5", name: "GLM-5", contextLength: 2e5 },
87
- { id: "MiniMaxAI/MiniMax-M3", name: "MiniMax M3", contextLength: 1e6 },
88
- { id: "MiniMaxAI/MiniMax-M2.7", name: "MiniMax M2.7", contextLength: 2e5 },
89
- { id: "minimax/minimax-m3-free", name: "MiniMax M3", contextLength: 1e6 },
90
- { id: "minimax/minimax-m2.7-free", name: "MiniMax M2.7", contextLength: 197e3 },
91
- { id: "MiniMaxAI/MiniMax-M2.5", name: "MiniMax M2.5", contextLength: 2e5 },
92
- { id: "xiaomi/mimo-v2.5-pro", name: "MiMo V2.5 Pro", contextLength: 1e6 },
93
- { id: "xiaomi/mimo-v2.5", name: "MiMo V2.5", contextLength: 1e6 },
94
- { id: "Qwen/Qwen3.8-Max", name: "Qwen 3.8 Max", contextLength: 1e6 },
95
- { id: "Qwen/Qwen3.8-27B", name: "Qwen 3.8 27B", contextLength: 262144 },
96
- { id: "Qwen/Qwen3.8-Flash", name: "Qwen 3.8 Flash", contextLength: 1e6 },
97
- { id: "Qwen/Qwen3.7-Max", name: "Qwen 3.7 Max", contextLength: 1e6 },
98
- { id: "Qwen/Qwen3.7-Plus", name: "Qwen 3.7 Plus", contextLength: 1e6 },
99
- { id: "Qwen/Qwen3.7-Flash", name: "Qwen 3.7 Flash", contextLength: 1e6 },
100
- { id: "Qwen/Qwen3.6-Max-Preview", name: "Qwen 3.6 Max Preview", contextLength: 2e5 },
101
- { id: "Qwen/Qwen3.6-Plus", name: "Qwen 3.6 Plus", contextLength: 2e5 },
102
- { id: "stepfun/Step-3.7-Flash", name: "Step 3.7 Flash", contextLength: 256e3 },
103
- { id: "stepfun/Step-3.5-Flash", name: "Step 3.5 Flash", contextLength: 1e6 },
104
- { id: "tencent/hy3-paid", name: "Tencent Hy3", contextLength: 262144 },
105
- { id: "tencent/hy4-preview", name: "Tencent Hy4 Preview", contextLength: 1048576 },
106
- { id: "google/gemini-3.7-flash", name: "Gemini 3.7 Flash", contextLength: 1048576 },
107
- { id: "google/gemini-3.6-flash", name: "Gemini 3.6 Flash", contextLength: 1e6 },
108
- { id: "google/gemini-3.5-flash", name: "Gemini 3.5 Flash", contextLength: 1e6 },
109
- { id: "google/gemini-3.5-flash-lite", name: "Gemini 3.5 Flash Lite", contextLength: 1e6 },
110
- { id: "google/gemini-3.1-flash-lite", name: "Gemini 3.1 Flash Lite", contextLength: 1e6 },
111
- { id: "sakana/fugu-ultra", name: "Fugu Ultra", contextLength: 1e6 },
112
- { id: "nvidia/nemotron-3-ultra-550b-a55b", name: "Nemotron 3 Ultra", contextLength: 1e6 },
113
- { id: "thinkingmachines/inkling-small", name: "Inkling Small", contextLength: 1e6 },
114
- { id: "poolside/laguna-s-2.1-free", name: "Laguna S 2.1", contextLength: 256e3 },
115
- { id: "meta/muse-spark-1.1", name: "Muse Spark 1.1", contextLength: 1048576 },
116
- { id: "meta/muse-spark-1.2", name: "Muse Spark 1.2", contextLength: 1048576 },
117
- { id: "meta/muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", contextLength: 1048576 },
118
- { id: "xai/grok-4.5", name: "Grok 4.5", contextLength: 5e5 },
119
- { id: "xai/grok-4.6", name: "Grok 4.6", contextLength: 5e5 }
120
- ];
121
-
122
- // src/models.ts
123
- import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
124
- import { dirname } from "path";
125
- var DEFAULT_PROVIDER_API_BASE = "https://api.commandcode.ai/provider/v1";
126
- var DEFAULT_MODELS_URL = `${DEFAULT_PROVIDER_API_BASE}/models`;
127
- var DEFAULT_MODELS_TIMEOUT_MS = 1e4;
128
- var DEFAULT_MODELS_TTL_MS = 24 * 60 * 60 * 1e3;
129
- var MODEL_CACHE_VERSION = 1;
130
- function isRecord2(value) {
131
- return typeof value === "object" && value !== null && !Array.isArray(value);
132
- }
133
- function stringField(record, key) {
134
- const value = record[key];
135
- if (typeof value !== "string" || value.length === 0) {
136
- throw new Error(`Expected ${key} to be a non-empty string`);
137
- }
138
- return value;
139
- }
140
- function positiveNumberField(record, key) {
141
- const value = record[key];
142
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
143
- throw new Error(`Expected ${key} to be a positive number`);
144
- }
145
- return value;
146
- }
147
- function commandCodeModelsFromApiResponse(value) {
148
- if (!isRecord2(value)) throw new Error("Expected models response to be an object");
149
- if (value.object !== "list") throw new Error("Expected models response object to be 'list'");
150
- if (!Array.isArray(value.data)) throw new Error("Expected models response data to be an array");
151
- const models = value.data.map((entry) => {
152
- if (!isRecord2(entry)) throw new Error("Expected model entry to be an object");
153
- return {
154
- id: stringField(entry, "id"),
155
- name: stringField(entry, "name"),
156
- contextLength: positiveNumberField(entry, "context_length")
157
- };
158
- });
159
- if (models.length === 0) throw new Error("Command Code returned an empty model catalog");
160
- return models;
161
- }
162
- function errorMessage(error) {
163
- return error instanceof Error ? error.message : String(error);
164
- }
165
- async function runWithTimeout(operation, timeoutMs, externalSignal) {
166
- const controller = new AbortController();
167
- let timer;
168
- let onExternalAbort;
169
- const cleanup = () => {
170
- if (timer !== void 0) clearTimeout(timer);
171
- if (externalSignal && onExternalAbort) {
172
- externalSignal.removeEventListener("abort", onExternalAbort);
173
- }
174
- };
175
- if (externalSignal?.aborted) {
176
- throw externalSignal.reason ?? new Error("The operation was aborted");
177
- }
178
- onExternalAbort = () => controller.abort(externalSignal?.reason);
179
- if (externalSignal && onExternalAbort) {
180
- externalSignal.addEventListener("abort", onExternalAbort, { once: true });
181
- }
182
- timer = setTimeout(() => controller.abort(new Error(`Model discovery timed out after ${timeoutMs}ms`)), timeoutMs);
183
- try {
184
- return await operation(controller.signal);
185
- } finally {
186
- cleanup();
187
- }
188
- }
189
- async function fetchCommandCodeModels(options = {}) {
190
- const url = options.url ?? DEFAULT_MODELS_URL;
191
- const fetchImpl = options.fetchImpl ?? fetch;
192
- const timeoutMs = options.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : DEFAULT_MODELS_TIMEOUT_MS;
193
- const body = await runWithTimeout(
194
- async (signal) => {
195
- const response = await fetchImpl(url, {
196
- headers: { accept: "application/json" },
197
- signal
198
- });
199
- if (!response.ok) {
200
- throw new Error(
201
- `Failed to fetch Command Code models: ${response.status} ${response.statusText}`
202
- );
203
- }
204
- return await response.json();
205
- },
206
- timeoutMs,
207
- options.signal
208
- );
209
- return commandCodeModelsFromApiResponse(body);
210
- }
211
- function cacheIsFresh(cachedAt, now, ttlMs) {
212
- return now - cachedAt < ttlMs;
213
- }
214
- function commandCodeModelsFromCache(value) {
215
- if (!isRecord2(value)) throw new Error("Expected model cache to be an object");
216
- if (value.version !== MODEL_CACHE_VERSION) {
217
- throw new Error(`Expected model cache version ${MODEL_CACHE_VERSION}`);
218
- }
219
- if (!Array.isArray(value.models)) throw new Error("Expected cached models to be an array");
220
- const models = value.models.map((entry) => {
221
- if (!isRecord2(entry)) throw new Error("Expected cached model entry to be an object");
222
- return {
223
- id: stringField(entry, "id"),
224
- name: stringField(entry, "name"),
225
- contextLength: positiveNumberField(entry, "contextLength")
226
- };
227
- });
228
- if (models.length === 0) throw new Error("Expected cached models to be non-empty");
229
- return models;
230
- }
231
- async function readCommandCodeModelsCache(cachePath) {
232
- const contents = await readFile(cachePath, "utf-8");
233
- return commandCodeModelsFromCache(JSON.parse(contents));
234
- }
235
- async function writeCommandCodeModelsCache(cachePath, models, fetchedAt = Date.now()) {
236
- await mkdir(dirname(cachePath), { recursive: true });
237
- const temporaryPath = `${cachePath}.${process.pid}.tmp`;
238
- try {
239
- await writeFile(
240
- temporaryPath,
241
- `${JSON.stringify({ version: MODEL_CACHE_VERSION, fetchedAt, models }, null, 2)}
242
- `,
243
- { encoding: "utf-8", mode: 384 }
244
- );
245
- await rename(temporaryPath, cachePath);
246
- } finally {
247
- try {
248
- await rm(temporaryPath, { force: true });
249
- } catch {
250
- }
251
- }
252
- }
253
- async function loadCommandCodeModels(options) {
254
- const cachePath = options.cachePath;
255
- const ttlMs = options.ttlMs ?? DEFAULT_MODELS_TTL_MS;
256
- const now = options.now ?? Date.now;
257
- try {
258
- const models = await fetchCommandCodeModels(options);
259
- const fetchedAt = now();
260
- try {
261
- await writeCommandCodeModelsCache(cachePath, models, fetchedAt);
262
- return { models, source: "live", fetchedAt };
263
- } catch (error) {
264
- return {
265
- models,
266
- source: "live",
267
- fetchedAt,
268
- warning: `Loaded the live Command Code model catalog but could not write the cache at ${cachePath}: ${errorMessage(error)}`
269
- };
270
- }
271
- } catch (liveError) {
272
- if (options.signal?.aborted) throw liveError;
273
- try {
274
- const cachedAt = await cachedTimestamp(cachePath);
275
- const models = await readCommandCodeModelsCache(cachePath);
276
- const stale = cachedAt !== null && !cacheIsFresh(cachedAt, now(), ttlMs);
277
- return {
278
- models,
279
- source: "cache",
280
- fetchedAt: cachedAt ?? void 0,
281
- warning: stale ? `Could not refresh the Command Code model catalog (${errorMessage(liveError)}). Using a stale cached catalog from ${cachePath}.` : `Could not refresh the Command Code model catalog (${errorMessage(liveError)}). Using the cached catalog from ${cachePath}.`
282
- };
283
- } catch (cacheError) {
284
- const fallback = options.fallbackModels ?? [];
285
- return {
286
- models: fallback,
287
- source: "fallback",
288
- warning: `Could not refresh the Command Code model catalog (${errorMessage(liveError)}) and no valid cached catalog is available at ${cachePath} (${errorMessage(cacheError)}). Using the built-in fallback catalog.`
289
- };
290
- }
291
- }
292
- }
293
- async function cachedTimestamp(cachePath) {
294
- try {
295
- const contents = await readFile(cachePath, "utf-8");
296
- const parsed = JSON.parse(contents);
297
- if (!isRecord2(parsed)) return null;
298
- return typeof parsed.fetchedAt === "number" && Number.isFinite(parsed.fetchedAt) ? parsed.fetchedAt : null;
299
- } catch {
300
- return null;
301
- }
302
- }
303
-
304
- // src/redact.ts
305
- function redactBearer(value) {
306
- return value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]");
307
- }
308
- function redactCredentials(value) {
309
- return value.replace(
310
- /\b(?:api[-_ ]?key|apikey|access[-_ ]?token|refresh[-_ ]?token|token|secret|password|authorization)\s*[=:]\s*[^\s,;&)]+/gi,
311
- (match) => {
312
- const separatorIndex = match.search(/[=:]/);
313
- return separatorIndex < 0 ? "[redacted]" : `${match.slice(0, separatorIndex + 1)}[redacted]`;
314
- }
315
- );
316
- }
317
- function redactUserTokens(value) {
318
- return value.replace(/\b(?:user|cc)_[A-Za-z0-9_-]{8,}\b/gi, "[redacted]");
319
- }
320
- function redactQuerySecrets(value) {
321
- return value.replace(
322
- /([?&](?:api[-_ ]?key|apikey|access_token|refresh_token|token|secret|password)=)[^&#\s]+/gi,
323
- "$1[redacted]"
324
- );
325
- }
326
- function redactStandaloneSecrets(value) {
327
- return value.replace(
328
- /\b(?:sk|rk|ghp|github_pat|xox[baprs])[-_A-Za-z0-9]{16,}\b|\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
329
- "[redacted]"
330
- );
331
- }
332
- function redactCommandCodeErrorText(value) {
333
- return redactStandaloneSecrets(
334
- redactQuerySecrets(redactUserTokens(redactCredentials(redactBearer(value))))
335
- );
336
- }
337
-
338
- // src/quota.ts
339
- var DEFAULT_API_BASE = "https://api.commandcode.ai";
340
- var QUOTA_TIMEOUT_MS = 15e3;
341
- function isRecord3(value) {
342
- return typeof value === "object" && value !== null && !Array.isArray(value);
343
- }
344
- function numberValue(value) {
345
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
346
- }
347
- function stringValue(value) {
348
- return typeof value === "string" && value.length > 0 ? value : void 0;
349
- }
350
- function errorMessage2(error) {
351
- return error instanceof Error ? error.message : String(error);
352
- }
353
- function normalizeResetAt(value) {
354
- let timestamp;
355
- if (typeof value === "number" && Number.isFinite(value)) timestamp = value;
356
- if (typeof value === "string" && value.length > 0) {
357
- const trimmed = value.trim();
358
- timestamp = /^\d+$/.test(trimmed) ? Number(trimmed) : Date.parse(trimmed);
359
- }
360
- if (timestamp === void 0 || !Number.isFinite(timestamp) || timestamp < 0) return null;
361
- return timestamp >= 1e12 ? Math.round(timestamp / 1e3) : timestamp;
362
- }
363
- function windowLimitsFromCredits(value) {
364
- if (!isRecord3(value)) return [];
365
- const limits = [];
366
- for (const [window, entry] of [
367
- ["fiveHour", value.fiveHour],
368
- ["weekly", value.weekly]
369
- ]) {
370
- if (!isRecord3(entry)) continue;
371
- const used = numberValue(entry.used);
372
- const cap = numberValue(entry.cap);
373
- if (used === void 0 || cap === void 0 || used === 0 && cap === 0) continue;
374
- limits.push({ window, used, cap, resetAt: normalizeResetAt(entry.resetAt) });
375
- }
376
- return limits;
377
- }
378
- function parseCredits(value) {
379
- if (!isRecord3(value) || !isRecord3(value.credits)) return null;
380
- const credits = value.credits;
381
- const monthlyCredits = numberValue(credits.monthlyCredits);
382
- const purchasedCredits = numberValue(credits.purchasedCredits);
383
- const freeCredits = numberValue(credits.freeCredits);
384
- if (monthlyCredits === void 0 && purchasedCredits === void 0 && freeCredits === void 0) {
385
- return null;
386
- }
387
- const monthly = monthlyCredits ?? 0;
388
- const purchased = purchasedCredits ?? 0;
389
- const free = freeCredits ?? 0;
390
- return {
391
- monthlyCredits: monthly,
392
- purchasedCredits: purchased,
393
- freeCredits: free,
394
- remainingCredits: monthly + purchased + free,
395
- windowLimits: windowLimitsFromCredits(value.windowLimits)
396
- };
397
- }
398
- function parseSubscription(value) {
399
- if (!isRecord3(value) || !isRecord3(value.data)) return null;
400
- const data = value.data;
401
- const planId = stringValue(data.planId);
402
- const status = stringValue(data.status);
403
- const currentPeriodStart = stringValue(data.currentPeriodStart);
404
- const currentPeriodEnd = stringValue(data.currentPeriodEnd);
405
- if (!planId && !status && !currentPeriodStart && !currentPeriodEnd) return null;
406
- return {
407
- planId: planId ?? null,
408
- status: status ?? null,
409
- currentPeriodStart: currentPeriodStart ?? null,
410
- currentPeriodEnd: currentPeriodEnd ?? null
411
- };
412
- }
413
- function parseSummary(value) {
414
- if (!isRecord3(value)) return null;
415
- const totalCost = numberValue(value.totalCost);
416
- const totalCount = numberValue(value.totalCount);
417
- if (totalCost === void 0 || totalCount === void 0) return null;
418
- const totalTokens = numberValue(value.totalTokens) ?? numberValue(value.tokens);
419
- return { totalCost, totalCount, ...totalTokens === void 0 ? {} : { totalTokens } };
420
- }
421
- function parseWhoami(value) {
422
- if (!isRecord3(value)) return null;
423
- const org = isRecord3(value.org) ? value.org : void 0;
424
- const user = isRecord3(value.user) ? value.user : void 0;
425
- const login = (org ? stringValue(org.login) : void 0) ?? (user ? stringValue(user.userName) ?? stringValue(user.name) : void 0);
426
- if (!login) return null;
427
- const orgId = org ? stringValue(org.id) : void 0;
428
- const keyName = user ? stringValue(user.keyName) ?? stringValue(user.displayName) : void 0;
429
- return { login, orgId: orgId ?? null, ...keyName ? { keyName } : {} };
430
- }
431
- function buildUrl(path, params) {
432
- const search = new URLSearchParams();
433
- for (const [key, value] of Object.entries(params)) {
434
- if (value) search.set(key, value);
435
- }
436
- const query = search.toString();
437
- return `${path}${query ? `?${query}` : ""}`;
438
- }
439
- function isHttpError(value) {
440
- return isRecord3(value) && value.__httpError === true && typeof value.message === "string" && typeof value.status === "number" && typeof value.body === "string";
441
- }
442
- function isQuotaError(value) {
443
- return isRecord3(value) && value.__quotaError === true && (value.kind === "timeout" || value.kind === "network");
444
- }
445
- function isBlockingHttpError(error) {
446
- return error.status === 401 || error.status === 403;
447
- }
448
- function httpFailure(error, context) {
449
- const detail = error.body.trim().slice(0, 200);
450
- return {
451
- ok: false,
452
- error: {
453
- kind: "http",
454
- message: redactCommandCodeErrorText(
455
- `${context} request failed (${error.status}): ${detail || error.message}`
456
- )
457
- }
458
- };
459
- }
460
- var QuotaTimeoutError = class extends Error {
461
- };
462
- async function fetchCommandCodeQuota(options) {
463
- if (!options.apiKey) {
464
- return { ok: false, error: { message: "No Command Code API key found", kind: "config" } };
465
- }
466
- const baseUrl = options.baseUrl ?? DEFAULT_API_BASE;
467
- const fetchImpl = options.fetchImpl ?? fetch;
468
- const timeoutMs = options.timeoutMs ?? QUOTA_TIMEOUT_MS;
469
- const overallController = new AbortController();
470
- const overallTimer = setTimeout(() => overallController.abort(), timeoutMs);
471
- const headers = {
472
- accept: "application/json",
473
- Authorization: `Bearer ${options.apiKey}`,
474
- ...options.extraHeaders
475
- };
476
- const request = async (path) => {
477
- if (overallController.signal.aborted) throw new QuotaTimeoutError();
478
- try {
479
- const response = await fetchImpl(`${baseUrl}${path}`, {
480
- method: "GET",
481
- headers,
482
- signal: overallController.signal
483
- });
484
- if (!response.ok) {
485
- return {
486
- __httpError: true,
487
- message: response.status === 401 || response.status === 403 ? "Command Code rejected the API key" : response.statusText,
488
- status: response.status,
489
- body: await response.text().catch(() => "")
490
- };
491
- }
492
- return await response.json();
493
- } catch (error) {
494
- if (overallController.signal.aborted) throw new QuotaTimeoutError();
495
- throw error;
496
- }
497
- };
498
- const safeRequest = async (path) => {
499
- try {
500
- return await request(path);
501
- } catch (error) {
502
- return {
503
- __quotaError: true,
504
- kind: error instanceof QuotaTimeoutError ? "timeout" : "network"
505
- };
506
- }
507
- };
508
- try {
509
- const whoamiRaw = await request("/alpha/whoami");
510
- if (isHttpError(whoamiRaw)) return httpFailure(whoamiRaw, "whoami");
511
- const account = parseWhoami(whoamiRaw);
512
- if (!account) {
513
- return {
514
- ok: false,
515
- error: { kind: "http", message: "Command Code returned an unrecognized account response" }
516
- };
517
- }
518
- const orgId = account.orgId ?? void 0;
519
- const [creditsRaw, subscriptionRaw] = await Promise.all([
520
- safeRequest(buildUrl("/alpha/billing/credits", { orgId })),
521
- safeRequest(buildUrl("/alpha/billing/subscriptions", { orgId }))
522
- ]);
523
- if (isHttpError(creditsRaw) && isBlockingHttpError(creditsRaw)) {
524
- return httpFailure(creditsRaw, "credits");
525
- }
526
- if (isHttpError(subscriptionRaw) && isBlockingHttpError(subscriptionRaw)) {
527
- return httpFailure(subscriptionRaw, "subscription");
528
- }
529
- const unavailable = [];
530
- const credits = isHttpError(creditsRaw) || isQuotaError(creditsRaw) ? null : parseCredits(creditsRaw);
531
- if (!credits) unavailable.push("credits");
532
- const subscription = isHttpError(subscriptionRaw) || isQuotaError(subscriptionRaw) ? null : parseSubscription(subscriptionRaw);
533
- if (!subscription) unavailable.push("subscription");
534
- const summaryRaw = await safeRequest(
535
- buildUrl("/alpha/usage/summary", {
536
- orgId,
537
- since: subscription?.currentPeriodStart ?? void 0
538
- })
539
- );
540
- if (isHttpError(summaryRaw) && isBlockingHttpError(summaryRaw)) {
541
- return httpFailure(summaryRaw, "summary");
542
- }
543
- const summary = isHttpError(summaryRaw) || isQuotaError(summaryRaw) ? null : parseSummary(summaryRaw);
544
- if (!summary) unavailable.push("usage");
545
- if (!credits && !subscription && !summary) {
546
- return {
547
- ok: false,
548
- error: {
549
- kind: overallController.signal.aborted ? "timeout" : "http",
550
- message: overallController.signal.aborted ? "Command Code quota request timed out" : "Command Code returned no recognized usage data for the account"
551
- }
552
- };
553
- }
554
- return {
555
- ok: true,
556
- quota: {
557
- account,
558
- credits,
559
- subscription,
560
- summary,
561
- ...unavailable.length > 0 ? { unavailable } : {}
562
- }
563
- };
564
- } catch (error) {
565
- if (error instanceof QuotaTimeoutError || overallController.signal.aborted) {
566
- return {
567
- ok: false,
568
- error: { message: "Command Code quota request timed out", kind: "timeout" }
569
- };
570
- }
571
- return {
572
- ok: false,
573
- error: {
574
- message: redactCommandCodeErrorText(
575
- `Failed to fetch Command Code quota: ${errorMessage2(error)}`
576
- ),
577
- kind: "network"
578
- }
579
- };
580
- } finally {
581
- clearTimeout(overallTimer);
582
- }
583
- }
584
-
585
- // src/quota-format.ts
586
- function formatWindowLimits(limits, now = Date.now) {
587
- const labels = {
588
- fiveHour: "5-hour",
589
- weekly: "Weekly"
590
- };
591
- return limits.map((limit) => {
592
- const used = limit.used.toFixed(2);
593
- const cap = limit.cap.toFixed(2);
594
- const percent = limit.cap > 0 ? Math.round(limit.used / limit.cap * 100) : 0;
595
- const reset = limit.resetAt === null ? "" : ` (resets ${formatResetClock(limit.resetAt, now)})`;
596
- return `${labels[limit.window]}: ${used} / ${cap} credits (${percent}% used)${reset}`;
597
- });
598
- }
599
- function formatResetClock(resetAtSeconds, now) {
600
- const date = new Date(resetAtSeconds * 1e3);
601
- if (Number.isNaN(date.getTime())) return "unknown";
602
- const diffMs = date.getTime() - now();
603
- if (diffMs <= 0) return "soon";
604
- const minutes = Math.ceil(diffMs / 6e4);
605
- if (minutes < 60) return `in ${minutes}m`;
606
- const hours = Math.floor(minutes / 60);
607
- const remainingMinutes = minutes % 60;
608
- if (hours < 24) {
609
- return remainingMinutes > 0 ? `in ${hours}h ${remainingMinutes}m` : `in ${hours}h`;
610
- }
611
- const days = Math.floor(hours / 24);
612
- return days === 1 ? "in 1 day" : `in ${days} days`;
613
- }
614
- function creditsDetail(credits) {
615
- if (!credits) return void 0;
616
- const parts = [
617
- `monthly $${credits.monthlyCredits.toFixed(2)}`,
618
- `purchased $${credits.purchasedCredits.toFixed(2)}`
619
- ];
620
- if (credits.freeCredits > 0) parts.push(`free $${credits.freeCredits.toFixed(2)}`);
621
- return `Sources: ${parts.join(" / ")}`;
622
- }
623
- function subscriptionLine(subscription) {
624
- const plan = (subscription.planId ?? "Unknown").replace(/[_-]+/g, " ").trim();
625
- const status = subscription.status ? ` (${subscription.status})` : "";
626
- return `Plan: ${plan}${status}`;
627
- }
628
- function formatTokens(tokens) {
629
- if (tokens >= 1e9) return `${(tokens / 1e9).toFixed(1)}B`;
630
- if (tokens >= 1e6) return `${(tokens / 1e6).toFixed(1)}M`;
631
- if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}k`;
632
- return String(tokens);
633
- }
634
- function formatQuota(quota, now = Date.now) {
635
- const lines = [];
636
- const remaining = quota.credits?.remainingCredits ?? 0;
637
- const spent = quota.summary?.totalCost ?? 0;
638
- const pool = remaining + spent;
639
- if (quota.credits || quota.summary) {
640
- lines.push("Credits");
641
- lines.push(` Remaining: $${remaining.toFixed(2)} of $${pool.toFixed(2)}`);
642
- lines.push(` Used: $${spent.toFixed(2)}`);
643
- lines.push(` ${pool > 0 ? Math.round(spent / pool * 100) : 0}% used`);
644
- }
645
- const detail = creditsDetail(quota.credits);
646
- if (detail) lines.push(detail);
647
- if (quota.subscription) lines.push(subscriptionLine(quota.subscription));
648
- if (quota.summary) {
649
- lines.push("");
650
- lines.push(quota.subscription?.currentPeriodStart ? "Usage (billing period)" : "Usage");
651
- lines.push(` Cost: $${quota.summary.totalCost.toFixed(2)}`);
652
- lines.push(` Requests: ${quota.summary.totalCount.toLocaleString("en-US")}`);
653
- if (quota.summary.totalTokens !== void 0) {
654
- lines.push(` Tokens: ${formatTokens(quota.summary.totalTokens)}`);
655
- }
656
- }
657
- lines.push("");
658
- lines.push("Account");
659
- lines.push(` ${quota.account.keyName ?? quota.account.login}`);
660
- const limits = quota.credits?.windowLimits ?? [];
661
- if (limits.length > 0) {
662
- lines.push("");
663
- lines.push("Usage windows:");
664
- lines.push(...formatWindowLimits(limits, now).map((line) => ` ${line}`));
665
- }
666
- if ((quota.unavailable?.length ?? 0) > 0) {
667
- lines.push("");
668
- lines.push(`Unavailable: ${quota.unavailable?.join(", ")}`);
669
- }
670
- lines.push("");
671
- lines.push("Full detail: https://commandcode.ai/usage");
672
- return lines.join("\n");
673
- }
674
-
675
- // src/status.ts
676
- function formatStatus(input) {
677
- const lines = [
678
- "Command Code plugin status",
679
- ` Source: ${input.source}`,
680
- ` Models: ${input.modelCount}`,
681
- ` Fetched: ${input.fetchedAt === void 0 ? "n/a" : new Date(input.fetchedAt).toISOString()}`,
682
- ` Endpoint: ${input.apiBase}`,
683
- ` Zero-data-retention: ${input.zdr ? "on" : "off"}`,
684
- ` Version: ${input.version}`
685
- ];
686
- return lines.join("\n");
687
- }
688
-
689
- // src/cli.ts
690
- function zdrEnabled() {
691
- return process.env.CMD_ZDR === "1" || process.env.COMMANDCODE_ZDR === "1";
692
- }
693
- function modelsCachePath() {
694
- const configured = process.env.COMMANDCODE_MODELS_CACHE;
695
- if (configured && configured.length > 0) return configured;
696
- return join(homedir(), ".cache", "opencode", "commandcode-models.json");
697
- }
698
- function pluginVersion() {
699
- try {
700
- const here = fileURLToPath(import.meta.url);
701
- const pkg = JSON.parse(readFileSync2(join(dirname2(here), "..", "package.json"), "utf-8"));
702
- return typeof pkg.version === "string" ? pkg.version : "unknown";
703
- } catch {
704
- return "unknown";
705
- }
706
- }
707
- function modelsTimeoutMs() {
708
- const raw = process.env.COMMANDCODE_MODELS_TIMEOUT_MS;
709
- if (!raw) return DEFAULT_MODELS_TIMEOUT_MS;
710
- const parsed = Number(raw);
711
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MODELS_TIMEOUT_MS;
712
- }
713
- async function runQuota() {
714
- const apiKey = readCommandCodeKey();
715
- if (!apiKey) {
716
- process.stdout.write(
717
- "No Command Code API key found. Run /connect and select Command Code.\n"
718
- );
719
- return;
720
- }
721
- const baseUrl = process.env.COMMANDCODE_API_BASE?.replace(/\/provider\/v1\/?$/, "") ?? "https://api.commandcode.ai";
722
- const extraHeaders = zdrEnabled() ? { "x-cmd-zdr": "1" } : void 0;
723
- const result = await fetchCommandCodeQuota({ apiKey, baseUrl, extraHeaders });
724
- if (!result.ok) {
725
- process.stdout.write(`${result.error.message}
726
- `);
727
- return;
728
- }
729
- process.stdout.write(`${formatQuota(result.quota)}
730
- `);
731
- }
732
- async function runStatus() {
733
- const apiBase = process.env.COMMANDCODE_API_BASE ?? DEFAULT_PROVIDER_API_BASE;
734
- const modelsUrl = process.env.COMMANDCODE_MODELS_URL ?? `${apiBase}/models`;
735
- const cachePath = modelsCachePath();
736
- const result = await loadCommandCodeModels({
737
- url: modelsUrl,
738
- cachePath,
739
- fallbackModels: FALLBACK_MODELS,
740
- timeoutMs: modelsTimeoutMs()
741
- });
742
- const status = {
743
- source: result.source,
744
- modelCount: result.models.length,
745
- fetchedAt: result.fetchedAt,
746
- apiBase,
747
- zdr: zdrEnabled(),
748
- version: pluginVersion()
749
- };
750
- process.stdout.write(`${formatStatus(status)}
751
- `);
752
- }
753
- async function main() {
754
- const cmd = process.argv[2];
755
- if (cmd === "quota") return runQuota();
756
- if (cmd === "status") return runStatus();
757
- process.stdout.write("Usage: opencode-commandcode <quota|status>\n");
758
- }
759
- main().catch((error) => {
760
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}
761
- `);
762
- process.exitCode = 1;
763
- });