Commit graph

3 commits

Author SHA1 Message Date
le king fu
9084d90609 feat(server): archive closed Claude usage days by month, serve them on /claude-usage/history
POST /claude-usage now merges every day of the sanitized snapshot except
the last one (today, still open) into <dir of CLAUDE_USAGE_PATH>/history/
YYYY-MM.json, after the snapshot itself is written. The latest snapshot
wins, so a day leaving the 30-day window keeps the value the agent froze
at D+2. Each month file is written atomically (same-dir temp, mode 600,
rename). A month file that cannot be read back is never overwritten, and
an archive failure is logged (months and error codes only) without
changing the 204.

GET /claude-usage/history?from=YYYY-MM&to=YYYY-MM (read token) serves the
archived days, defaulting to the 12 months up to the last archived month,
400 on a bad format, from > to or a span over 36 months. Month files are
re-read through the snapshot's per-day whitelist, now exported from
claude-usage.js as sanitizeDay().

The new module also carries the one-shot import command used by the
backfill (`node claude-usage-history.js --import <file|->`), where an
already archived date always wins.

Refs #55

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:41:30 -04:00
le king fu
e2a24d768b fix(server): require a full ISO receivedAt on read, cover the temp-file cleanup
readClaudeUsage() and readHostSnapshot() validated the stored receivedAt
with new Date(), which accepts "1" or a bare date. Both now go through
isIsoTimestamp(), exported by claude-usage.js and built on the sanitizer's
own check, so no regex is duplicated.

The existing 500 tests failed at mkdir, before the temp file existed, so
removing the unlinkSync() cleanup of either writer left the suite green. Two
new cases make the rename fail after the temp write (the target is a
non-empty directory) and require that no temp file is left.

Refs #54

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:25:26 -04:00
le king fu
ef8558e90e test(claude-usage): cover the routes, the sanitizer and the new write surface
Some checks are pending
CI / verification (pull_request) Successful in 26s
CI / verification (push) Waiting to run
- __tests__/claude-usage.test.js (119 cases): sanitizer contract and
  idempotence, dropped values (unknown fields, negative, past 2^53,
  detail outside its regex, status outside the enum, bad dates, model
  keys), list bounds, token isolation both ways, 503 naming each variable,
  413 at 65 537 bytes and /hosts/<id> still 413 at 4 097, atomic mode-600
  write, POST then GET round trip, exact staleness boundary under a frozen
  clock, no_data degradation, rejection logs, COPY line.
- writes.test.js: POST /claude-usage may write only in the directory of
  CLAUDE_USAGE_PATH, its rejections write nothing, GET /claude-usage joins
  the read routes.
- README.md and CLAUDE.md: both routes, the three env vars, the
  /data/claude-usage read-write mount owned by uid 1000, the token table.

Resolves #48

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:16:11 -04:00