@narumitw/pi-usage 0.63.0 → 0.64.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/docs/providers.md CHANGED
@@ -17,6 +17,7 @@ The README contains the capability overview and shared security requirements.
17
17
  - [Vercel AI Gateway](#vercel-ai-gateway-credits)
18
18
  - [Baseten](#baseten-model-apis-spend)
19
19
  - [OpenCode Go](#opencode-go-zen)
20
+ - [Command Code](#command-code)
20
21
  - [xAI](#xai-consumer-subscriptions)
21
22
  - [Z.AI](#zai-glm-coding-plan)
22
23
 
@@ -63,8 +64,9 @@ Unknown envelope fields or pagination markers fail closed rather than assuming u
63
64
  Only plan primary/secondary windows are used; Codex model-specific limits are not native app quotas.
64
65
  Each window requires a finite 0–100 used percentage and positive whole-second duration; optional remaining percentages must agree within one percentage point, and reset timestamps must be valid nonnegative epoch seconds.
65
66
  `allowed_usage_percent` is **App allowance**, a cap on shared plan usage, not remaining quota.
66
- Plan/app windows and reset boundaries stay separate in reports and the statusline; Codex display preferences do not apply.
67
- App used/remaining values are validated internally but not displayed; app reset/window information and the separately labeled allowance cap remain visible.
67
+ Reports keep plan/app windows and reset boundaries separate; the statusline shows only plan percentages and resets/window labels.
68
+ Codex display preferences do not apply.
69
+ App used/remaining values are validated internally but not displayed; app reset/window information and the separately labeled allowance cap remain visible in detailed reports.
68
70
 
69
71
  Missing-companion paths remain web-only without requests. Other companion failures invalidate numerical data and remain observable.
70
72
  No native token, refresh token, app name, or account ID is rendered or persisted by this feature.
@@ -264,6 +266,24 @@ The fixed endpoint is queried only when the OpenCode Go model uses the official
264
266
  When resolved provider auth includes a base URL, that URL must use the same origin.
265
267
  Other origins fail before the credential is sent.
266
268
 
269
+ ### Command Code
270
+
271
+ - Provider ID: `command-code`
272
+ - Semantics: plan credits plus rolling 5-hour and weekly spending caps
273
+ - Account route: `GET https://api.commandcode.ai/alpha/whoami`
274
+ - Credits route: `GET https://api.commandcode.ai/alpha/billing/credits`
275
+ - Plan route: `GET https://api.commandcode.ai/alpha/billing/subscriptions`
276
+ - Period route: `GET https://api.commandcode.ai/alpha/usage/summary`
277
+ - Displayed data: five-hour and weekly spend caps in USD with reset times, the billing-period credit pool and remaining balance, plan name and status, request and token totals, and purchased or free credits when present
278
+ - Statusline examples: `cmd 99% 5h 92% wk 96% mo`
279
+
280
+ The official `@commandcode/pi-commandcode-provider` extension registers this provider through `pi.registerProvider()`, so `command-code` is not a Pi built-in provider.
281
+ Pi still resolves its stored `/login` API key through the normal provider registry, and the extension sends it as Bearer authorization only when the selected model and resolved auth share the official `https://api.commandcode.ai` origin.
282
+ `credits.monthlyCredits`, `purchasedCredits`, and `freeCredits` are remaining amounts, so the billing-period pool is their sum plus `usage/summary.totalCost`, and `windowLimits.*.resetAt` is a millisecond epoch.
283
+ The account response is required; the credits, plan, and period responses degrade to individual notes when unavailable, and a plan without rolling limits reports that instead of inventing windows.
284
+ These are undocumented Command Code endpoints pinned on 2026-09-29 against live responses and the same routes the Command Code CLI `/usage` command reads.
285
+ They may change without notice, and this paragraph is the contract evidence for the reviewed behavior.
286
+
267
287
  ### xAI consumer subscriptions
268
288
 
269
289
  - Provider ID: `xai`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-usage",
3
- "version": "0.63.0",
3
+ "version": "0.64.0",
4
4
  "description": "Pi extension that shows current-account usage and DeepSeek API balance for supported providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,6 +17,7 @@
17
17
  "baseten",
18
18
  "vercel-ai-gateway",
19
19
  "codex",
20
+ "command-code",
20
21
  "kimi",
21
22
  "kimi-coding",
22
23
  "moonshot",
package/src/format.ts CHANGED
@@ -37,6 +37,7 @@ export function formatUsageReport(report: UsageReport, displayState: UsageDispla
37
37
  else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
38
38
  else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
39
39
  else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
40
+ else if (report.providerId === "command-code") formatCommandCodeReport(lines, report);
40
41
  else if (report.providerId === "kimi-coding") formatKimiCodingReport(lines, report);
41
42
  else if (report.providerId === "moonshotai" || report.providerId === "moonshotai-cn") {
42
43
  formatMoonshotReport(lines, report);
@@ -62,19 +63,14 @@ export function formatUsageStatusline(
62
63
  ): string | undefined {
63
64
  if (report.providerId === "openai" && report.source === "openai-chatgpt-auth") return "chatgpt usage: web only";
64
65
  if (report.source === "openai-chatgpt-companion") {
65
- return `chatgpt ${["chatgpt-plan", "chatgpt-app"]
66
- .map((group) => {
67
- const windows = report.buckets
68
- .filter((bucket) => bucket.groupId === group)
69
- .map((bucket) => {
70
- const reset = formatResetCountdown(bucket.resetsAt, now);
71
- const window = reset ? `↻ ${reset}` : formatWindowLabel(bucket.windowMinutes, "weekly", true);
72
- if (group === "chatgpt-app") return window;
73
- return `${bucket.remaining === undefined ? "unavailable" : `${bucket.remaining.toFixed(0)}%`} ${window}`;
74
- });
75
- return `${group === "chatgpt-plan" ? "plan" : "app"} ${windows.join(" ")}`;
76
- })
77
- .join(" · ")}`;
66
+ const windows = report.buckets
67
+ .filter((bucket) => bucket.groupId === "chatgpt-plan")
68
+ .map((bucket) => {
69
+ const reset = formatResetCountdown(bucket.resetsAt, now);
70
+ const window = reset ? `↻ ${reset}` : formatWindowLabel(bucket.windowMinutes, "weekly", true);
71
+ return `${bucket.remaining === undefined ? "unavailable" : `${bucket.remaining.toFixed(0)}%`} ${window}`;
72
+ });
73
+ return `chatgpt plan ${windows.join(" ")}`;
78
74
  }
79
75
  if (report.providerId === "baseten") return formatBasetenStatusline(report);
80
76
  if (report.providerId === "openai-codex") {
@@ -91,6 +87,7 @@ export function formatUsageStatusline(
91
87
  if (typeof total?.value === "number") return `openrouter ${formatUsd(total.value)} used`;
92
88
  }
93
89
  if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
90
+ if (report.providerId === "command-code") return formatCommandCodeStatusline(report);
94
91
  if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
95
92
  if (report.providerId === "moonshotai" || report.providerId === "moonshotai-cn") {
96
93
  return formatMoonshotStatusline(report);
@@ -298,6 +295,36 @@ function formatOpenCodeZenStatusline(report: UsageReport): string | undefined {
298
295
  return parts.length > 1 ? parts.join(" ") : undefined;
299
296
  }
300
297
 
298
+ function formatCommandCodeReport(lines: string[], report: UsageReport): void {
299
+ for (const bucket of report.buckets) {
300
+ const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
301
+ if (bucket.used === undefined || bucket.limit === undefined) {
302
+ lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}unavailable${reset}`);
303
+ continue;
304
+ }
305
+ lines.push(
306
+ `${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatUsd(bucket.used)} of ${formatUsd(bucket.limit)} used · ${percentRemaining(bucket)}% left${reset}`,
307
+ );
308
+ }
309
+ for (const metric of report.metrics) {
310
+ lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
311
+ }
312
+ }
313
+
314
+ function formatCommandCodeStatusline(report: UsageReport): string | undefined {
315
+ const parts = ["cmd"];
316
+ for (const [id, label] of [
317
+ ["five-hour", "5h"],
318
+ ["weekly", "wk"],
319
+ ["monthly", "mo"],
320
+ ] as const) {
321
+ const bucket = report.buckets.find((candidate) => candidate.id === id);
322
+ if (bucket?.unit !== "usd" || bucket.limit === undefined || bucket.remaining === undefined) continue;
323
+ parts.push(`${percentRemaining(bucket)}% ${label}`);
324
+ }
325
+ return parts.length > 1 ? parts.join(" ") : undefined;
326
+ }
327
+
301
328
  function formatKimiCodingReport(lines: string[], report: UsageReport): void {
302
329
  for (const bucket of report.buckets) {
303
330
  const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ export {
34
34
  export { formatProviderStates, formatUsageReport, formatUsageStatusline } from "./format.js";
35
35
  export { normalizeBasetenBillingUsagePayload } from "./providers/baseten.js";
36
36
  export { normalizeCodexBackendPayload } from "./providers/codex.js";
37
+ export { commandCodeOrgId, normalizeCommandCodeUsagePayload } from "./providers/command-code.js";
37
38
  export { normalizeDeepSeekBalancePayload } from "./providers/deepseek.js";
38
39
  export {
39
40
  createFireworksAdapter,
@@ -80,6 +81,11 @@ export {
80
81
  } from "./settings.js";
81
82
  export type {
82
83
  BasetenBillingUsagePayload,
84
+ CommandCodeAccountPayload,
85
+ CommandCodeCreditsPayload,
86
+ CommandCodeSubscriptionPayload,
87
+ CommandCodeUsageBundle,
88
+ CommandCodeUsageSummaryPayload,
83
89
  DeepSeekBalancePayload,
84
90
  FireworksAccountsPayload,
85
91
  FireworksBillingSummaryPayload,
@@ -0,0 +1,228 @@
1
+ import { sanitizeDisplayText } from "../core.js";
2
+ import type {
3
+ CommandCodeAccountPayload,
4
+ CommandCodeUsageBundle,
5
+ UsageBucket,
6
+ UsageMetric,
7
+ UsageReport,
8
+ } from "../types.js";
9
+
10
+ const FIVE_HOUR_WINDOW_MINUTES = 300;
11
+ const WEEKLY_WINDOW_MINUTES = 10_080;
12
+ const IDENTIFIER_PATTERN = /^[A-Za-z0-9._~-]{1,128}$/u;
13
+ const MILLISECOND_EPOCH_FLOOR = 1e12;
14
+ const WINDOWS = [
15
+ { key: "fiveHour", id: "five-hour", label: "Five-hour window", windowMinutes: FIVE_HOUR_WINDOW_MINUTES },
16
+ { key: "weekly", id: "weekly", label: "Weekly window", windowMinutes: WEEKLY_WINDOW_MINUTES },
17
+ ] as const;
18
+ const PLAN_LABELS: Readonly<Record<string, string>> = {
19
+ "individual-go": "Go",
20
+ "individual-goat": "GOAT",
21
+ "individual-pro": "Pro",
22
+ "individual-provider": "Provider",
23
+ "individual-max": "Max",
24
+ "individual-ultra": "Ultra",
25
+ };
26
+
27
+ export function commandCodeOrgId(account: CommandCodeAccountPayload): string | undefined {
28
+ const id = asObject(account.org)?.id;
29
+ return typeof id === "string" && IDENTIFIER_PATTERN.test(id) ? id : undefined;
30
+ }
31
+
32
+ /**
33
+ * Source contract observed live on 2026-09-29. Command Code serves the account, credits, plan,
34
+ * and billing-period usage that its CLI `/usage` command displays from the undocumented
35
+ * `https://api.commandcode.ai/alpha/*` endpoints, authenticated with the same Bearer API key Pi
36
+ * stores for the `command-code` provider. `resetAt` is a millisecond epoch, plan credits and
37
+ * rolling-window caps are USD-denominated, and `org` may be `null` for a personal account.
38
+ * `credits.monthlyCredits`, `purchasedCredits`, and `freeCredits` are remaining amounts, so the
39
+ * billing-period pool is their sum plus `usage/summary.totalCost`.
40
+ */
41
+ export function normalizeCommandCodeUsagePayload(bundle: CommandCodeUsageBundle, capturedAt: number): UsageReport {
42
+ const account = asObject(bundle.account);
43
+ const user = asObject(account?.user);
44
+ const accountLabel = asString(user?.userName) ?? asString(user?.name);
45
+ if (!accountLabel) {
46
+ throw new Error("Command Code account response omitted a safe account name.");
47
+ }
48
+
49
+ const creditsPayload = asObject(bundle.credits);
50
+ const credits = asObject(creditsPayload?.credits);
51
+ const windowLimits = asObject(creditsPayload?.windowLimits);
52
+ const subscription = asObject(asObject(bundle.subscription)?.data);
53
+ const usage = asObject(bundle.usage);
54
+
55
+ const buckets: UsageBucket[] = [];
56
+ for (const window of WINDOWS) {
57
+ const bucket = parseWindow(windowLimits?.[window.key], window);
58
+ if (bucket) buckets.push(bucket);
59
+ }
60
+
61
+ const totalCost = asNonnegativeNumber(usage?.totalCost);
62
+ const remainingCredits = commandCodeRemainingCredits(credits);
63
+ const monthlyReset = asIsoEpochSeconds(subscription?.currentPeriodEnd);
64
+ if (totalCost !== undefined && remainingCredits !== undefined) {
65
+ const pool = totalCost + remainingCredits;
66
+ if (pool > 0) {
67
+ buckets.push({
68
+ id: "monthly",
69
+ label: "Monthly credits",
70
+ used: totalCost,
71
+ remaining: remainingCredits,
72
+ limit: pool,
73
+ unit: "usd",
74
+ period: "billing-period",
75
+ ...(monthlyReset !== undefined ? { resetsAt: monthlyReset } : {}),
76
+ });
77
+ }
78
+ }
79
+
80
+ const metrics: UsageMetric[] = [];
81
+ const plan = planLabel(subscription?.planId);
82
+ const planStatus = asString(subscription?.status, 40);
83
+ if (plan || planStatus) {
84
+ metrics.push({
85
+ id: "plan",
86
+ label: "Plan",
87
+ value: [plan, planStatus ? `(${planStatus})` : undefined].filter(Boolean).join(" "),
88
+ });
89
+ }
90
+ if (buckets.every((bucket) => bucket.id !== "monthly")) {
91
+ if (remainingCredits !== undefined) {
92
+ metrics.push({
93
+ id: "monthly-credits",
94
+ label: "Monthly credits remaining",
95
+ value: remainingCredits,
96
+ unit: "usd",
97
+ });
98
+ } else if (totalCost !== undefined) {
99
+ metrics.push({ id: "period-cost", label: "Cost this billing period", value: totalCost, unit: "usd" });
100
+ }
101
+ }
102
+ const purchased = asNonnegativeNumber(credits?.purchasedCredits);
103
+ if (purchased) {
104
+ metrics.push({ id: "purchased-credits", label: "Purchased credits remaining", value: purchased, unit: "usd" });
105
+ }
106
+ const free = asNonnegativeNumber(credits?.freeCredits);
107
+ if (free) {
108
+ metrics.push({ id: "free-credits", label: "Free credits remaining", value: free, unit: "usd" });
109
+ }
110
+ const totalCount = asNonnegativeNumber(usage?.totalCount);
111
+ if (totalCount !== undefined) {
112
+ metrics.push({ id: "requests", label: "Requests this period", value: totalCount, unit: "count" });
113
+ }
114
+ const totalTokens = asNonnegativeNumber(usage?.totalTokens);
115
+ if (totalTokens !== undefined) {
116
+ metrics.push({ id: "tokens", label: "Tokens this period", value: totalTokens, unit: "count" });
117
+ } else {
118
+ const tokensIn = asNonnegativeNumber(usage?.totalTokensIn);
119
+ const tokensOut = asNonnegativeNumber(usage?.totalTokensOut);
120
+ if (tokensIn !== undefined) {
121
+ metrics.push({ id: "tokens-in", label: "Input tokens", value: tokensIn, unit: "count" });
122
+ }
123
+ if (tokensOut !== undefined) {
124
+ metrics.push({ id: "tokens-out", label: "Output tokens", value: tokensOut, unit: "count" });
125
+ }
126
+ }
127
+
128
+ const notes: string[] = [];
129
+ if (!creditsPayload) notes.push("Command Code credits were unavailable.");
130
+ else if (windowLimits?.limited === false) {
131
+ notes.push("Rolling 5-hour and weekly limits are not enabled for this plan.");
132
+ }
133
+ if (!subscription) notes.push("Command Code plan details were unavailable.");
134
+ if (!usage) notes.push("Command Code billing-period usage was unavailable.");
135
+
136
+ if (buckets.length === 0 && metrics.length === 0) {
137
+ throw new Error("Command Code usage response returned no displayable usage data.");
138
+ }
139
+
140
+ return {
141
+ providerId: "command-code",
142
+ providerName: "Command Code",
143
+ capturedAt,
144
+ source: "command-code-alpha-usage",
145
+ semantics: { kind: "consumer-subscription", label: "Command Code plan credits and rolling limits" },
146
+ accountLabel,
147
+ buckets,
148
+ metrics,
149
+ ...(notes.length > 0 ? { notes } : {}),
150
+ };
151
+ }
152
+
153
+ function parseWindow(value: unknown, window: (typeof WINDOWS)[number]): UsageBucket | undefined {
154
+ const row = asObject(value);
155
+ if (!row) return undefined;
156
+ const used = asNonnegativeNumber(row.used);
157
+ const cap = asNonnegativeNumber(row.cap);
158
+ if (used === undefined || cap === undefined || cap <= 0) return undefined;
159
+ const resetsAt = asMillisecondEpochSeconds(row.resetAt);
160
+ return {
161
+ id: window.id,
162
+ label: window.label,
163
+ used,
164
+ remaining: Math.max(0, cap - used),
165
+ limit: cap,
166
+ unit: "usd",
167
+ windowMinutes: window.windowMinutes,
168
+ ...(resetsAt !== undefined ? { resetsAt } : {}),
169
+ };
170
+ }
171
+
172
+ function commandCodeRemainingCredits(credits: Record<string, unknown> | undefined): number | undefined {
173
+ if (!credits) return undefined;
174
+ const parts = ["monthlyCredits", "purchasedCredits", "freeCredits"]
175
+ .map((key) => asNonnegativeNumber(credits[key]))
176
+ .filter((value): value is number => value !== undefined);
177
+ if (parts.length === 0) return undefined;
178
+ return parts.reduce((total, value) => total + value, 0);
179
+ }
180
+
181
+ function planLabel(value: unknown): string | undefined {
182
+ const raw = asString(value, 40);
183
+ if (!raw) return undefined;
184
+ const known = PLAN_LABELS[raw];
185
+ if (known) return known;
186
+ const words = raw
187
+ .replace(/^individual[-_]/u, "")
188
+ .replace(/[-_]+/gu, " ")
189
+ .trim();
190
+ return words ? capitalize(words) : raw;
191
+ }
192
+
193
+ function asObject(value: unknown): Record<string, unknown> | undefined {
194
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
195
+ return value as Record<string, unknown>;
196
+ }
197
+
198
+ function asString(value: unknown, maxLength = 80): string | undefined {
199
+ if (typeof value !== "string") return undefined;
200
+ return sanitizeDisplayText(value, maxLength) || undefined;
201
+ }
202
+
203
+ function asNonnegativeNumber(value: unknown): number | undefined {
204
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
205
+ return value;
206
+ }
207
+
208
+ function asIsoEpochSeconds(value: unknown): number | undefined {
209
+ if (typeof value !== "string" || !value.trim()) return undefined;
210
+ const parsed = Date.parse(value);
211
+ if (Number.isNaN(parsed)) return undefined;
212
+ return Math.floor(parsed / 1000);
213
+ }
214
+
215
+ function asMillisecondEpochSeconds(value: unknown): number | undefined {
216
+ const raw =
217
+ typeof value === "number" && Number.isFinite(value)
218
+ ? value
219
+ : typeof value === "string" && /^\d+$/u.test(value.trim())
220
+ ? Number(value.trim())
221
+ : undefined;
222
+ if (raw === undefined || raw < 0) return undefined;
223
+ return Math.floor(raw >= MILLISECOND_EPOCH_FLOOR ? raw / 1000 : raw);
224
+ }
225
+
226
+ function capitalize(value: string): string {
227
+ return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
228
+ }
package/src/query.ts CHANGED
@@ -2,10 +2,11 @@
2
2
  // separating that security boundary would duplicate request and redaction policy across providers.
3
3
  import { randomBytes } from "node:crypto";
4
4
  import { type ExtensionContext, readStoredCredential } from "@earendil-works/pi-coding-agent";
5
- import { errorMessage, fingerprintResolvedAuth, redactUsageError } from "./core.js";
5
+ import { abortError, errorMessage, fingerprintResolvedAuth, redactUsageError } from "./core.js";
6
6
  import { fallbackOAuthCredentialCandidates, type OAuthCredentialCandidateReader } from "./oauth-credential-source.js";
7
7
  import { normalizeBasetenBillingUsagePayload } from "./providers/baseten.js";
8
8
  import { normalizeCodexBackendPayload } from "./providers/codex.js";
9
+ import { commandCodeOrgId, normalizeCommandCodeUsagePayload } from "./providers/command-code.js";
9
10
  import { normalizeDeepSeekBalancePayload } from "./providers/deepseek.js";
10
11
  import { createFireworksAdapter } from "./providers/fireworks.js";
11
12
  import { normalizeGitHubCopilotUsagePayload } from "./providers/github-copilot.js";
@@ -26,6 +27,11 @@ import { zaiResponseError } from "./providers/zai-errors.js";
26
27
  import type {
27
28
  BasetenBillingUsagePayload,
28
29
  CodexBackendPayload,
30
+ CommandCodeAccountPayload,
31
+ CommandCodeCreditsPayload,
32
+ CommandCodeSubscriptionPayload,
33
+ CommandCodeUsageBundle,
34
+ CommandCodeUsageSummaryPayload,
29
35
  DeepSeekBalancePayload,
30
36
  GitHubCopilotUsagePayload,
31
37
  KimiCodingUsagePayload,
@@ -51,6 +57,11 @@ import { resolveUsageTarget } from "./usage-targets.js";
51
57
  const BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
52
58
  const BASETEN_USAGE_WINDOW_DAYS = 30;
53
59
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
60
+ const COMMAND_CODE_API_ORIGIN = "https://api.commandcode.ai";
61
+ const COMMAND_CODE_ACCOUNT_URL = `${COMMAND_CODE_API_ORIGIN}/alpha/whoami`;
62
+ const COMMAND_CODE_CREDITS_URL = `${COMMAND_CODE_API_ORIGIN}/alpha/billing/credits`;
63
+ const COMMAND_CODE_SUBSCRIPTIONS_URL = `${COMMAND_CODE_API_ORIGIN}/alpha/billing/subscriptions`;
64
+ const COMMAND_CODE_USAGE_SUMMARY_URL = `${COMMAND_CODE_API_ORIGIN}/alpha/usage/summary`;
54
65
  const DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
55
66
  const GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
56
67
  const OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
@@ -181,6 +192,63 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
181
192
  return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
182
193
  },
183
194
  },
195
+ {
196
+ id: "command-code",
197
+ displayName: "Command Code",
198
+ semantics: { kind: "consumer-subscription", label: "Command Code plan credits and rolling limits" },
199
+ async query(auth, signal, timeoutMs, guard) {
200
+ if (!guard) throw new Error("Command Code usage requires request-boundary revalidation.");
201
+ const startedAt = Date.now();
202
+ await guard();
203
+ const account = (await fetchProviderJson(
204
+ COMMAND_CODE_ACCOUNT_URL,
205
+ auth,
206
+ signal,
207
+ remainingTimeout(timeoutMs, startedAt, "fetching the Command Code account"),
208
+ "Command Code account endpoint",
209
+ { redirect: "error" },
210
+ )) as CommandCodeAccountPayload;
211
+ await guard();
212
+ const orgId = commandCodeOrgId(account);
213
+ const [credits, subscription] = await Promise.all([
214
+ fetchCommandCodeOptional(
215
+ commandCodeUrl(COMMAND_CODE_CREDITS_URL, { orgId }),
216
+ auth,
217
+ signal,
218
+ timeoutMs - (Date.now() - startedAt),
219
+ "Command Code credits endpoint",
220
+ ),
221
+ fetchCommandCodeOptional(
222
+ commandCodeUrl(COMMAND_CODE_SUBSCRIPTIONS_URL, { orgId }),
223
+ auth,
224
+ signal,
225
+ timeoutMs - (Date.now() - startedAt),
226
+ "Command Code plan endpoint",
227
+ ),
228
+ ]);
229
+ await guard();
230
+ const usage = await fetchCommandCodeOptional(
231
+ commandCodeUrl(COMMAND_CODE_USAGE_SUMMARY_URL, {
232
+ orgId,
233
+ since: commandCodeSubscriptionStart(subscription),
234
+ }),
235
+ auth,
236
+ signal,
237
+ timeoutMs - (Date.now() - startedAt),
238
+ "Command Code usage summary endpoint",
239
+ );
240
+ await guard();
241
+ if (signal.aborted) throw abortError();
242
+ // The account response is mandatory; the credits, plan, and period payloads degrade to notes.
243
+ const bundle: CommandCodeUsageBundle = {
244
+ account,
245
+ ...(credits ? { credits: credits as CommandCodeCreditsPayload } : {}),
246
+ ...(subscription ? { subscription: subscription as CommandCodeSubscriptionPayload } : {}),
247
+ ...(usage ? { usage: usage as CommandCodeUsageSummaryPayload } : {}),
248
+ };
249
+ return normalizeCommandCodeUsagePayload(bundle, Date.now());
250
+ },
251
+ },
184
252
  createFireworksAdapter(fetchProviderJson),
185
253
  {
186
254
  id: "opencode-go",
@@ -923,6 +991,7 @@ function hasOfficialUrlOrigin(value: string, providerId: string): boolean {
923
991
  }
924
992
  if (providerId === "openai") return url.origin === "https://api.openai.com";
925
993
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
994
+ if (providerId === "command-code") return url.origin === COMMAND_CODE_API_ORIGIN;
926
995
  if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
927
996
  if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
928
997
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
@@ -1096,3 +1165,40 @@ async function fetchZaiPlan(
1096
1165
  function isAbortError(error: unknown): boolean {
1097
1166
  return error instanceof Error && error.name === "AbortError";
1098
1167
  }
1168
+
1169
+ async function fetchCommandCodeOptional(
1170
+ url: string,
1171
+ auth: ResolvedUsageAuth,
1172
+ signal: AbortSignal,
1173
+ timeoutMs: number,
1174
+ description: string,
1175
+ ): Promise<Record<string, unknown> | undefined> {
1176
+ if (signal.aborted) throw abortError();
1177
+ // Optional sections must not discard collected data when an earlier request used the budget.
1178
+ if (timeoutMs <= 0) return undefined;
1179
+ try {
1180
+ const payload = await fetchProviderJson(url, auth, signal, timeoutMs, description, { redirect: "error" });
1181
+ if (signal.aborted) throw abortError();
1182
+ return payload;
1183
+ } catch (error) {
1184
+ if (signal.aborted) throw abortError();
1185
+ if (isAbortError(error) || isStaleExtensionContextError(error)) throw error;
1186
+ return undefined;
1187
+ }
1188
+ }
1189
+
1190
+ function commandCodeUrl(base: string, params: Record<string, string | undefined>): string {
1191
+ const query = new URLSearchParams();
1192
+ for (const [key, value] of Object.entries(params)) {
1193
+ if (value) query.set(key, value);
1194
+ }
1195
+ const search = query.toString();
1196
+ return search ? `${base}?${search}` : base;
1197
+ }
1198
+
1199
+ function commandCodeSubscriptionStart(subscription: Record<string, unknown> | undefined): string | undefined {
1200
+ const data = subscription?.data;
1201
+ if (!data || typeof data !== "object" || Array.isArray(data)) return undefined;
1202
+ const value = (data as Record<string, unknown>).currentPeriodStart;
1203
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
1204
+ }
package/src/types.ts CHANGED
@@ -238,3 +238,32 @@ export type CodexBackendPayload = {
238
238
  credits?: unknown;
239
239
  rate_limit_reset_credits?: unknown;
240
240
  };
241
+
242
+ export type CommandCodeAccountPayload = {
243
+ user?: unknown;
244
+ org?: unknown;
245
+ };
246
+
247
+ export type CommandCodeCreditsPayload = {
248
+ credits?: unknown;
249
+ windowLimits?: unknown;
250
+ };
251
+
252
+ export type CommandCodeSubscriptionPayload = {
253
+ data?: unknown;
254
+ };
255
+
256
+ export type CommandCodeUsageSummaryPayload = {
257
+ totalCost?: unknown;
258
+ totalCount?: unknown;
259
+ totalTokens?: unknown;
260
+ totalTokensIn?: unknown;
261
+ totalTokensOut?: unknown;
262
+ };
263
+
264
+ export interface CommandCodeUsageBundle {
265
+ account: CommandCodeAccountPayload;
266
+ credits?: CommandCodeCreditsPayload;
267
+ subscription?: CommandCodeSubscriptionPayload;
268
+ usage?: CommandCodeUsageSummaryPayload;
269
+ }
package/src/usage.ts CHANGED
@@ -50,6 +50,7 @@ import { createUsageTargetSelectOptions, listUsageTargets, resolveUsageTarget }
50
50
  const CACHE_TTL_MS = 5 * 60 * 1000;
51
51
  const STATUS_COUNTDOWN_REFRESH_MS = 60 * 1000;
52
52
  const DEFAULT_TIMEOUT_MS = 15_000;
53
+ const COMMAND_CODE_REVALIDATION_RESERVE_MS = 1_000;
53
54
  const ALL_PROVIDER_CONCURRENCY = 2;
54
55
  const FAILURE_BACKOFF_MS = 30_000;
55
56
  const MAX_ACCOUNT_STATES = 32;
@@ -277,6 +278,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
277
278
  adapter.targets !== undefined ||
278
279
  [
279
280
  "baseten",
281
+ "command-code",
280
282
  "deepseek",
281
283
  "minimax",
282
284
  "minimax-cn",
@@ -414,11 +416,17 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
414
416
  querySequence += 1;
415
417
  queryId = querySequence;
416
418
  setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
419
+ // Command Code can retain partial data after an optional transport timeout. Keep time
420
+ // inside the overall deadline for its remaining auth guards and publication guard.
421
+ const queryTimeoutMs = Math.max(
422
+ 1,
423
+ deadlineAt - Date.now() - (adapter.id === "command-code" ? COMMAND_CODE_REVALIDATION_RESERVE_MS : 0),
424
+ );
417
425
  const report = await queryProviderUsage(
418
426
  adapter,
419
427
  auth,
420
428
  signal,
421
- Math.max(1, deadlineAt - Date.now()),
429
+ queryTimeoutMs,
422
430
  requiresRequestBoundaryGuard ? guard : undefined,
423
431
  target.targetId,
424
432
  );