Skip to content

feat(cli): local-model cost savings, re-homed onto current main (#421)#454

Merged
iamtoruk merged 3 commits into
mainfrom
integrate/local-model-savings
Jun 6, 2026
Merged

feat(cli): local-model cost savings, re-homed onto current main (#421)#454
iamtoruk merged 3 commits into
mainfrom
integrate/local-model-savings

Conversation

@iamtoruk
Copy link
Copy Markdown
Member

@iamtoruk iamtoruk commented Jun 6, 2026

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 as savingsUSD, priced against the baseline.
  • Savings surfaced in the dashboard, JSON/CSV export, menubar payload, macOS menubar, and GNOME indicator. Daily cache bumped to v8 with a savingsConfigHash that 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 in main.ts. Those were since extracted to usage-aggregator.ts, so #423 conflicted and, if merged naively, would have duplicated functions. The savings logic was re-homed into usage-aggregator.ts; main.ts keeps 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 models report) 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

  • tsc clean, build clean, Swift menubar builds.
  • Full suite 1086 tests pass (1060 baseline + 26 savings tests).
  • No-regression diff: integrated build vs current main on the same 30 days is identical to the cent for existing accounting.
  • Two review agents confirmed the re-homed savings logic is field-for-field identical to feat(cli): track local-model cost savings against a paid baseline (#421) #423.
  • Live test on a closed 30-day window: mapping Sonnet to an Opus baseline moved Sonnet cost ($2.073) entirely to savings ($3.455 at Opus rates), dropped the total by exactly $2.073, left every other model byte-identical, and reverted cleanly on remove.

Forecasting (ability 3 of #421) remains out of scope, as noted in #423.

justingheorghe and others added 3 commits June 1, 2026 11:06
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.
@iamtoruk iamtoruk merged commit fda117f into main Jun 6, 2026
3 checks passed
@iamtoruk iamtoruk deleted the integrate/local-model-savings branch June 6, 2026 20:39
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.

[Feature] Local model cost saving reports

2 participants