feat(cli): local-model cost savings, re-homed onto current main (#421)#454
Merged
Conversation
Add a new `localModelSavings` config and `codeburn model-savings` CLI that maps a local-model name (e.g. llama3.1:8b) to a paid baseline (e.g. gpt-4o). The local call still costs $0; the new `savingsUSD` field tracks the counterfactual spend avoided by running locally and is reported separately from `costUSD` everywhere a number is shown. * Parser normalization (`applyLocalModelSavings`) runs on Claude parse, direct provider calls, and the cached-call path. It forces `costUSD` to 0 and attaches `savingsUSD` + `savingsBaselineModel` + `isLocalSavings` on the `ParsedApiCall`. Local-savings wins for actual cost even when the same model is also in `modelAliases`. * Session, project, day, model, category, activity, skill, and subagent rollups all carry `savingsUSD` alongside `costUSD`. * `status --format json` adds `today.savings` and `month.savings`. * `status --format menubar-json` adds a `current.localModelSavings` block (totalUSD, calls, byModel, byProvider) plus savings on topModels, topProjects, topSessions, topActivities, and history daily entries. Schema fields default-decode for backward compat. * `report --format json` adds savings across overview/daily/ projects/models/activities/skills/subagents/topSessions, with the active paid baseline name on each model row. * `models` command gains a `Saved` column on table/markdown/CSV and a `savingsUSD`/`savingsBaselineModel` pair in JSON. Default `--min-cost 0.01` filter now ORs in `savingsUSD >= minCost` so local models with $0 actual cost but >0 savings still surface. * CSV/JSON exports add a `Saved (CODE)` column on summary/daily/ models/projects/sessions. * Dashboard TUI shows a green 'saved $X by local models' footer line in the overview when any savings are present. * macOS Swift payload gains a `LocalModelSavings` Codable block and savings fields on every model/activity/session/daily struct. Hero shows a green leaf 'Saved $X' caption, models section gets a green `Saved` column. `swift build` clean. * GNOME indicator adds 'saved $X' to the hero meta line and a `codeburn-model-saved` column to the model row. * Daily cache schema bumped to v8 (`savingsUSD` on day/model/ category/provider). `savingsConfigHash` invalidates the cache when the user changes their baseline mapping so historical saved-spend numbers never lie about a stale baseline. * Defensive `Object.hasOwn` lookup in `getLocalSavingsBaseline` blocks the prototype-pollution test that previously surfaced via the savings path with a hostile `__proto__` model name. * New tests (5 files, 25 tests, 549 lines) cover pricing helpers, end-to-end parser normalization, day aggregator savings, menubar payload savings, CLI set/list/remove, and daily-cache hash invalidation. Existing tests for daily-cache / day-aggregator / models-report updated for the new fields. Full vitest suite: 1028/1028 passing across 73 test files. `tsc --noEmit` clean. `npm run build` clean. (Note: `mac/Tests` has a pre-existing `no such module 'Testing'` environment error on the installed Swift toolchain, confirmed on `main` before this PR; not caused by these changes.)
…ngs into usage-aggregator Resolves conflicts from the post-PR refactor that moved buildPeriodData, hydrateCache, and the menubar payload builder out of main.ts into usage-aggregator.ts. The PR's savings additions to those functions are re-homed there; config.ts keeps both new fields; parser.ts keeps both imports; redact.ts session details carry savingsUSD.
Without a model-savings mapping the Saved column was an unlabeled column of dashes between Cost and Calls (menubar) and a dim '-' column (CLI models report). Show it only when at least one model has savingsUSD > 0, so users with no mapping keep the plain Cost/Calls layout. The menubar Hero caption was already conditional.
This was referenced Jun 6, 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.
Integrates @justingheorghe's PR #423 (local-model cost savings, issue #421) onto current main. Closes #421. Supersedes #423, which conflicted after the payload-builder refactor.
What it adds
codeburn model-savings <local> <baseline>maps a local model to a paid baseline. Mapped calls have actual cost forced to $0 and the avoided spend recorded separately assavingsUSD, priced against the baseline.savingsConfigHashthat re-hydrates history when the mapping changes.Why this branch instead of #423
#423 was written when
buildPeriodData,hydrateCache, and the menubar payload builder lived inmain.ts. Those were since extracted tousage-aggregator.ts, so #423 conflicted and, if merged naively, would have duplicated functions. The savings logic was re-homed intousage-aggregator.ts;main.tskeeps only the CLI command, status-json fields, and preAction wiring.UX fix on top of the PR
The Saved column (menubar Models section and the CLI
modelsreport) now appears only when there is actual savings, instead of showing an unlabeled column of dashes for users with no mapping. Hero and TUI dashboard were already conditional.Verification
Forecasting (ability 3 of #421) remains out of scope, as noted in #423.