cdx-manager 0.20.13 → 0.20.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <br clear="left"/>
6
6
 
7
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.20.13-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
7
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.20.14-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
8
8
 
9
9
  **Stop guessing which AI account still has quota.** `cdx` tracks the rate-limit window of every Codex and Claude account you own, tells you which one is usable right now, and launches it with isolated auth — in one command.
10
10
 
@@ -773,15 +773,17 @@ Every usage record, whichever path produced it, uses one set of definitions:
773
773
 
774
774
  Cached input is counted once: it is excluded from `input_tokens` and reported in the cache fields, never in both. `null` means the provider did not report the figure, which is not the same as zero.
775
775
 
776
- `cdx stats` also shows a **`COST~`** column and ranks sessions by it. It weights each token class by what it costs relative to one uncached input token — cache read 0.1x, cache write 1.25x, and output at the recorded model's output/input ratio — and reports the result in uncached-input-equivalent tokens. These are ratios rather than prices. It is **not** a currency amount, and it is not comparable across providers whose own prices differ. The cache-write multiplier assumes the five-minute cache TTL; a one-hour TTL costs 2x and nothing in a transcript says which was used, so long-TTL writes are slightly under-weighted.
776
+ `cdx stats` also shows a **`COST~`** column and ranks sessions by it. It weights each token class by what it costs relative to one uncached input token — cache read at the recorded model's ratio (0.1x by default), cache write 1.25x, and output at the recorded model's output/input ratio — and reports the result in uncached-input-equivalent tokens. These are ratios rather than prices. It is **not** a currency amount, and it is not comparable across providers whose own prices differ. The cache-write multiplier assumes the five-minute cache TTL; a one-hour TTL costs 2x and nothing in a transcript says which was used, so long-TTL writes are slightly under-weighted.
777
777
 
778
778
  Ranking on the raw total would put a session that mostly replayed a cache above one that spent far more on generation, because cache reads are typically the overwhelming majority of tokens consumed while costing a fiftieth of an output token.
779
779
 
780
- The **`USD~`** column turns that into money for runs whose serving model cdx recorded. It needs one number per model — the list price per million uncached input tokens — because `COST~` has already absorbed every other rate. Prices live in configuration, not in code: set `CDX_TOKEN_PRICES` to a JSON object mapping model id to dollars per million tokens to override the built-in table, whose last review date is printed with the totals. A run whose model cdx never saw stays unpriced rather than being charged at a default tier, and the totals line says how many runs were actually priced.
780
+ The **`USD~`** column turns that into money for runs whose serving model cdx recorded. It prices each token class directly using the built-in model table. To customize prices: set `CDX_TOKEN_PRICES` to a JSON object mapping model id to dollars per million tokens to override the built-in table, whose last review date is printed with the totals. A run whose model cdx never saw stays unpriced rather than being charged at a default tier, and the totals line says how many runs were actually priced.
781
781
 
782
- The table carries two numbers per model — uncached input and output per million tokens — and derives the cache rates from the input rate: a cache read is 0.1x it and a cache write 1.25x, a relationship that holds on the supported non-Pro model rows. Each token class is then priced at its own rate rather than through a weighted shortcut, because output/input ratios vary by model.
782
+ The table requires `input` and `output` prices in USD per million tokens and accepts an optional `cache_read` price in the same units. Without it, cache reads cost 0.1x input; cache writes assume 1.25x input. GPT-6.1 Sol and Claude Opus 5.5 use 0.05x cache reads, while Claude Fable/Mythos 5.1 use 0.025x. Both `COST~` and `USD~` honor those rates. `CDX_TOKEN_PRICES` replaces the supplied model row: a two-field override keeps the legacy 0.1x fallback; include `cache_read` to override it (including zero).
783
783
 
784
- Three caveats stated rather than hidden. A run is priced at the model serving its most recent record, so a run that switched models mid-way is priced at the newer one. The figure is an estimate at public list prices — not an invoice. And OpenAI's long-context tier, which roughly doubles both rates above a 272K-token request, is **not** modelled: cdx records tokens per run rather than per request, so it cannot tell which requests crossed that line, and long-context Codex work is under-costed.
784
+ Three caveats stated rather than hidden. A run is priced at the model serving its most recent record, so a run that switched models mid-way is priced at the newer one. The figure uses standard public list prices, excluding Fast/priority, Batch/Flex, regional premiums, and tool fees — not an invoice. And OpenAI's long-context tier, which increases rates above a 272K-token request, is **not** modelled: cdx records tokens per run rather than per request, so it cannot tell which requests crossed that line, and long-context Codex work is under-costed.
785
+
786
+ The built-in price table ships with **cdx-manager**. Run `cdx update` to receive new model prices; updating Codex or Claude alone does not refresh it. Then check `cdx --version` and `cdx stats --json`. Existing runs with a recorded model are recalculated using the new table when stats are read. `CDX_TOKEN_PRICES` overrides still take precedence. Maintainers: follow [the model and pricing update runbook](logics/runbook/run_005_maintaining_the_token_price_table.md) for source checks, tests, release preparation and installed-version verification.
785
787
 
786
788
  A model absent from the table stays unpriced rather than being charged a default tier, and the totals line names it — which is exactly the key `CDX_TOKEN_PRICES` wants:
787
789
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.20.13
1
+ 0.20.14
@@ -0,0 +1,34 @@
1
+ # CDX Manager 0.20.14
2
+
3
+ ## Current model prices in stats
4
+
5
+ - Add GPT-6.1 Sol, GPT-6 Sol, GPT-6 Luna, Claude Fable 5.1, Claude Mythos 5.1,
6
+ Claude Opus 5.5 and Claude Sonnet 5.5 to the built-in token price table,
7
+ reviewed against official provider documentation on 2026-10-04.
8
+ - Honor model-specific cache-read prices in both `USD~` and weighted `COST~`
9
+ stats: 5% of input for GPT-6.1 Sol and Opus 5.5, and 2.5% for Fable/Mythos 5.1.
10
+ - `CDX_TOKEN_PRICES` accepts an optional `cache_read` price in USD per million
11
+ tokens, including zero. Existing two-field overrides keep the 10% fallback.
12
+ - Recorded runs using these models are priced when stats are read. Unknown
13
+ models remain unpriced; account model selections are unchanged.
14
+
15
+ ## Updating the price table
16
+
17
+ - Run `cdx update` after publication to receive the new table. Updating only
18
+ Codex or Claude does not update cdx-manager's built-in prices.
19
+ - Document the complete maintainer and installed-machine workflow in the
20
+ pricing runbook: exact IDs, official sources, helper limitations, regression
21
+ tests, release preparation and version/stats verification.
22
+ - Costs remain estimates at standard short-context list prices. Fast/priority,
23
+ Batch/Flex, regional premiums, tool fees, one-hour cache writes and per-request
24
+ long-context premiums are not modelled.
25
+
26
+ ## Validation
27
+
28
+ - All 1058 Python tests pass; project lint and Logics lint pass.
29
+ - npm package contents, Python sdist/wheel build and Twine metadata checks pass.
30
+
31
+ - Regression coverage includes the seven model rates, mixed token classes,
32
+ cache overrides, backwards compatibility and stats ranking.
33
+ - Local history recognizes all eight recorded models, including 30 Opus 5.5
34
+ runs that were previously unpriced.
@@ -452,6 +452,17 @@
452
452
  "x86_64-pc-windows-msvc": "0dad961e2f5e79970645db96ed14ef7a7fcb8b7ce0a4441a06310a3ab16b65a4",
453
453
  "x86_64-unknown-linux-musl": "b317cf53c0c502321662538aef083690e7673d789d91d55181b1bcffbb6d0a2f"
454
454
  }
