feat(usage): show and redeem free limit resets for cc accounts

Adds the free usage-limit reset (the desktop app's "Reset for free")
to the Quota Tracker for cc OAuth accounts, mirroring the existing
Codex reset-credit button.

- Reset button with remaining count on the card; tooltip shows use-by
  date and which limits get refilled.
- Expiry modal (clock button) listing each grant: label, resets left,
  refills (session / weekly), use-by date, time remaining, status.
- Confirm dialog before redeeming, since a reset is irreversible.
- Usage and reset calls send the CLI User-Agent required for cedar_ember.
- Usage cache is dropped after a redeem so the card shows the refilled limits.
This commit is contained in:
Reid Nguyen
2026-09-23 12:16:10 +07:00
parent 8403576095
commit e571a8b6da
6 changed files with 236 additions and 23 deletions

View File

@@ -0,0 +1,23 @@
import { describe, it, expect } from "vitest";
import { parseClaudeResetGrants } from "../../open-sse/services/usage/claude.js";
describe("parseClaudeResetGrants", () => {
it("sums usable grants and picks next_grant_id", () => {
const r = parseClaudeResetGrants({
eligible: true,
next_grant_id: "g2",
grants: [
{ id: "g1", resets_left: 1, ends_at: "2026-10-01T00:00:00Z" },
{ id: "g2", resets_left: 2, ends_at: "2026-10-22T00:00:00Z", clears: ["five_hour", "seven_day"] },
{ id: "g3", resets_left: 5, paused: true },
],
});
expect(r).toMatchObject({ availableCount: 3, nextGrantId: "g2", expiresAt: "2026-10-22T00:00:00Z" });
expect(r.grants.map((g) => g.id)).toEqual(["g1", "g2", "g3"]); // modal lists paused too
expect(r.grants[1].clears).toEqual(["five_hour", "seven_day"]);
});
it("returns null when ineligible or missing", () => {
expect(parseClaudeResetGrants(undefined)).toBeNull();
expect(parseClaudeResetGrants({ eligible: false, grants: [] })).toBeNull();
});
});