usage panel on the claude code pill

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>
This commit is contained in:
2026-08-18 04:23:12 +00:00
co-authored by Claude Opus 5
parent 77f90a91f0
commit dc68a289ff
9 changed files with 1009 additions and 130 deletions
+19
View File
@@ -31,6 +31,7 @@ import { logger } from './logger';
import { resolveHomeDir } from '@@/user-home';
import type { ChatIdentity } from './claude-sessions';
import { officerModelForApiModel } from './model-aliases';
import { readClaudeUsage } from './claude-usage';
import { readSttConfig } from '../server-settings/stt';
/**
@@ -393,6 +394,24 @@ chatRouter.post('/tasks/:id/pause', async (ctx) => {
});
// GET /chat/models — Claude tiers only (the runner is the `claude` CLI).
/*
GET /chat/usage — what Claude Code's own usage display shows.
OWNER ONLY, and not because of the registry: `chat` is a `confined` permission, so a member holding it
reaches every route in this router. These are the OWNER'S subscription windows and the OWNER'S transcripts,
spent by every `claude` on the machine — a member seeing them would be reading someone else's account, and
a member seeing them go red would be reading a bill they cannot act on.
A 403 rather than an empty reading: there is no per-member version of this to fall back to. Members run
`claude` as their own Linux account with their own credential, so the honest answer to "what is my usage"
for them is a different query against a different login, and inventing it here would answer the wrong one.
*/
chatRouter.get('/usage', async (ctx) => {
const who = await chatIdentity(ctx.get('user'));
if (!who.isOwner) throw errors.FORBIDDEN('Usage belongs to the account that owns this machine');
return ctx.json(await readClaudeUsage());
});
chatRouter.get('/models', async (ctx: Context) => {
const who = await chatIdentity(ctx.get('user'));
try {