Files
english/.opencode/skills/stitch/scripts/stitch-quota.ts
2026-04-12 01:06:31 +07:00

124 lines
3.3 KiB
TypeScript

/**
* stitch-quota.ts — Local quota tracker for Google Stitch daily credits.
*
* Usage:
* npx tsx stitch-quota.ts check # Show remaining credits
* npx tsx stitch-quota.ts increment # Bump count after generation
* npx tsx stitch-quota.ts reset # Force reset counter
*
* Tracks usage in ~/.claudekit/.stitch-quota.json.
* Auto-resets when date changes (UTC midnight).
*/
import fs from "fs";
import path from "path";
import os from "os";
// -- Config --
const QUOTA_DIR = path.join(os.homedir(), ".claudekit");
const QUOTA_FILE = path.join(QUOTA_DIR, ".stitch-quota.json");
// Stitch free tier: 400 daily credits (generate), 15 redesign credits (edit)
// Source: stitch.withgoogle.com dashboard. No API to fetch real usage.
const DEFAULT_LIMIT = parseInt(process.env.STITCH_QUOTA_LIMIT || "400", 10);
const WARN_THRESHOLD = 0.2; // Warn when <20% remaining
interface QuotaState {
date: string;
count: number;
limit: number;
}
// -- Helpers --
function todayUTC(): string {
return new Date().toISOString().slice(0, 10);
}
function loadQuota(): QuotaState {
try {
if (fs.existsSync(QUOTA_FILE)) {
const data = JSON.parse(fs.readFileSync(QUOTA_FILE, "utf-8"));
// Auto-reset if date changed
if (data.date !== todayUTC()) {
return { date: todayUTC(), count: 0, limit: data.limit || DEFAULT_LIMIT };
}
return data;
}
} catch {
// Corrupted file — start fresh
}
return { date: todayUTC(), count: 0, limit: DEFAULT_LIMIT };
}
function saveQuota(state: QuotaState): void {
fs.mkdirSync(QUOTA_DIR, { recursive: true });
fs.writeFileSync(QUOTA_FILE, JSON.stringify(state, null, 2));
}
// -- Commands --
function check(): void {
const state = loadQuota();
saveQuota(state); // Persist auto-reset if date changed
const remaining = state.limit - state.count;
const pct = remaining / state.limit;
console.log(JSON.stringify({
date: state.date,
used: state.count,
remaining,
limit: state.limit,
percentRemaining: Math.round(pct * 100),
}, null, 2));
if (remaining <= 0) {
console.error("[X] Daily quota exhausted. Use ck:ui-ux-pro-max as fallback.");
process.exit(2);
} else if (pct < WARN_THRESHOLD) {
console.error(`[!] Low quota: ${remaining}/${state.limit} credits remaining (${Math.round(pct * 100)}%)`);
} else {
console.error(`[OK] ${remaining}/${state.limit} credits remaining`);
}
}
function increment(): void {
const state = loadQuota();
state.count += 1;
saveQuota(state);
const remaining = state.limit - state.count;
console.error(`[OK] Quota updated: ${state.count}/${state.limit} used (${remaining} remaining)`);
if (remaining <= 0) {
console.error("[!] Daily quota now exhausted.");
} else if (remaining / state.limit < WARN_THRESHOLD) {
console.error(`[!] Low quota warning: ${remaining} credits remaining`);
}
}
function reset(): void {
const state: QuotaState = { date: todayUTC(), count: 0, limit: DEFAULT_LIMIT };
saveQuota(state);
console.error(`[OK] Quota reset: 0/${state.limit} used`);
}
// -- Main --
const command = process.argv[2];
switch (command) {
case "check":
check();
break;
case "increment":
increment();
break;
case "reset":
reset();
break;
default:
console.error("Usage: npx tsx stitch-quota.ts <check|increment|reset>");
process.exit(1);
}