cdx-manager 0.20.12 → 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 +9 -7
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_20_13.md +32 -0
- package/changelogs/CHANGELOGS_0_20_14.md +34 -0
- package/checksums/release-archives.json +22 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/commands/status.py +2 -3
- package/src/health.py +3 -0
- package/src/provider_runtime.py +81 -21
- package/src/run_usage.py +25 -9
- package/src/update_check.py +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
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
|
|
|
@@ -164,7 +164,7 @@ or turn historical tool output into new authorization.
|
|
|
164
164
|
- **Disk usage and cleanup.** `cdx disk` reports `CDX_HOME` usage, `cdx disk profiles --candidates` identifies reclaimable profile caches/logs with evidence, and `cdx clean profiles ...` applies explicit cleanup actions.
|
|
165
165
|
- **Health and repair.** `cdx doctor` inspects dependencies, permissions, and orphan profiles; `cdx repair` plans or applies safe fixes.
|
|
166
166
|
- **Portable bundles.** `cdx export` / `cdx import` move sessions between machines, with passphrase-encrypted auth data when you ask for it.
|
|
167
|
-
- **Update prompts.** Periodic update checks surface `cdx update` directly in the `cdx`, `cdx status`, and launch output when a newer release is available. When `logics-manager` is installed, `cdx` can also suggest `logics-manager
|
|
167
|
+
- **Update prompts.** Periodic update checks surface `cdx update` directly in the `cdx`, `cdx status`, and launch output when a newer release is available. When `logics-manager` is installed, `cdx` can also suggest `logics-manager update`.
|
|
168
168
|
- **Logics viewer shortcut.** `cdx view` opens the Logics browser/focus viewer through `logics-manager view` when the companion CLI is installed. All viewer flags are forwarded: `--lan`, `--lan-rw`, `--focus <ref>`, `--read`, `--port`, `--host`, `--refresh-interval`, `--tls`, `--tls-cert`, `--tls-key`, `--open`, `--no-open`. `cdx view --json` reports availability and update diagnostics without opening the viewer.
|
|
169
169
|
|
|
170
170
|
---
|
|
@@ -504,7 +504,7 @@ cdx power all default
|
|
|
504
504
|
cdx model provider:ollama default
|
|
505
505
|
```
|
|
506
506
|
|
|
507
|
-
`--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on`
|
|
507
|
+
`--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on` adds standing guidance that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly. This guidance is passed as instructions, never as a first message: Claude receives it through `--append-system-prompt` and Codex through `-c developer_instructions=...`, appended after any `developer_instructions` already set in the profile's `config.toml` (on Python older than 3.11 without `tomli`, a profile that sets its own is left untouched and gets no cdx guidance). Launching or resuming a session therefore never submits a turn on your behalf. Antigravity and Ollama have no instruction channel and receive no guidance. If you also pass `--append-system-prompt` through `--extra-args`, Claude receives the option twice; prefer putting your own text in the profile's `CLAUDE.md`.
|
|
508
508
|
|
|
509
509
|
### Terminal Titles
|
|
510
510
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
1
|
+
0.20.14
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CDX Manager 0.20.13
|
|
2
|
+
|
|
3
|
+
## Launches no longer open with a message you never typed
|
|
4
|
+
|
|
5
|
+
- RTK and Logics guidance used to be passed as the provider's positional prompt,
|
|
6
|
+
which Claude Code and Codex submit as a user turn: a bare `cdx <session>`
|
|
7
|
+
opened with that guidance shown as your message plus a model reply to it, and
|
|
8
|
+
every resume sent it again as a new message.
|
|
9
|
+
- Claude now receives the guidance through `--append-system-prompt`, and Codex
|
|
10
|
+
through `-c developer_instructions=...`. Launch, resume, and headless runs are
|
|
11
|
+
all covered, and no turn is ever submitted on your behalf.
|
|
12
|
+
- Codex guidance is appended after any `developer_instructions` already set in
|
|
13
|
+
the profile's `config.toml` rather than replacing it. When that value cannot be
|
|
14
|
+
read (Python older than 3.11 without `tomli`), cdx leaves it untouched and adds
|
|
15
|
+
no guidance.
|
|
16
|
+
- Antigravity has no instruction channel and, like Ollama, no longer receives
|
|
17
|
+
the guidance.
|
|
18
|
+
- `cdx doctor --check-provider-flags` now verifies the options used to pass it.
|
|
19
|
+
|
|
20
|
+
## Logics update hint
|
|
21
|
+
|
|
22
|
+
- The update notice now suggests `logics-manager update` instead of the legacy
|
|
23
|
+
`logics-manager self-update` alias.
|
|
24
|
+
|
|
25
|
+
## Validation
|
|
26
|
+
|
|
27
|
+
- 1055 Python tests and project lint pass.
|
|
28
|
+
- Verified against real CLIs on macOS, Linux (WSL), and Windows: Claude Code
|
|
29
|
+
(2.1.177, 2.1.283) and Codex CLI (0.156.1, 0.157.1) receive the guidance, and
|
|
30
|
+
interactive launches and resumes no longer add a user turn to the provider
|
|
31
|
+
transcript, where 0.20.12 added one. Codex guidance also survives a resumed
|
|
32
|
+
thread and merges with a profile's own `developer_instructions`.
|
|
@@ -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.
|
|
@@ -441,6 +441,28 @@
|
|
|
441
441
|
"x86_64-pc-windows-msvc": "183d28516f764e9a9d494feeab4406de9ad8159330082e12bf88f360f65158ac",
|
|
442
442
|
"x86_64-unknown-linux-musl": "25274c81ccb3048e4e7c96e7e6fd236bb03486dcf5b429be5c31e8dd6d841c5e"
|
|
443
443
|
}
|
|
444
|
+
},
|
|
445
|
+
"v0.20.12": {
|
|
446
|
+
"github_tarball_sha256": "463efdea19f037c6e4f04d30d3cc34649ef84e18019f23ecf1752a131437ecba",
|
|
447
|
+
"github_zip_sha256": "43ddd669cac5ab0fc7aef146d18e84670710128bba56ec39911d179f6b98abdf",
|
|
448
|
+
"tray_assets": {
|
|
449
|
+
"aarch64-apple-darwin": "14bef88f478dda41ba8af4fbe54859ff5a9c420ab58cad0168b563d00cfa1385",
|
|
450
|
+
"aarch64-unknown-linux-musl": "0d78ef10ec531a02a43c5bd059b49142721059daf2803e3d36829886b478ab0c",
|
|
451
|
+
"x86_64-apple-darwin": "0ae9b837573e35144a7a9f871f030c5445044ba66e09f1d1958d635c2efb6d8f",
|
|
452
|
+
"x86_64-pc-windows-msvc": "0dad961e2f5e79970645db96ed14ef7a7fcb8b7ce0a4441a06310a3ab16b65a4",
|
|
453
|
+
"x86_64-unknown-linux-musl": "b317cf53c0c502321662538aef083690e7673d789d91d55181b1bcffbb6d0a2f"
|
|
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
|
+
}
|
|
444
466
|
}
|
|
445
467
|
}
|
|
446
468
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/commands/status.py
CHANGED
|
@@ -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:
|
|
320
|
-
#
|
|
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/health.py
CHANGED
|
@@ -14,6 +14,7 @@ from .provider_runtime import (
|
|
|
14
14
|
HEADLESS_CODEX_PERMISSION_ARGS,
|
|
15
15
|
LAUNCH_FEATURE_ARGS,
|
|
16
16
|
LAUNCH_PERMISSION_ARGS,
|
|
17
|
+
PREFERENCE_INSTRUCTION_ARGS,
|
|
17
18
|
codex_auth_diagnostic,
|
|
18
19
|
)
|
|
19
20
|
from .status_source import _extract_account_identity
|
|
@@ -195,6 +196,8 @@ def _mapped_provider_flags(provider):
|
|
|
195
196
|
for flag in LAUNCH_FEATURE_ARGS.get(provider, []):
|
|
196
197
|
if flag.startswith("-"):
|
|
197
198
|
mappings.setdefault("cdx feature flags", set()).add(flag)
|
|
199
|
+
for flag in PREFERENCE_INSTRUCTION_ARGS.get(provider, []):
|
|
200
|
+
mappings.setdefault("cdx feature flags", set()).add(flag)
|
|
198
201
|
for permission, args in interactive.items():
|
|
199
202
|
mappings.setdefault(permission, set()).update(
|
|
200
203
|
arg for arg in args if arg.startswith("-")
|
package/src/provider_runtime.py
CHANGED
|
@@ -71,6 +71,15 @@ LAUNCH_FEATURE_ARGS = {
|
|
|
71
71
|
PROVIDER_OLLAMA: [OLLAMA_USAGE_FLAG],
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
#: How each provider receives cdx's standing guidance (RTK, logics-manager) as
|
|
75
|
+
#: instructions rather than as a first user message. A provider absent here
|
|
76
|
+
#: gets no guidance. Declared as a table so the provider-flag health check can
|
|
77
|
+
#: verify these options exist in the installed CLI.
|
|
78
|
+
PREFERENCE_INSTRUCTION_ARGS = {
|
|
79
|
+
PROVIDER_CLAUDE: ["--append-system-prompt"],
|
|
80
|
+
PROVIDER_CODEX: ["-c"],
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
LAUNCH_PERMISSION_ARGS = {
|
|
75
84
|
PROVIDER_CLAUDE: {
|
|
76
85
|
"review": ["--permission-mode", "plan"],
|
|
@@ -575,19 +584,71 @@ def _logics_enabled(session, env=None):
|
|
|
575
584
|
return _logics_manager_available(path=env.get("PATH"))
|
|
576
585
|
|
|
577
586
|
|
|
578
|
-
def
|
|
579
|
-
|
|
580
|
-
|
|
587
|
+
def _launch_preferences(session, env=None):
|
|
588
|
+
"""The standing guidance cdx adds to a session, or None.
|
|
589
|
+
|
|
590
|
+
It goes to the provider as instructions, never as the conversation's
|
|
591
|
+
first message: a positional prompt is submitted as a user turn, so a bare
|
|
592
|
+
`cdx <session>` used to open with guidance the user never typed and a
|
|
593
|
+
model reply to it. Ollama has no instruction channel and gets none;
|
|
594
|
+
Antigravity has none either (`--prompt-interactive` is a user turn).
|
|
595
|
+
"""
|
|
596
|
+
if session["provider"] not in PREFERENCE_INSTRUCTION_ARGS:
|
|
597
|
+
return None
|
|
581
598
|
prompts = []
|
|
582
599
|
if _rtk_enabled(session):
|
|
583
600
|
prompts.append(RTK_PROMPT)
|
|
584
601
|
if _logics_enabled(session, env=env):
|
|
585
602
|
prompts.append(LOGICS_PROMPT)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
603
|
+
return "\n\n".join(prompts) or None
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _codex_configured_developer_instructions(auth_home):
|
|
607
|
+
"""(readable, value) for `developer_instructions` in the profile's config.
|
|
608
|
+
|
|
609
|
+
A `-c developer_instructions=...` override replaces the configured value
|
|
610
|
+
outright, so cdx has to know it to append rather than erase it. `readable`
|
|
611
|
+
is False only when the key is present but cannot be parsed (no TOML reader
|
|
612
|
+
on Python < 3.11 without tomli): the caller then leaves it alone.
|
|
613
|
+
"""
|
|
614
|
+
try:
|
|
615
|
+
with open(os.path.join(auth_home, "config.toml"), encoding="utf-8") as handle:
|
|
616
|
+
text = handle.read()
|
|
617
|
+
except OSError:
|
|
618
|
+
return True, None
|
|
619
|
+
if "developer_instructions" not in text:
|
|
620
|
+
return True, None
|
|
621
|
+
try:
|
|
622
|
+
import tomllib
|
|
623
|
+
except ImportError:
|
|
624
|
+
try:
|
|
625
|
+
import tomli as tomllib
|
|
626
|
+
except ImportError:
|
|
627
|
+
return False, None
|
|
628
|
+
try:
|
|
629
|
+
value = tomllib.loads(text).get("developer_instructions")
|
|
630
|
+
except ValueError:
|
|
631
|
+
# Codex refuses an unparsable config itself; there is nothing to keep.
|
|
632
|
+
return True, None
|
|
633
|
+
return True, value if isinstance(value, str) and value.strip() else None
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _launch_preference_args(session, env=None):
|
|
637
|
+
preferences = _launch_preferences(session, env=env)
|
|
638
|
+
_validate_initial_prompt(preferences, label="launch guidance")
|
|
639
|
+
if not preferences:
|
|
640
|
+
return []
|
|
641
|
+
if session["provider"] == PROVIDER_CLAUDE:
|
|
642
|
+
return ["--append-system-prompt", preferences]
|
|
643
|
+
readable, configured = _codex_configured_developer_instructions(_get_auth_home(session))
|
|
644
|
+
if not readable:
|
|
645
|
+
# Better to go without cdx's guidance than to erase the user's own.
|
|
646
|
+
return []
|
|
647
|
+
if configured:
|
|
648
|
+
preferences = f"{configured.rstrip()}\n\n{preferences}"
|
|
649
|
+
# json.dumps yields a valid TOML basic string, which is how codex parses
|
|
650
|
+
# a `-c` value.
|
|
651
|
+
return ["-c", f"developer_instructions={json.dumps(preferences)}"]
|
|
591
652
|
|
|
592
653
|
|
|
593
654
|
def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None, capture_transcript=True):
|
|
@@ -598,8 +659,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
598
659
|
for key, value in _shared_tool_cache_overrides(env).items():
|
|
599
660
|
env.setdefault(key, value)
|
|
600
661
|
env.update(launch_notify_env(session, notifications_enabled(session)))
|
|
601
|
-
|
|
602
|
-
_validate_initial_prompt(initial_prompt)
|
|
662
|
+
preference_args = _launch_preference_args(session, env=env)
|
|
603
663
|
if session["provider"] == PROVIDER_CLAUDE:
|
|
604
664
|
launch = session.get("launch") or {}
|
|
605
665
|
args = ["--name", session["name"]]
|
|
@@ -609,6 +669,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
609
669
|
if launch.get("model"):
|
|
610
670
|
args += ["--model", _claude_cli_model(launch["model"])]
|
|
611
671
|
args += _launch_config_args(session)
|
|
672
|
+
args += preference_args
|
|
612
673
|
args += _extra_args(session)
|
|
613
674
|
if initial_prompt:
|
|
614
675
|
args.append(initial_prompt)
|
|
@@ -665,6 +726,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
665
726
|
if launch.get("model"):
|
|
666
727
|
args += ["--model", launch["model"]]
|
|
667
728
|
args += _launch_config_args(session)
|
|
729
|
+
args += preference_args
|
|
668
730
|
if initial_prompt:
|
|
669
731
|
args.append(initial_prompt)
|
|
670
732
|
return _wrap_launch_with_transcript(session, {
|
|
@@ -772,8 +834,7 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
|
|
|
772
834
|
if not capability["resumable"]:
|
|
773
835
|
raise CdxError(f"Provider {session.get('provider')} does not support native resume through cdx.")
|
|
774
836
|
|
|
775
|
-
|
|
776
|
-
_validate_initial_prompt(resume_prompt)
|
|
837
|
+
preference_args = _launch_preference_args(session, env=env)
|
|
777
838
|
|
|
778
839
|
if session["provider"] == PROVIDER_CLAUDE:
|
|
779
840
|
launch = session.get("launch") or {}
|
|
@@ -792,9 +853,8 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
|
|
|
792
853
|
if launch.get("model"):
|
|
793
854
|
args += ["--model", _claude_cli_model(launch["model"])]
|
|
794
855
|
args += _launch_config_args(session)
|
|
856
|
+
args += preference_args
|
|
795
857
|
args += _extra_args(session)
|
|
796
|
-
if resume_prompt:
|
|
797
|
-
args.append(resume_prompt)
|
|
798
858
|
auth_home = _get_auth_home(session)
|
|
799
859
|
claude_env = _claude_env(env, auth_home, own_terminal_title=True)
|
|
800
860
|
oauth_token = _read_claude_launch_oauth_token(auth_home)
|
|
@@ -822,9 +882,8 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
|
|
|
822
882
|
if launch.get("model"):
|
|
823
883
|
args += ["--model", launch["model"]]
|
|
824
884
|
args += _launch_config_args(session)
|
|
885
|
+
args += preference_args
|
|
825
886
|
args += _extra_args(session)
|
|
826
|
-
if resume_prompt:
|
|
827
|
-
args.append(resume_prompt)
|
|
828
887
|
return _wrap_launch_with_transcript(session, {
|
|
829
888
|
"command": "codex",
|
|
830
889
|
"args": args,
|
|
@@ -835,12 +894,12 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
|
|
|
835
894
|
}, capture_transcript=capture_transcript, env=env)
|
|
836
895
|
|
|
837
896
|
|
|
838
|
-
def _validate_initial_prompt(initial_prompt):
|
|
897
|
+
def _validate_initial_prompt(initial_prompt, label="initial_prompt"):
|
|
839
898
|
if initial_prompt is not None:
|
|
840
899
|
if not isinstance(initial_prompt, str):
|
|
841
|
-
raise CdxError("
|
|
900
|
+
raise CdxError(f"{label} must be a string.")
|
|
842
901
|
if len(initial_prompt) > 32768:
|
|
843
|
-
raise CdxError("
|
|
902
|
+
raise CdxError(f"{label} exceeds maximum allowed length.")
|
|
844
903
|
|
|
845
904
|
|
|
846
905
|
def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_prompt=None):
|
|
@@ -850,8 +909,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
850
909
|
# Headless runs read the same home, and so the same hooks, as an interactive
|
|
851
910
|
# launch. Their caller already learns of completion from the return value.
|
|
852
911
|
env.update(launch_notify_env(session, enabled=False))
|
|
853
|
-
|
|
854
|
-
_validate_initial_prompt(initial_prompt)
|
|
912
|
+
preference_args = _launch_preference_args(session, env=env)
|
|
855
913
|
launch = session.get("launch") or {}
|
|
856
914
|
power = _launch_power(session)
|
|
857
915
|
permission = launch.get("permission")
|
|
@@ -872,6 +930,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
872
930
|
if launch.get("fallback_model"):
|
|
873
931
|
args += ["--fallback-model", _claude_fallback_model(launch["fallback_model"])]
|
|
874
932
|
args += _launch_config_args(session)
|
|
933
|
+
args += preference_args
|
|
875
934
|
args += _extra_args(session)
|
|
876
935
|
if initial_prompt:
|
|
877
936
|
args.append(initial_prompt)
|
|
@@ -897,6 +956,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
897
956
|
args += _codex_fast_config_args(launch)
|
|
898
957
|
if permission:
|
|
899
958
|
args += HEADLESS_CODEX_PERMISSION_ARGS.get(permission, [])
|
|
959
|
+
args += preference_args
|
|
900
960
|
if initial_prompt:
|
|
901
961
|
args.append(initial_prompt)
|
|
902
962
|
return {
|
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
|
-
#:
|
|
93
|
-
#:
|
|
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
|
-
|
|
124
|
-
|
|
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":
|
|
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
|
|
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-
|
|
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
|
}
|
package/src/update_check.py
CHANGED
|
@@ -229,7 +229,7 @@ def check_logics_manager_for_update(base_dir, env=None, now_fn=None, runner=None
|
|
|
229
229
|
"tool": "logics-manager",
|
|
230
230
|
"latest_version": latest_version,
|
|
231
231
|
"current_version": current_version,
|
|
232
|
-
"update_command": "logics-manager
|
|
232
|
+
"update_command": "logics-manager update",
|
|
233
233
|
"url": cached.get("url"),
|
|
234
234
|
"cached": True,
|
|
235
235
|
}
|
|
@@ -255,7 +255,7 @@ def check_logics_manager_for_update(base_dir, env=None, now_fn=None, runner=None
|
|
|
255
255
|
"tool": "logics-manager",
|
|
256
256
|
"latest_version": latest_version,
|
|
257
257
|
"current_version": current_version,
|
|
258
|
-
"update_command": "logics-manager
|
|
258
|
+
"update_command": "logics-manager update",
|
|
259
259
|
"url": payload["url"],
|
|
260
260
|
"cached": False,
|
|
261
261
|
}
|