feat(cost): read the plan a tool is signed into, and show it on Tools - #45
Merged
Conversation
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>
… 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
previously approved these changes
Aug 1, 2026
kleyt0n
left a comment
Contributor
There was a problem hiding this comment.
lgtm. for the next PRs, I recommend also create a Issue, so we can track the features in the roadmap
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).--jsoncarries it astools.list[].planwith its source.Sources, in precedence order
[cost.subscriptions]— configured, always wins.~/.claude.json→oauthAccount.seatTier(team_tier_1), falling back touserRateLimitTierfor personal plans, which name no seat.~/.codex/auth.json→ the OpenID token's payload nameschatgpt_plan_type. Decoded (base64url, hand-rolled — 15 lines against a new dependency) for that one claim; the token is neither verified nor kept.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
claude_code: plan=team_tier_1 source=account,openai_codex: plan=team source=accountin--json; the SPEND card goes from≥≈$30/mo · 1 tool(s) unpriced(transcript-only) to≈$60.00/mo · 2 plan(s) detected.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 --offlinedegrades cleanly.🤖 Generated with Claude Code