Skip to content

feat(cost): read the plan a tool is signed into, and show it on Tools - #45

Merged
kleyt0n merged 4 commits into
mainfrom
feat/account-plans
Aug 5, 2026
Merged

feat(cost): read the plan a tool is signed into, and show it on Tools#45
kleyt0n merged 4 commits into
mainfrom
feat/account-plans

Conversation

@as-koshiyama

@as-koshiyama as-koshiyama commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

#44 has merged; this now targets main directly. origin/main is merged in and the diff is this PR's own three commits.

What

#44 taught surface to price the plan a tool's transcripts name. This PR reads the plan the tool is signed into right now, from the account files the tools already keep on disk, and surfaces it in three places: the SPEND card's arithmetic, the Cost view's subscription table, and a new PLAN column on the Tools view (the raw slug the tool wrote — team_tier_1, team — a dash when it names none). --json carries it as tools.list[].plan with its source.

Sources, in precedence order

  1. [cost.subscriptions] — configured, always wins.
  2. Account file — what the tool is on now, and present even for a tool that hasn't run in the window:
    • Claude Code: ~/.claude.jsonoauthAccount.seatTier (team_tier_1), falling back to userRateLimitTier for personal plans, which name no seat.
    • Codex: ~/.codex/auth.json → the OpenID token's payload names chatgpt_plan_type. Decoded (base64url, hand-rolled — 15 lines against a new dependency) for that one claim; the token is neither verified nor kept.
  3. Transcript — the plan active when usage was written (feat(cost): a SPEND card that prices seats, beside a TOKEN COST card #44's mechanism), filling gaps.

Privacy

Only the plan's name leaves the parse. The credentials beside it are never stored, logged, shown or compared; nothing is fetched. Every doc that said "reads no account state" now says precisely this instead (README, dashboard.md, costs.md, configuration.md, surface.example.toml) — the claim was already softened by #44, and this PR makes the wording exact.

Verification

  • Real machine: claude_code: plan=team_tier_1 source=account, openai_codex: plan=team source=account in --json; the SPEND card goes from ≥≈$30/mo · 1 tool(s) unpriced (transcript-only) to ≈$60.00/mo · 2 plan(s) detected.
  • Seven new tests: the Claude seat read with rate-tier fallback and signed-out → None; the Codex plan out of a fabricated JWT; a token without the claim → None, not an error; missing files → nothing; account-beats-transcript merge with gap-filling; the detection-id ↔ usage-id translation; and the Tools view rendering the PLAN column.
  • cargo fmt, clippy -D warnings, all 256 tests pass; --json --offline degrades cleanly.

🤖 Generated with Claude Code

A transcript names the plan that was active when usage was written; the
account file names the plan the tool is signed into now, and exists
even for a tool that has not run in the window. Two more sources, in a
new scan section:

- Claude Code caches its seat in ~/.claude.json: oauthAccount.seatTier
  (team_tier_1), falling back to userRateLimitTier for personal plans,
  which name no seat.
- Codex keeps an OpenID token in ~/.codex/auth.json whose payload names
  chatgpt_plan_type. The payload is base64url-decoded for that one
  claim - hand-rolled, fifteen lines against a dependency - and the
  token is neither verified nor kept.

Only the plan's name leaves the parse. The credentials beside it are
never stored, logged or shown, nothing is fetched, and the docs that
said "reads no account state" now say precisely this instead.

Account beats transcript beats nothing; [cost.subscriptions] beats all
three. The merged map lives on Scan, so the SPEND card and the Cost
view price it, the Tools view shows it in a new PLAN column - the raw
slug the tool wrote, a dash when it names none - and --json carries it
as tools.list[].plan with its source.

Verified on a real machine: claude_code=team_tier_1 and codex=team,
both source=account, pricing the SPEND card at ~$60/mo where before
only the transcript-named Codex plan priced $30 of it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
as-koshiyama and others added 2 commits August 1, 2026 21:07
… Tools

Pricing the seat slug under-reported a real Team premium seat by 70%:
seatTier reads team_tier_1 for standard and premium seats alike ($30 at
list), while the seat actually paid for carries Max-class limits -
userRateLimitTier: default_claude_max_5x, $100 at list, which is what
its operator in fact pays. The rate-limit tier names the capacity the
seat buys, so it wins when it carries a known list price; a standard
seat's rate tier is unpriceable and falls back to the seat slug, and a
personal plan names no seat at all. The known-slug table moves to
config::list_price so detection and pricing cannot drift apart.

The Tools view gains a $/MO column beside PLAN: plain when the figure
is configured, marked with the estimate sign at a plan's list price -
which is exactly where a wrong estimate gets seen and corrected with
one [cost.subscriptions] line. Estimates can only ever be estimates;
the config is the accuracy mechanism, and now the dashboard shows
which figures deserve it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OpenAI repriced Business (ne Team) on 2026-04-02: $25/seat monthly, $20 annual. The shared 'team' arm also priced Claude's team_tier_1, so the two vendors' slugs split into labelled arms - one shared arm was quietly wrong for one vendor. The table carries monthly-billing list rates and says so; annual billing being lower is one more reason every figure from it is marked an estimate and a config entry wins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kleyt0n
kleyt0n previously approved these changes Aug 1, 2026

@kleyt0n kleyt0n left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. for the next PRs, I recommend also create a Issue, so we can track the features in the roadmap

@kleyt0n
kleyt0n changed the base branch from feat/spend-vs-token-cost to main August 3, 2026 10:31
@kleyt0n
kleyt0n dismissed their stale review August 3, 2026 10:31

The base branch was changed.

@kleyt0n
kleyt0n merged commit 33a8aec into main Aug 5, 2026
7 checks passed
@kleyt0n
kleyt0n deleted the feat/account-plans branch August 5, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants