103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
|
|
/**
|
||
|
|
* headless-usage.ts — `sf headless usage`
|
||
|
|
*
|
||
|
|
* Purpose: expose live LLM-provider usage data (account tier, project, per-model
|
||
|
|
* quota usage with reset windows) via the headless CLI so operators and CI can
|
||
|
|
* see capacity state without launching the interactive UI.
|
||
|
|
*
|
||
|
|
* Today this covers the gemini-cli provider (the most quota-sensitive surface
|
||
|
|
* because of AI Ultra's per-model windowed quotas). Other providers can be
|
||
|
|
* added by extending the snapshot helper as their introspection APIs are
|
||
|
|
* wired into dedicated provider packages.
|
||
|
|
*
|
||
|
|
* Consumer: headless.ts when command === "usage".
|
||
|
|
*/
|
||
|
|
|
||
|
|
import {
|
||
|
|
type GeminiAccountSnapshot,
|
||
|
|
snapshotGeminiCliAccount,
|
||
|
|
} from "@singularity-forge/google-gemini-cli-provider";
|
||
|
|
|
||
|
|
export interface HandleUsageOptions {
|
||
|
|
json?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface HandleUsageResult {
|
||
|
|
exitCode: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render a snapshot as a compact text table (default) or as JSON for machine
|
||
|
|
* consumers. Always writes to stdout; never throws.
|
||
|
|
*/
|
||
|
|
export async function handleUsage(
|
||
|
|
cwd: string,
|
||
|
|
options: HandleUsageOptions = {},
|
||
|
|
): Promise<HandleUsageResult> {
|
||
|
|
let snapshot: GeminiAccountSnapshot | null;
|
||
|
|
try {
|
||
|
|
snapshot = await snapshotGeminiCliAccount(cwd);
|
||
|
|
} catch (err) {
|
||
|
|
const msg = err instanceof Error ? err.message : String(err);
|
||
|
|
const payload = {
|
||
|
|
provider: "google-gemini-cli",
|
||
|
|
ok: false,
|
||
|
|
error: msg,
|
||
|
|
};
|
||
|
|
process.stdout.write(
|
||
|
|
options.json ? `${JSON.stringify(payload)}\n` : `error: ${msg}\n`,
|
||
|
|
);
|
||
|
|
return { exitCode: 1 };
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!snapshot) {
|
||
|
|
const payload = {
|
||
|
|
provider: "google-gemini-cli",
|
||
|
|
ok: false,
|
||
|
|
error:
|
||
|
|
"No gemini-cli account snapshot — run `gemini auth login` and verify ~/.gemini/oauth_creds.json exists.",
|
||
|
|
};
|
||
|
|
process.stdout.write(
|
||
|
|
options.json
|
||
|
|
? `${JSON.stringify(payload)}\n`
|
||
|
|
: `${payload.error}\n`,
|
||
|
|
);
|
||
|
|
return { exitCode: 1 };
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.json) {
|
||
|
|
process.stdout.write(
|
||
|
|
`${JSON.stringify({ provider: "google-gemini-cli", ok: true, snapshot })}\n`,
|
||
|
|
);
|
||
|
|
return { exitCode: 0 };
|
||
|
|
}
|
||
|
|
|
||
|
|
const lines: string[] = [];
|
||
|
|
lines.push("Gemini CLI usage");
|
||
|
|
lines.push("");
|
||
|
|
lines.push(` project: ${snapshot.projectId}`);
|
||
|
|
if (snapshot.userTierId || snapshot.userTierName) {
|
||
|
|
lines.push(
|
||
|
|
` userTier: ${snapshot.userTierId ?? "?"}${snapshot.userTierName ? ` (${snapshot.userTierName})` : ""}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (snapshot.paidTier?.id || snapshot.paidTier?.name) {
|
||
|
|
lines.push(
|
||
|
|
` paidTier: ${snapshot.paidTier.id ?? "?"}${snapshot.paidTier.name ? ` — ${snapshot.paidTier.name}` : ""}`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
lines.push("");
|
||
|
|
lines.push(" Per-model quota:");
|
||
|
|
const modelW = Math.max(
|
||
|
|
20,
|
||
|
|
...snapshot.models.map((m) => m.modelId.length),
|
||
|
|
);
|
||
|
|
for (const m of snapshot.models) {
|
||
|
|
const usedPct = (m.usedFraction * 100).toFixed(1).padStart(5);
|
||
|
|
const reset = m.resetTime ?? "-";
|
||
|
|
lines.push(` ${m.modelId.padEnd(modelW)} used=${usedPct}% reset=${reset}`);
|
||
|
|
}
|
||
|
|
process.stdout.write(`${lines.join("\n")}\n`);
|
||
|
|
return { exitCode: 0 };
|
||
|
|
}
|