clicking the harness pill opens what claude code's own usage display shows: session/weekly/scoped limits with reset countdowns, tokens by day for the last week, tokens by model. officer has none of these numbers and could not derive them. per-session cost is in-memory in websocket.ts and dies on restart, and the rate-limit windows belong to the ANTHROPIC ACCOUNT — spent by every `claude` on the box, not only by this chat. so both halves come from the cli's own record: the oauth usage endpoint for the windows, ~/.claude/projects for the tokens, ~/.claude.json for which account is signed in. shape and the non-obvious correctness details are lifted from omarchy's agents collector (basecamp/omarchy quattro, via pastilhas.agents which adds the account email): dedup by message id, the four token counters, the per-payload percent-vs-fraction scale, and reading the model-scoped window out of limits[] rather than the flat buckets — every seven_day_* bucket is null on this account while limits[] carries a real fable window. verified against the live payload today; limitsFromPayload is tested against it. owner-only in the handler, not the registry: `chat` is confined, so a member holding it reaches every route here, and these are someone else's windows. the pill falls back to the plain label it always was for anyone else and for opencode. 442ms cold, 1ms warm on 87MB of transcripts; cached 60s server-side, and the query only runs when the popover opens. not exercised through http — the route needs an officer restart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
106 lines
4.6 KiB
TypeScript
106 lines
4.6 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { friendlyModelName, limitsFromPayload } from './claude-usage';
|
|
|
|
/**
|
|
* The live payload, 2026-08-18, trimmed to the fields this reads.
|
|
*
|
|
* Kept verbatim rather than minimised because the two things that are easy to get wrong are both visible
|
|
* only in the whole shape: every model-scoped BUCKET is null while `limits[]` carries a real scoped entry,
|
|
* and the flat buckets and the array report the same windows twice.
|
|
*/
|
|
const LIVE = {
|
|
five_hour: { utilization: 15.0, resets_at: '2026-08-18T08:00:00.434323+00:00' },
|
|
seven_day: { utilization: 22.0, resets_at: '2026-08-19T01:00:00.434348+00:00' },
|
|
seven_day_oauth_apps: null,
|
|
seven_day_opus: null,
|
|
seven_day_sonnet: null,
|
|
limits: [
|
|
{ kind: 'session', group: 'session', percent: 15, resets_at: '2026-08-18T08:00:00.434323+00:00', scope: null },
|
|
{ kind: 'weekly_all', group: 'weekly', percent: 22, resets_at: '2026-08-19T01:00:00.434348+00:00', scope: null },
|
|
{
|
|
kind: 'weekly_scoped',
|
|
group: 'weekly',
|
|
percent: 6,
|
|
resets_at: '2026-08-19T01:00:00.434561+00:00',
|
|
scope: { model: { id: null, display_name: 'Fable' }, surface: null },
|
|
},
|
|
],
|
|
};
|
|
|
|
describe('limitsFromPayload', () => {
|
|
test('reads the live payload the way Claude Code displays it', () => {
|
|
expect(limitsFromPayload(LIVE)).toEqual([
|
|
{ label: 'Session', percent: 15, resetsAt: '2026-08-18T08:00:00.434323+00:00' },
|
|
{ label: 'Weekly', percent: 22, resetsAt: '2026-08-19T01:00:00.434348+00:00' },
|
|
{ label: 'Fable Weekly', percent: 6, resetsAt: '2026-08-19T01:00:00.434561+00:00' },
|
|
]);
|
|
});
|
|
|
|
test('a scoped window survives its flat bucket being null', () => {
|
|
// `seven_day_opus` and friends stayed behind at null when the scoped entries moved into `limits[]`, so
|
|
// a reader that trusts the buckets alone drops an allowance the account is actually spending against.
|
|
const scoped = limitsFromPayload(LIVE).find((l) => l.label === 'Fable Weekly');
|
|
expect(scoped?.percent).toBe(6);
|
|
expect(LIVE.seven_day_opus).toBeNull();
|
|
});
|
|
|
|
test('the unscoped entries in limits[] are not repeated as their own rows', () => {
|
|
// `session` and `weekly_all` restate the flat buckets. Reading both would print Session and Weekly twice.
|
|
expect(limitsFromPayload(LIVE).map((l) => l.label)).toHaveLength(3);
|
|
});
|
|
|
|
test('the oauth-apps bucket shadows the plain weekly one when it is populated', () => {
|
|
const limits = limitsFromPayload({
|
|
...LIVE,
|
|
seven_day_oauth_apps: { utilization: 40, resets_at: null },
|
|
});
|
|
expect(limits.find((l) => l.label === 'Weekly')?.percent).toBe(40);
|
|
});
|
|
|
|
test('a fractional payload is scaled up, and a percent payload is left alone', () => {
|
|
/*
|
|
The two conventions are indistinguishable one value at a time — `1.0` is either 1% or 100% — so the
|
|
scale is decided by the WHOLE payload: any value at or above 1 means it is already percentages.
|
|
*/
|
|
const fractions = { five_hour: { utilization: 0.37, resets_at: null }, limits: [] };
|
|
expect(limitsFromPayload(fractions)[0]?.percent).toBeCloseTo(37);
|
|
|
|
const percents = { five_hour: { utilization: 1.0, resets_at: null }, seven_day: { utilization: 22 }, limits: [] };
|
|
expect(limitsFromPayload(percents)[0]?.percent).toBe(1);
|
|
});
|
|
|
|
test('a scoped window is named from its kind, never from the model name', () => {
|
|
// "Opus 5 (1M context)" contains "1M", and reading a window out of the title turns that into a minute.
|
|
const limits = limitsFromPayload({
|
|
limits: [
|
|
{
|
|
kind: 'weekly_scoped',
|
|
percent: 12,
|
|
resets_at: null,
|
|
scope: { model: { display_name: 'Opus 5 (1M context)' } },
|
|
},
|
|
],
|
|
});
|
|
expect(limits).toEqual([{ label: 'Opus 5 (1M context) Weekly', percent: 12, resetsAt: null }]);
|
|
});
|
|
|
|
test('an empty or malformed payload produces no limits rather than throwing', () => {
|
|
expect(limitsFromPayload({})).toEqual([]);
|
|
expect(limitsFromPayload({ limits: 'not an array', five_hour: 7 })).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('friendlyModelName', () => {
|
|
test('rejoins the version and drops the date suffix', () => {
|
|
expect(friendlyModelName('claude-opus-5')).toBe('Opus 5');
|
|
expect(friendlyModelName('claude-opus-4-8')).toBe('Opus 4.8');
|
|
expect(friendlyModelName('claude-haiku-4-5-20251001')).toBe('Haiku 4.5');
|
|
expect(friendlyModelName('claude-fable-5')).toBe('Fable 5');
|
|
});
|
|
|
|
test('handles ids that are not Anthropic ids', () => {
|
|
expect(friendlyModelName('kimi-for-coding')).toBe('Kimi For Coding');
|
|
expect(friendlyModelName('')).toBe('Unknown');
|
|
});
|
|
});
|