455
+ },
456
+ "v0.20.13": {
457
+ "github_tarball_sha256": "009d4bfd6ea139fe2968f16c49dd21ca4b5f675637eeea2e2b23b77216adeb3a",
458
+ "github_zip_sha256": "425d9cd004d6aa282596f754a4afa04615b8954f022d4479e3840ccf5b0a81e6",
459
+ "tray_assets": {
460
+ "aarch64-apple-darwin": "313b7e39cd2c18dfd0a0f0cc7ea31ff7a30bea7df0b678dea777802413888650",
461
+ "aarch64-unknown-linux-musl": "fabfd1e1ab12c6c7c7f014258d70b73f269e046b56f46e55f49f7545c5a6ada6",
462
+ "x86_64-apple-darwin": "d8cf73b4cd1f89d883dc0e029d38f777d8e99da5c0db1d9853437ecfe05ebd42",
463
+ "x86_64-pc-windows-msvc": "9c55cab41d489afa77279de219dbc3ad861ad23d3ee50cee55d995033acb0c20",
464
+ "x86_64-unknown-linux-musl": "04794bc12fd5e367cb44af579aa70464f12680945dc92bbe6b0eda275a88141a"
465
+ }
455
466
  }
456
467
  }
457
468
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.20.13",
3
+ "version": "0.20.14",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.20.13"
7
+ version = "0.20.14"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -316,9 +316,8 @@ def _summarize_stats(entries):
316
316
  # model cdx never saw stays unpriced rather than being charged at a
317
317
  # default tier, and `priced_runs` says how much of the figure is
318
318
  # actually covered.
319
- # Weighted per entry, not on the summed row: the output ratio is
320
- # the one multiplier that differs between vendors, and a session's
321
- # runs can span models.
319
+ # Weighted per entry, not on the summed row: output and cache-read
320
+ # ratios vary by model, and a session's runs can span models.
322
321
  row["weighted_tokens"] += weighted_usage(
323
322
  entry.get("usage"), entry.get("usage_model")) or 0
324
323
  cost = estimate_cost(entry.get("usage"), entry.get("usage_model"))
package/src/run_usage.py CHANGED
@@ -89,8 +89,8 @@ def normalize_usage(input_tokens=None, cache_creation_tokens=None, cache_read_to
89
89
 
90
90
  #: What a cached token costs relative to one uncached input token.
91
91
  #:
92
- #: Verified against both vendors on 2026-08-14 and identical on each: a cache
93
- #: read is a 90% discount on the input rate, and a cache write is 1.25x it.
92
+ #: Defaults for rows without an explicit cache-read price. Newer models can
93
+ #: discount reads further; both stats and currency use the table's rate.
94
94
  #: Anthropic's one-hour cache TTL costs 2x instead; nothing in a transcript
95
95
  #: says which TTL was used, so long-TTL writes are under-weighted. Cache writes
96
96
  #: are a small share of a cache-heavy session, so that error is bounded --
@@ -112,6 +112,14 @@ def output_multiplier(model, prices=None):
112
112
  return entry["output"] / entry["input"]
113
113
 
114
114
 
115
+ def cache_read_multiplier(model, prices=None):
116
+ """This model's cache-read/input ratio, with the legacy fallback."""
117
+ entry = (prices if prices is not None else token_prices()[0]).get(model or "")
118
+ if not entry or "cache_read" not in entry:
119
+ return CACHE_READ_MULTIPLIER
120
+ return entry["cache_read"] / entry["input"]
121
+
122
+
115
123
  def weighted_usage(usage, model=None, prices=None):
116
124
  """Consumption in uncached-input-equivalent tokens, or None if unknown.
117
125
 
@@ -120,9 +128,8 @@ def weighted_usage(usage, model=None, prices=None):
120
128
  tokens -- so a raw total is very nearly a measure of cache reads alone.
121
129
  That is not the ranking anyone reading `cdx stats` is looking for.
122
130
 
123
- The output ratio comes from the model that served the run, because it is
124
- the one multiplier that differs between vendors. The cache multipliers do
125
- not, so they are constants.
131
+ Output and cache-read ratios come from the model that served the run.
132
+ Cache writes retain the five-minute TTL assumption.
126
133
 
127
134
  `reasoning_tokens` is deliberately excluded: Codex reports it as a subset
128
135
  of its output, and counting it would bill those tokens twice.
@@ -132,7 +139,7 @@ def weighted_usage(usage, model=None, prices=None):
132
139
  weights = {
133
140
  "input_tokens": 1.0,
134
141
  "cache_creation_tokens": CACHE_WRITE_MULTIPLIER,
135
- "cache_read_tokens": CACHE_READ_MULTIPLIER,
142
+ "cache_read_tokens": cache_read_multiplier(model, prices),
136
143
  "output_tokens": output_multiplier(model, prices),
137
144
  }
138
145
  present = [
@@ -160,12 +167,16 @@ def weighted_usage(usage, model=None, prices=None):
160
167
  #: Anthropic and OpenAI figures come from the vendors' own references, checked
161
168
  #: on the review date below.
162
169
  #:
163
- #: Not modelled: OpenAI's long-context tier, which roughly doubles both rates
170
+ #: Not modelled: OpenAI's long-context tier, which increases the rates
164
171
  #: above a 272K-token request. cdx records tokens per run, not per request, so
165
172
  #: it cannot tell which requests crossed that line -- long-context Codex work
166
173
  #: is therefore under-costed here, and knowingly so.
167
174
  DEFAULT_TOKEN_PRICES = {
168
175
  # Anthropic
176
+ "claude-fable-5-1": {"input": 10.0, "output": 50.0, "cache_read": 0.25},
177
+ "claude-mythos-5-1": {"input": 10.0, "output": 50.0, "cache_read": 0.25},
178
+ "claude-opus-5-5": {"input": 4.0, "output": 20.0, "cache_read": 0.2},
179
+ "claude-sonnet-5-5": {"input": 2.0, "output": 10.0},
169
180
  "claude-fable-5": {"input": 10.0, "output": 50.0},
170
181
  "claude-mythos-5": {"input": 10.0, "output": 50.0},
171
182
  "claude-opus-5": {"input": 5.0, "output": 25.0},
@@ -180,6 +191,9 @@ DEFAULT_TOKEN_PRICES = {
180
191
  "claude-haiku-4-5": {"input": 1.0, "output": 5.0},
181
192
  "claude-haiku-4-5-20251001": {"input": 1.0, "output": 5.0},
182
193
  # OpenAI
194
+ "gpt-6.1-sol": {"input": 2.0, "output": 10.0, "cache_read": 0.1},
195
+ "gpt-6-sol": {"input": 2.0, "output": 10.0},
196
+ "gpt-6-luna": {"input": 0.1, "output": 0.5},
183
197
  "gpt-6-astra": {"input": 10.0, "output": 50.0},
184
198
  "gpt-5.6": {"input": 4.0, "output": 20.0},
185
199
  "gpt-5.6-sol": {"input": 4.0, "output": 20.0},
@@ -195,7 +209,7 @@ DEFAULT_TOKEN_PRICES = {
195
209
  "gpt-5.4-nano-2026-03-17": {"input": 0.2, "output": 1.25},
196
210
  "gpt-5.3-codex": {"input": 1.75, "output": 14.0},
197
211
  }
198
- TOKEN_PRICES_REVIEWED = "2026-09-05"
212
+ TOKEN_PRICES_REVIEWED = "2026-10-04"
199
213
  #: How long a review stays fresh before the staleness test asks for another.
200
214
  TOKEN_PRICES_MAX_AGE_DAYS = 90
201
215
  TOKEN_PRICES_ENV = "CDX_TOKEN_PRICES"
@@ -218,6 +232,8 @@ def token_prices(env=None):
218
232
  if isinstance(entry, dict) and "input" in entry and "output" in entry:
219
233
  merged[str(name)] = {
220
234
  "input": float(entry["input"]), "output": float(entry["output"])}
235
+ if "cache_read" in entry:
236
+ merged[str(name)]["cache_read"] = float(entry["cache_read"])
221
237
  return merged, TOKEN_PRICES_ENV
222
238
  return dict(DEFAULT_TOKEN_PRICES), f"built-in, reviewed {TOKEN_PRICES_REVIEWED}"
223
239
 
@@ -244,7 +260,7 @@ def estimate_cost(usage, model, prices=None):
244
260
  return None
245
261
  per_million = {
246
262
  "input_tokens": rate["input"],
247
- "cache_read_tokens": rate["input"] * CACHE_READ_MULTIPLIER,
263
+ "cache_read_tokens": rate.get("cache_read", rate["input"] * CACHE_READ_MULTIPLIER),
248
264
  "cache_creation_tokens": rate["input"] * CACHE_WRITE_MULTIPLIER,
249
265
  "output_tokens": rate["output"],
250
266
  }