/** * CommandCode usage handler * * Mirrors the official command-code CLI /usage command: it calls the alpha API * to surface the 5-hour + weekly usage windows, the subscription plan, and the * credits consumed in the current billing period. * * GET /alpha/whoami → org.id (org-scoped billing; null for personal) * GET /alpha/billing/credits → { credits: { monthlyCredits, purchasedCredits, * freeCredits }, windowLimits: { fiveHour, weekly } } * GET /alpha/billing/subscriptions → { data: { planId, currentPeriodStart, ... } } * GET /alpha/usage/summary?since= → period token/cost totals * * The CLI fetches whoami first (for orgId), then credits + subscription in * parallel, then the summary with since = currentPeriodStart. We keep the same * order/dependencies: window limits live on credits, and the plan period start * determines the summary window. */ import { proxyAwareFetch } from "../../utils/proxyFetch.js"; import { U, parseResetTime } from "./shared.js"; const USAGE = U("commandcode"); const BASE = USAGE.baseUrl || "https://api.commandcode.ai"; const WHOAMI_URL = BASE + (USAGE.whoamiUrl || "/alpha/whoami"); const CREDITS_URL = BASE + (USAGE.creditsUrl || "/alpha/billing/credits"); const SUBSCRIPTIONS_URL = BASE + (USAGE.subscriptionsUrl || "/alpha/billing/subscriptions"); const SUMMARY_URL = BASE + (USAGE.summaryUrl || "/alpha/usage/summary"); function buildHeaders(token) { return { Authorization: `Bearer ${token}`, Accept: "application/json", }; } /** Build a normalized quota row. `unit` is "$" — the API reports currency credits. */ function makeQuota({ used, total, resetAt, unlimited = false, unit = "$" }) { const safeTotal = Math.max(0, Number(total) || 0); const safeUsed = Math.max(0, Number(used) || 0); if (unlimited || safeTotal === 0) { return { used: safeUsed, total: 0, remainingPercentage: unlimited ? 100 : 0, resetAt: resetAt || null, unit, unlimited: true, }; } const remaining = Math.max(0, safeTotal - safeUsed); const remainingPercentage = (remaining / safeTotal) * 100; return { used: safeUsed, total: safeTotal, remainingPercentage, resetAt: resetAt || null, unit, unlimited: false, }; } /** * @param {string} apiKey - commandcode API key (user_...) * @param {object|null} proxyOptions */ export async function getCommandCodeUsage(apiKey, proxyOptions = null) { if (!apiKey) { return { message: "CommandCode credential not available." }; } const headers = buildHeaders(apiKey); try { // whoami resolves the org id (billing is org-scoped; null for personal). const whoamiRes = await proxyAwareFetch( WHOAMI_URL, { method: "GET", headers }, proxyOptions, ); if (whoamiRes.status === 401 || whoamiRes.status === 403) { return { message: "CommandCode credential invalid or expired." }; } if (!whoamiRes.ok) { return { message: `CommandCode whoami API error (${whoamiRes.status}).` }; } const whoami = await whoamiRes.json().catch(() => null); const orgId = whoami?.org?.id ?? null; const orgQuery = orgId ? `?orgId=${encodeURIComponent(orgId)}` : ""; const [creditsRes, subsRes] = await Promise.all([ proxyAwareFetch( CREDITS_URL + orgQuery, { method: "GET", headers }, proxyOptions, ), proxyAwareFetch( SUBSCRIPTIONS_URL + orgQuery, { method: "GET", headers }, proxyOptions, ), ]); if ( creditsRes.status === 401 || creditsRes.status === 403 || subsRes.status === 401 || subsRes.status === 403 ) { return { message: "CommandCode credential invalid or expired." }; } if (!creditsRes.ok) { return { message: `CommandCode credits API error (${creditsRes.status}).`, }; } const credits = await creditsRes.json().catch(() => null); const subs = await subsRes.json().catch(() => null); const subData = subs?.data; const planId = subData?.planId ?? null; const periodStart = subData?.currentPeriodStart ?? null; // Summary needs `since`; the CLI falls back to first-of-month when the // subscription period start is unavailable. const since = periodStart || firstOfMonth(); const summaryRes = await proxyAwareFetch( `${SUMMARY_URL}?since=${encodeURIComponent(since)}`, { method: "GET", headers }, proxyOptions, ); const summary = summaryRes.ok ? await summaryRes.json().catch(() => null) : null; const quotas = {}; const windowLimits = credits?.windowLimits || {}; const fiveHour = windowLimits.fiveHour; if (fiveHour && Number(fiveHour.cap) > 0) { quotas["5-hour window"] = makeQuota({ used: fiveHour.used, total: fiveHour.cap, resetAt: parseResetTime(fiveHour.resetAt), }); } const weekly = windowLimits.weekly; if (weekly && Number(weekly.cap) > 0) { quotas["Weekly window"] = makeQuota({ used: weekly.used, total: weekly.cap, resetAt: parseResetTime(weekly.resetAt), }); } // Monthly credits consumed this billing period (from summary when present, // else the credits object's monthlyCredits as a fallback). const monthlyUsed = typeof summary?.totalCredits === "number" ? summary.totalCredits : typeof credits?.credits?.monthlyCredits === "number" ? credits.credits.monthlyCredits : 0; const monthlyTotal = typeof credits?.credits?.monthlyCredits === "number" ? credits.credits.monthlyCredits : 0; if (monthlyTotal > 0 || monthlyUsed > 0) { quotas["Monthly credits"] = makeQuota({ used: monthlyUsed, total: monthlyTotal, resetAt: periodStart ? undefined : null, }); } if (Object.keys(quotas).length === 0) { return { plan: planId || "CommandCode", message: "CommandCode connected, but no quota was reported.", quotas: {}, }; } return { plan: planId || "CommandCode", quotas, periodBasis: summary?.periodBasis || "billing-period", }; } catch (error) { return { message: `CommandCode usage error: ${error.message}` }; } } function firstOfMonth() { const now = new Date(); return new Date(now.getFullYear(), now.getMonth(), 1).toISOString(); }