Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ total_tokens = input + output + cache_creation + cache_read + r
expectedTotal() in src/lib/queue-compact.js.
```

**Cost is computed from `input_tokens + output_tokens + cached_input_tokens + cache_creation_input_tokens + reasoning_output_tokens` only — never `total_tokens`** (`computeRowCost` in `src/lib/pricing/index.js`). If a new provider only fills `total_tokens` with input=0/output=0, the dashboard renders **$0 cost** regardless of pricing entries. Distribute the total across columns or extend `computeRowCost`.
**Cost is normally computed from `input_tokens + output_tokens + cached_input_tokens + cache_creation_input_tokens + reasoning_output_tokens` only — never `total_tokens`** (`computeRowCost` in `src/lib/pricing/index.js`). The bounded exception is a compatible Hermes row explicitly marked `cost_provenance=hermes-actual`, where local API aggregates use its `actual_cost_usd` instead of model pricing. If a new provider only fills `total_tokens` with input=0/output=0, the dashboard renders **$0 cost** regardless of pricing entries. Distribute the total across columns or extend `computeRowCost`.

### Queue entry

Expand All @@ -96,6 +96,11 @@ total_tokens = input + output + cache_creation + cache_read + r

UTC, half-hour buckets, append-only — readers take the latest entry per `(source, model, hour_start)`.

Hermes rows may additionally include `actual_cost_usd` and
`cost_provenance: "hermes-actual"`, but only when every contributing local
Hermes task explicitly reports `cost_status=actual`. Unknown, estimated,
partial, older-schema, and pre-existing buckets retain normal model pricing.

### Project-wide

- CommonJS in `src/`, ESM + TypeScript strict in `dashboard/`. No mixing.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Look at how Claude Code, Codex, or Gemini are wired in for reference — they're

## Privacy Rule (non-negotiable)

TokenTracker processes **usage metadata only**: source, model, token and conversation counts, timestamps, and derived cost. The queue stores source, model, counts, and timestamps. Derived cost is not stored in the queue and may be cached in browser localStorage. Never log, store, transmit, or print prompts, responses, message bodies, or private user-code paths. Credentials are used only for declared provider authentication or quota flows and their credential files; never place them in TokenTracker queues, logs, fixtures, diagnostics, API responses, or unrelated outbound payloads. If your change touches a parser, double-check this boundary.
TokenTracker processes **usage metadata only**: source, model, token and conversation counts, timestamps, and derived cost. The queue stores source, model, counts, and timestamps. Derived cost normally comes from local model pricing and may be cached in browser localStorage; a compatible Hermes row explicitly marked `hermes-actual` may carry its local actual-cost amount instead. Never log, store, transmit, or print prompts, responses, message bodies, or private user-code paths. Credentials are used only for declared provider authentication or quota flows and their credential files; never place them in TokenTracker queues, logs, fixtures, diagnostics, API responses, or unrelated outbound payloads. If your change touches a parser, double-check this boundary.

## Releasing (maintainers only)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Historical Swift and C# source remains in the repository as an unsupported archi
- 🔒 **Your usage data never leaves your machine.** Usage metadata only — source, model, token and conversation counts, timestamps, and derived cost; never prompts, responses, message bodies, or private user-code paths. No account, no telemetry, no analytics, no phone-home. TokenTracker does make documented outbound calls *on your behalf* (for example model prices, provider authentication/quotas, and opt-in tools); none upload token-usage metadata.
- 📊 **One calm web dashboard.** Your whole picture in the browser at a local URL, no login — light or dark, auto-refreshing while the tab is open. [What's on it ↓](#-the-dashboard)
- 📈 **Quota at a glance, on every card.** Live plan-quota usage (e.g. 5h + weekly) as color-coded chips right on each provider's card — see how close you are to your limits without leaving the overview. Where the provider reports countable units you get the actual number rather than a percentage to convert in your head: GitHub Copilot reads `158/300` premium requests. Full windows + reset countdowns on the Limits page. Covers Claude, Codex, Cursor, Gemini, Kimi, Z.AI, Kiro, Copilot, and Antigravity.
- 💰 **Cost you can trust — and a price tag when it can't.** 2,200+ models priced from [LiteLLM](https://github.com/BerriAI/litellm) (refreshed daily) with a bundled offline snapshot, so USD totals are right even without a network. A model too new to have a price is badged **pricing missing** rather than quietly counted as $0, and prices refresh in the background instead of waiting for a restart. Cross-provider records are de-duplicated to match each provider's own billing.
- 💰 **Cost you can trust — and a price tag when it can't.** 2,200+ models priced from [LiteLLM](https://github.com/BerriAI/litellm) (refreshed daily) with a bundled offline snapshot, so USD totals are right even without a network. When Hermes has already persisted an explicitly actual per-model amount, the local dashboard uses that amount instead of guessing from the alias; it remains gateway-calculated, not a provider invoice. A model too new to have a price is badged **pricing missing** rather than quietly counted as $0, and prices refresh in the background instead of waiting for a restart. Cross-provider records are de-duplicated to match each provider's own billing.
- 🔌 **20+ tools, zero config.** Claude Code, Codex, Cursor, Gemini, Copilot, Antigravity, OpenCode, Kiro, Zed, Goose, and more — auto-detected, hooks auto-install on first run. Zero to dashboard in ~30 seconds.
- 🧩 **Skills tab.** Syncs 250+ public skills across your tools.

Expand Down
2 changes: 1 addition & 1 deletion agent-os/standards/global/privacy-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Changing parsers, fixtures, logs, diagnostics, queue rows, local API responses,
## Required behavior

- Permit required usage metadata: source, model, token and conversation counts, timestamps, and derived cost.
- Derived cost is not stored in the queue and may be cached in browser localStorage.
- Derived cost normally is not stored in the queue and may be cached in browser localStorage. A compatible Hermes row explicitly marked `hermes-actual` may carry its local actual-cost amount; TokenTracker does not query the gateway for it.
- Never persist or expose private content: prompts, responses, message bodies, or private user-code paths.
- Credentials are used only for declared provider authentication or quota flows and their credential files; never place them in TokenTracker queues, logs, fixtures, diagnostics, API responses, or unrelated outbound payloads.
- Use real but anonymized fixtures; remove content fields and identifying paths before committing them.
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/lib/model-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export function buildFleetData(modelBreakdown: any, { copyFn }: AnyRecord = {})
// genuinely free one, which is why isKnownZeroCostModel exists; it
// stays as the fallback for responses from an older server.
const pricingTier = typeof model?.pricing_tier === "string" ? model.pricing_tier : null;
const costProvenance =
typeof model?.cost_provenance === "string" ? model.cost_provenance : null;
const pricingMissing = pricingTier
? UNPRICED_PRICING_TIERS.has(pricingTier) && modelTokens > 0
: modelTokens > 0 && (modelCost == null || modelCost <= 0) && !isKnownZeroCostModel(name);
Expand All @@ -109,6 +111,7 @@ export function buildFleetData(modelBreakdown: any, { copyFn }: AnyRecord = {})
usage: modelTokens,
cost: modelCost,
pricingTier,
costProvenance,
pricingMissing,
pricingFuzzy,
};
Expand Down
38 changes: 38 additions & 0 deletions openspec/changes/issue-184-hermes-authoritative-cost/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Issue #184: Consume Hermes authoritative cost

## Decision

**Decision loop:** In-the-Loop. The owner selected this Phase 1 boundary on 2026-08-20.

TokenTracker will consume already-persisted per-model Hermes cost attribution from local `state.db`. It will not contact LiteLLM, hold a gateway credential, query `LiteLLM_SpendLogs`, or infer client ownership from a logical route and time window.

## Motivation

Logical gateway aliases can resolve through TokenTracker's generic fuzzy price even when Hermes later records an authoritative per-model cost. The dashboard must prefer the persisted authoritative amount when Hermes explicitly labels it actual, while retaining the current local estimate for absent, unknown, or estimated attribution.

## Scope

- Read compatible `actual_cost_usd`, `estimated_cost_usd`, `cost_status`, and `cost_source` columns from Hermes `session_model_usage`.
- Persist cost deltas with the existing local Hermes queue rows and latest-wins bucket semantics.
- Prefer an explicit Hermes `cost_status=actual` value for affected cost aggregates and expose cost provenance in the local API/dashboard model data.
- Preserve the existing behavior for older Hermes schemas and non-authoritative rows.

## Non-goals

- Direct LiteLLM API/PostgreSQL/export collection.
- Gateway credentials, request IDs, key aliases, spend-log correlation, or remote-network calls.
- Rewriting historical queue rows to retrospectively replace estimates.
- Project-level cost attribution beyond the existing local project queue contract.

## Acceptance criteria

1. A compatible Hermes row with `cost_status=actual` contributes its persisted `actual_cost_usd` instead of a LiteLLM fuzzy estimate.
2. A missing, unknown, or estimated Hermes cost retains the existing TokenTracker estimate and pricing tier.
3. A second incremental read is idempotent, and a later cumulative cost increase contributes only its delta.
4. The usage summary, daily aggregation, and model breakdown agree on the selected cost source.
5. The API exposes a non-secret provenance classification; it does not expose Hermes session IDs, prompts, credential values, request bodies, or gateway identifiers.
6. Older Hermes schemas without cost columns remain readable.

## Follow-up dependency

A separate Hermes/gateway change must populate `actual_cost_usd` and `cost_status=actual` from a trusted per-call source before this feature can show live authoritative gateway cost. The current local Hermes database reports only `unknown`/`none` cost attribution.
72 changes: 36 additions & 36 deletions openwiki-facts/source-facts.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1012",
"evidence": "src/lib/local-api.js:1026",
"mutation": false
},
{
Expand All @@ -53,7 +53,7 @@
"GET",
"HEAD"
],
"evidence": "src/lib/local-api.js:1026",
"evidence": "src/lib/local-api.js:1040",
"mutation": false
},
{
Expand All @@ -62,95 +62,95 @@
"GET",
"HEAD"
],
"evidence": "src/lib/local-api.js:1097",
"evidence": "src/lib/local-api.js:1111",
"mutation": false
},
{
"path": "/functions/tokentracker-local-sync",
"methods": [
"POST"
],
"evidence": "src/lib/local-api.js:1201",
"evidence": "src/lib/local-api.js:1215",
"mutation": true
},
{
"path": "/functions/tokentracker-wrapped",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1231",
"evidence": "src/lib/local-api.js:1245",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-summary",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1242",
"evidence": "src/lib/local-api.js:1256",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-daily",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1327",
"evidence": "src/lib/local-api.js:1341",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-heatmap",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1338",
"evidence": "src/lib/local-api.js:1352",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-model-breakdown",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1406",
"evidence": "src/lib/local-api.js:1420",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-category-breakdown",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1485",
"evidence": "src/lib/local-api.js:1510",
"mutation": false
},
{
"path": "/functions/tokentracker-project-usage-summary",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1531",
"evidence": "src/lib/local-api.js:1556",
"mutation": false
},
{
"path": "/functions/tokentracker-user-status",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1544",
"evidence": "src/lib/local-api.js:1569",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-hourly",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1554",
"evidence": "src/lib/local-api.js:1579",
"mutation": false
},
{
"path": "/functions/tokentracker-usage-monthly",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1564",
"evidence": "src/lib/local-api.js:1589",
"mutation": false
},
{
Expand All @@ -159,23 +159,23 @@
"GET",
"POST"
],
"evidence": "src/lib/local-api.js:1598",
"evidence": "src/lib/local-api.js:1623",
"mutation": true
},
{
"path": "/functions/tokentracker-usage-limits",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1747",
"evidence": "src/lib/local-api.js:1772",
"mutation": false
},
{
"path": "/functions/tokentracker-ingest-health",
"methods": [
"GET"
],
"evidence": "src/lib/local-api.js:1775",
"evidence": "src/lib/local-api.js:1800",
"mutation": false
}
]
Expand Down Expand Up @@ -243,79 +243,79 @@
},
{
"name": "parseOpencodeDbIncremental",
"evidence": "src/lib/rollout.js:2615"
"evidence": "src/lib/rollout.js:2644"
},
{
"name": "parseCursorApiIncremental",
"evidence": "src/lib/rollout.js:2804"
"evidence": "src/lib/rollout.js:2833"
},
{
"name": "parseKiroIncremental",
"evidence": "src/lib/rollout.js:3062"
"evidence": "src/lib/rollout.js:3091"
},
{
"name": "parseHermesIncremental",
"evidence": "src/lib/rollout.js:3648"
"evidence": "src/lib/rollout.js:3699"
},
{
"name": "parseKiroCliIncremental",
"evidence": "src/lib/rollout.js:4320"
"evidence": "src/lib/rollout.js:4403"
},
{
"name": "parseKimiIncremental",
"evidence": "src/lib/rollout.js:4965"
"evidence": "src/lib/rollout.js:5048"
},
{
"name": "parseKimiCodeIncremental",
"evidence": "src/lib/rollout.js:5156"
"evidence": "src/lib/rollout.js:5239"
},
{
"name": "parseCodebuddyIncremental",
"evidence": "src/lib/rollout.js:5380"
"evidence": "src/lib/rollout.js:5463"
},
{
"name": "parseRoocodeIncremental",
"evidence": "src/lib/rollout.js:5883"
"evidence": "src/lib/rollout.js:5966"
},
{
"name": "parseZedIncremental",
"evidence": "src/lib/rollout.js:6200"
"evidence": "src/lib/rollout.js:6283"
},
{
"name": "parseGooseIncremental",
"evidence": "src/lib/rollout.js:6517"
"evidence": "src/lib/rollout.js:6600"
},
{
"name": "parseDroidIncremental",
"evidence": "src/lib/rollout.js:6915"
"evidence": "src/lib/rollout.js:6998"
},
{
"name": "parseKilocodeIncremental",
"evidence": "src/lib/rollout.js:7162"
"evidence": "src/lib/rollout.js:7245"
},
{
"name": "parseOmpIncremental",
"evidence": "src/lib/rollout.js:7298"
"evidence": "src/lib/rollout.js:7381"
},
{
"name": "parsePiIncremental",
"evidence": "src/lib/rollout.js:7551"
"evidence": "src/lib/rollout.js:7634"
},
{
"name": "parseCraftIncremental",
"evidence": "src/lib/rollout.js:7835"
"evidence": "src/lib/rollout.js:7918"
},
{
"name": "parseCopilotIncremental",
"evidence": "src/lib/rollout.js:8199"
"evidence": "src/lib/rollout.js:8282"
},
{
"name": "parseGrokBuildIncremental",
"evidence": "src/lib/rollout.js:8665"
"evidence": "src/lib/rollout.js:8748"
},
{
"name": "parseAntigravityIncremental",
"evidence": "src/lib/rollout.js:8837"
"evidence": "src/lib/rollout.js:8920"
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion openwiki/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ flowchart TD
```

The runtime processes usage metadata — source, model, token and conversation counts, timestamps, and derived cost.
The queue stores the usage fields and timestamps; derived cost is computed downstream, not stored in the queue.
The queue stores usage fields and timestamps. Cost is normally computed downstream; a compatible Hermes row explicitly marked `hermes-actual` may also carry its local actual-cost amount, and TokenTracker does not query the gateway for it.
Never persist prompts, responses, message bodies, or private user-code paths.
Credentials are used only for declared provider authentication or quota flows and their credential files; never place them in TokenTracker queues, logs, fixtures, diagnostics, API responses, or unrelated outbound payloads.

Expand Down
Loading