cdx-manager 0.14.0 → 0.15.1
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 +84 -18
- package/changelogs/CHANGELOGS_0_15_0.md +144 -0
- package/changelogs/CHANGELOGS_0_15_1.md +85 -0
- package/checksums/release-archives.json +16 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli_args.py +51 -7
- package/src/cli_helpers.py +13 -0
- package/src/cli_render.py +17 -0
- package/src/commands/runs.py +306 -19
- package/src/commands/status.py +41 -9
- package/src/config.py +29 -0
- package/src/provider_background.py +263 -0
- package/src/provider_runtime.py +96 -4
- package/src/run_failover.py +237 -0
- package/src/run_registry.py +115 -3
- package/src/session_service.py +126 -2
- package/src/session_status.py +18 -1
- package/src/status_source.py +59 -0
package/README.md
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
-
[](LICENSE) ](LICENSE)  
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**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.
|
|
6
6
|
|
|
7
|
-
If you
|
|
7
|
+
If you pay for several AI coding subscriptions, you know the dance: you hit a 5-hour limit mid-task, guess which other account still has room, re-authenticate, and lose your context on the way. `cdx` turns that into `cdx next`.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+

|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g cdx-manager
|
|
17
|
+
|
|
18
|
+
cdx add codex work # register an isolated Codex profile named "work"
|
|
19
|
+
cdx add claude perso # and a Claude one
|
|
20
|
+
cdx status # every account's remaining quota, side by side
|
|
21
|
+
cdx next # which account should I use right now?
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Supported providers: `codex`, `claude`, `antigravity`, `ollama`.
|
|
25
|
+
|
|
26
|
+
`cdx next` answers the question you actually have, and prints the command to run:
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## The Core Five
|
|
33
|
+
|
|
34
|
+
Everything else is optional. These five cover the daily loop:
|
|
35
|
+
|
|
36
|
+
| Command | What it gives you |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `cdx add <provider> <name>` | An isolated account profile — its own auth, its own home |
|
|
39
|
+
| `cdx <name>` | Launch that account, auth checked first |
|
|
40
|
+
| `cdx status` | Remaining 5-hour and weekly quota for every account at once |
|
|
41
|
+
| `cdx next` | The account you should use right now, and why |
|
|
42
|
+
| `cdx run --prompt ... --json` | The same routing, headless, for scripts and agents |
|
|
13
43
|
|
|
14
44
|
---
|
|
15
45
|
|
|
16
46
|
## Table of Contents
|
|
17
47
|
|
|
48
|
+
- [Quick Start](#quick-start)
|
|
49
|
+
- [The Core Five](#the-core-five)
|
|
18
50
|
- [What it does](#what-it-does)
|
|
19
51
|
- [Technical Overview](#technical-overview)
|
|
20
52
|
- [Getting Started](#getting-started)
|
|
@@ -32,24 +64,58 @@ One command to launch any session. Zero auth juggling.
|
|
|
32
64
|
|
|
33
65
|
## What it does
|
|
34
66
|
|
|
67
|
+
### Quota-aware routing
|
|
68
|
+
|
|
69
|
+
The reason `cdx` exists: knowing, across every account you own, which one you can actually use.
|
|
70
|
+
|
|
71
|
+
- **Usage at a glance.** `cdx status` shows token usage, 5-hour window quota, weekly quota, last-updated timestamps, priority guidance, and the last launched session in one aligned table.
|
|
72
|
+
- **Pick for me.** `cdx next` selects the best available assistant from your pinned priorities and the live quota picture, shows the reason, and prints the exact command to run.
|
|
73
|
+
- **Next-ready notification.** `cdx ready` schedules a native system notification for the next assistant that comes back from cooldown, then returns immediately. `cdx notify` covers reset times and scheduled wake-ups.
|
|
74
|
+
- **Passive status resolution.** Codex status is read from the local Codex app-server rate-limit API when available, with legacy transcript/history parsing kept as a fallback.
|
|
75
|
+
- **Banked reset visibility.** Eligible Codex accounts show the number of manually redeemable bonus resets in `cdx status` and expose reset details in JSON output.
|
|
76
|
+
|
|
77
|
+
### Isolated multi-account sessions
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
35
81
|
- **Multiple providers, one tool.** Register as many Codex, Claude, Antigravity, or Ollama sessions as you need. Codex and Claude get isolated auth environments; Antigravity is launchable through `agy` with OS-keyring auth; Ollama runs local models through `ollama run`.
|
|
36
82
|
- **Instant launch.** `cdx work` opens your "work" session. `cdx personal` opens another. No config files to edit mid-flow.
|
|
37
83
|
- **Quick relaunch.** `cdx last` reopens the most recently launched assistant profile.
|
|
84
|
+
- **Resume by identity, not by recency.** Each session records the provider's own conversation id — imposed at launch for Claude, read back from the rollout for Codex — so `cdx resume <name>` reopens *that* conversation regardless of your working directory or of which session ran last. `cdx can-resume --json` reports the strategy and where the id came from, so an automated caller can tell a named resume from a best-effort one.
|
|
38
85
|
- **Auth guardrails.** `cdx` checks authentication before launching. If a session is not logged in, it tells you exactly what to run — no silent failures.
|
|
39
|
-
- **Optional session labels.** `cdx label <name> <label>` adds a short human label; list and full status tables show a `LABEL` column only when at least one session has one.
|
|
40
|
-
- **Usage at a glance.** `cdx status` shows token usage, 5-hour window quota, weekly quota, last-updated timestamps, priority guidance, and the last launched session in one aligned table.
|
|
41
|
-
- **Next-ready notification.** `cdx ready` schedules a native system notification for the next assistant that comes back from cooldown, then returns immediately.
|
|
42
|
-
- **Session control.** Disable a session without deleting it when an account is temporarily out of credits; disabled sessions remain visible and sort last.
|
|
43
86
|
- **Persistent launch settings.** Pin per-session power, permission, and fast-mode preferences once; `cdx` reapplies them on every launch until you unset them.
|
|
87
|
+
- **Spend ceiling and model fallback.** `cdx set <name> --budget 5` caps what a headless run may spend, and `--fallback-model sonnet,haiku` lets a run continue on the next model when the first is overloaded. Both are Claude Code flags that the provider accepts with `--print` only, so they apply to `cdx run` and not to interactive launches; `cdx configs` says so.
|
|
88
|
+
- **Session control.** Disable a session without deleting it when an account is temporarily out of credits; disabled sessions remain visible and sort last.
|
|
89
|
+
- **Optional session labels.** `cdx label <name> <label>` adds a short human label; list and full status tables show a `LABEL` column only when at least one session has one.
|
|
90
|
+
- **Clean removal.** `cdx rmv` wipes a session and its entire auth directory. No orphaned files, no stale credentials.
|
|
91
|
+
|
|
92
|
+
### Headless automation
|
|
93
|
+
|
|
94
|
+
- **One task, one JSON result.** `cdx run` executes a prompt against a chosen or auto-selected session and returns a stable payload; `--detach` returns a `run_id` immediately.
|
|
95
|
+
- **Survives a rate limit.** `cdx run --failover` notices that a run stopped because its account ran out, moves the task to the next account the ranking allows, and carries on — one `run_id` for the whole task, with every account it occupied listed in `cdx run-report`. A spent account is always confirmed against its own refreshed quota first, so a healthy account is never abandoned. From there two routes migrate a run: the provider said so in words cdx recognises, or — since that wording differs between an enterprise workspace out of credits, a personal plan hitting its 5-hour window, and Claude's own phrasing — simply that the account's quota is gone and the run failed. `cdx run-report` names which route applied. Exhausting every account reports `failover_exhausted`, which a caller can tell apart from the task itself failing.
|
|
96
|
+
- **Native background delegation (opt-in).** With `CDX_EXPERIMENTAL_NATIVE_BG=1`, `cdx run --detach` hands the run to Claude Code's own background agents (`--bg`) instead of cdx's detached launcher. The run keeps its cdx `run_id`, closes out with its token usage and final answer read from the provider's own session transcript, and the parked session is stopped once its task is done. Off by default for one release while it settles; the in-house launcher is unchanged and is what every run takes otherwise. Codex keeps the in-house path while its `cloud`/`exec-server` surfaces are marked experimental.
|
|
97
|
+
- **Observable runs.** `cdx run-status`, `cdx run-tail`, `cdx run-report`, and `cdx runs --since` follow a run while it happens and after it ends.
|
|
98
|
+
- **Selection without launching.** `cdx select --provider ... --require-ready --json` answers "which session should this job use?" for orchestrators.
|
|
99
|
+
- **Escape hatch for unmapped flags.** `cdx set <name> --extra-args "--add-dir ../shared"` passes provider arguments cdx does not model straight through, so a flag your provider shipped last week never forces you to abandon cdx. These arguments are explicitly unvalidated: cdx does not parse or verify them, `cdx schema --json` lists them under `unvalidated` rather than describing them, and `cdx doctor --check-provider-flags` ignores them. They are split into literal argv entries and never passed through a shell.
|
|
100
|
+
- **Validated contract.** `cdx schema --json` publishes the enums, mutually-exclusive argument groups, and error codes programmatic callers should validate against.
|
|
101
|
+
|
|
102
|
+
### Context handoff
|
|
103
|
+
|
|
104
|
+
- **Shared handoff context.** Keep a per-workspace Markdown context, or build one from a source session transcript, and install it into another assistant session before switching providers or accounts.
|
|
105
|
+
- **Session transcript capture.** Every launch is recorded to a local log file via `script`, giving you a full terminal transcript for each session.
|
|
106
|
+
|
|
107
|
+
### Maintenance
|
|
108
|
+
|
|
44
109
|
- **Launch history.** Inspect recent launches with provider, result, duration, working directory, launch settings, and transcript path.
|
|
110
|
+
- **Aggregate usage.** `cdx stats` totals runs, token usage, and time spent per session over any period.
|
|
111
|
+
|
|
112
|
+

|
|
113
|
+
|
|
45
114
|
- **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.
|
|
115
|
+
- **Health and repair.** `cdx doctor` inspects dependencies, permissions, and orphan profiles; `cdx repair` plans or applies safe fixes.
|
|
116
|
+
- **Portable bundles.** `cdx export` / `cdx import` move sessions between machines, with passphrase-encrypted auth data when you ask for it.
|
|
46
117
|
- **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 self-update`.
|
|
47
118
|
- **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.
|
|
48
|
-
- **Shared handoff context.** Keep a per-workspace Markdown context, or build one from a source session transcript, and install it into another assistant session before switching providers or accounts.
|
|
49
|
-
- **Passive status resolution.** Codex status is read from the local Codex app-server rate-limit API when available, with legacy transcript/history parsing kept as a fallback.
|
|
50
|
-
- **Banked reset visibility.** Eligible Codex accounts show the number of manually redeemable bonus resets in `cdx status` and expose reset details in JSON output.
|
|
51
|
-
- **Session transcript capture.** Every launch is recorded to a local log file via `script`, giving you a full terminal transcript for each session.
|
|
52
|
-
- **Clean removal.** `cdx rmv` wipes a session and its entire auth directory. No orphaned files, no stale credentials.
|
|
53
119
|
|
|
54
120
|
---
|
|
55
121
|
|
|
@@ -357,8 +423,8 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
357
423
|
| `cdx config <name> [--json]` | Show persistent launch settings for a session |
|
|
358
424
|
| `cdx configs [--json]` | Show persistent launch settings for all sessions in one table |
|
|
359
425
|
| `cdx power\|perm\|fast\|model <name\|all\|provider:PROVIDER\|a,b> <value\|default> [--json]` | Shortcut commands for setting or clearing one launch setting |
|
|
360
|
-
| `cdx set <name>\|--sessions all\|a,b\|--provider PROVIDER [--power minimal\|low\|medium\|high\|xhigh] [--permission review\|default\|auto\|full] [--fast on\|off] [--rtk on\|off] [--logics on\|off] [--model MODEL] [--priority 0..100] [--json]` | Persist launch settings for one or more sessions |
|
|
361
|
-
| `cdx unset <name>\|--sessions all\|a,b\|--provider PROVIDER (--power\|--permission\|--fast\|--rtk\|--logics\|--model\|--priority\|--all) [--json]` | Remove persisted launch settings and fall back to provider defaults |
|
|
426
|
+
| `cdx set <name>\|--sessions all\|a,b\|--provider PROVIDER [--power minimal\|low\|medium\|high\|xhigh] [--permission review\|default\|auto\|full] [--fast on\|off] [--rtk on\|off] [--logics on\|off] [--model MODEL] [--fallback-model MODEL[,MODEL...]] [--budget USD] [--extra-args 'ARGS'] [--priority 0..100] [--json]` | Persist launch settings for one or more sessions |
|
|
427
|
+
| `cdx unset <name>\|--sessions all\|a,b\|--provider PROVIDER (--power\|--permission\|--fast\|--rtk\|--logics\|--model\|--fallback-model\|--budget\|--extra-args\|--priority\|--all) [--json]` | Remove persisted launch settings and fall back to provider defaults |
|
|
362
428
|
| `cdx history [name] [--limit N] [--summary] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--json]` | Show recent launch history or aggregate total launch time per assistant, optionally filtered by period |
|
|
363
429
|
| `cdx last [--json]` | Launch the most recent existing session from launch history |
|
|
364
430
|
| `cdx resume <name> [--json]` | Resume the provider-native conversation for a session using the named command form |
|
|
@@ -384,7 +450,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
384
450
|
| `cdx notify --next-ready [--poll seconds] [--once] [--schedule] [--refresh] [--json]` | Wait until the recommended session is usable, or schedule the next known reset notification |
|
|
385
451
|
| `cdx next [--json] [--refresh]` | Select the best next assistant using the same priority logic as `cdx status` |
|
|
386
452
|
| `cdx select --provider PROVIDER [--min-reasoning-effort minimal\|low\|medium\|high\|xhigh] [--min-power minimal\|low\|medium\|high\|xhigh] [--require-ready] [--refresh] --json` | Select a suitable session for headless automation |
|
|
387
|
-
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT\|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--reasoning-effort minimal\|low\|medium\|high\|xhigh] [--power minimal\|low\|medium\|high\|xhigh] [--permission MODE] [--timeout-seconds N] [--detach] [--refresh] --json` | Run one headless task and return a stable JSON result; `--detach` returns the `run_id` at launch without waiting, `--prompt-file -` reads the prompt from stdin |
|
|
453
|
+
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT\|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--reasoning-effort minimal\|low\|medium\|high\|xhigh] [--power minimal\|low\|medium\|high\|xhigh] [--permission MODE] [--timeout-seconds N] [--detach] [--failover] [--refresh] --json` | Run one headless task and return a stable JSON result; `--detach` returns the `run_id` at launch without waiting, `--failover` continues the task on the next account when this one hits its rate limit, `--prompt-file -` reads the prompt from stdin |
|
|
388
454
|
| `cdx run-report <run_id> --json` | Full report, transcript metadata, and final payload for a run |
|
|
389
455
|
| `cdx run-status <run_id> --json` | Status of one run by id |
|
|
390
456
|
| `cdx run-tail <run_id> [--lines N] --json` | Last lines of a run's own output, while it is still running or after it finished |
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# CDX Manager 0.15.0
|
|
2
|
+
|
|
3
|
+
> Quota awareness stops being advice. A headless run that hits a rate limit now
|
|
4
|
+
> continues on an account that still has quota, and `cdx resume` reopens the
|
|
5
|
+
> conversation cdx named rather than whatever ran here last.
|
|
6
|
+
|
|
7
|
+
## Read this first
|
|
8
|
+
|
|
9
|
+
Two defaults change for existing sessions.
|
|
10
|
+
|
|
11
|
+
- **`cdx resume <name>` changes meaning.** It used to run `codex resume --last`
|
|
12
|
+
or `claude --continue`, both of which mean "the most recent conversation in
|
|
13
|
+
this directory". It now names the provider's own conversation id, so it
|
|
14
|
+
reopens the conversation *that session* last had, regardless of your working
|
|
15
|
+
directory or of what ran since. Sessions with no recorded id keep the old
|
|
16
|
+
behaviour; `cdx can-resume --json` tells you which applies.
|
|
17
|
+
- **Every Claude launch now carries `--session-id`.** cdx mints the id and
|
|
18
|
+
passes it, which is what makes the above possible.
|
|
19
|
+
|
|
20
|
+
## Highlights
|
|
21
|
+
|
|
22
|
+
- `cdx run --failover` continues a task on the next eligible account when the
|
|
23
|
+
one it started on runs out, keeping one `run_id` for the whole task.
|
|
24
|
+
- `cdx set --budget` caps what a headless run may spend; `--fallback-model`
|
|
25
|
+
lets it continue on another model when the first is unavailable.
|
|
26
|
+
- `cdx set --extra-args` passes provider flags cdx does not model straight
|
|
27
|
+
through, so a flag your provider shipped last week no longer forces you out
|
|
28
|
+
of cdx.
|
|
29
|
+
- Resume and handoff are anchored on a provider-native conversation id instead
|
|
30
|
+
of on recency.
|
|
31
|
+
- Opt-in delegation of `cdx run --detach` to Claude Code's own background
|
|
32
|
+
agents, behind `CDX_EXPERIMENTAL_NATIVE_BG=1`.
|
|
33
|
+
|
|
34
|
+
## Changes
|
|
35
|
+
|
|
36
|
+
### Failover across accounts
|
|
37
|
+
|
|
38
|
+
`cdx run --failover` detects that a run stopped because its account is spent,
|
|
39
|
+
re-ranks the remaining sessions through the same ranking `cdx next` uses,
|
|
40
|
+
carries the task across, and continues. The whole task keeps one `run_id`;
|
|
41
|
+
`cdx run-report` lists every session it occupied, in order, with the reason for
|
|
42
|
+
each transition, and `cdx runs` shows it once.
|
|
43
|
+
|
|
44
|
+
Migration requires the account's own refreshed rate-limit status to confirm it
|
|
45
|
+
is spent, because the two ways of being wrong are not symmetric: missing a rate
|
|
46
|
+
limit costs nothing new, while inventing one would move a healthy run off a
|
|
47
|
+
working account and spend a second account's quota redoing it.
|
|
48
|
+
|
|
49
|
+
Two routes trigger it. `provider_reported_exhaustion` when the provider says so
|
|
50
|
+
in words cdx recognises, and `account_status_exhausted` when the quota is gone
|
|
51
|
+
and the run failed, whatever the wording. The second exists because the wording
|
|
52
|
+
is not one thing — an enterprise workspace out of credits, a personal plan
|
|
53
|
+
hitting its 5-hour window, and Claude's own phrasing are different sentences,
|
|
54
|
+
and only the first has been observed.
|
|
55
|
+
|
|
56
|
+
Exhausting every eligible account reports `failover_exhausted`, and hitting the
|
|
57
|
+
transition bound reports `failover_limit_reached`; both are distinct from the
|
|
58
|
+
task itself failing. `--failover` and `--detach` are mutually exclusive:
|
|
59
|
+
failover has to watch the run to react.
|
|
60
|
+
|
|
61
|
+
### Spend ceiling and model fallback
|
|
62
|
+
|
|
63
|
+
`cdx set <name> --budget 5` and `--fallback-model sonnet,haiku` persist like
|
|
64
|
+
every other launch setting. Both are Claude Code flags the provider accepts
|
|
65
|
+
only alongside `--print`, so they apply to `cdx run` and to nothing else;
|
|
66
|
+
`cdx configs` says so rather than letting a launch fail. `--fallback-model`
|
|
67
|
+
takes a comma-separated list, tried in order.
|
|
68
|
+
|
|
69
|
+
### Resume by identity
|
|
70
|
+
|
|
71
|
+
Sessions record the provider's own conversation id and where it came from.
|
|
72
|
+
Claude accepts a caller-supplied id, so cdx mints it at launch — provenance
|
|
73
|
+
`imposed`. Codex mints its own, so cdx reads it back from the rollout after a
|
|
74
|
+
run — provenance `observed`, and legitimately absent until the session has run
|
|
75
|
+
once. `cdx can-resume --json` reports the strategy, the id, and its provenance,
|
|
76
|
+
so an automated caller can tell a named resume from a best-effort one.
|
|
77
|
+
|
|
78
|
+
### Passthrough for unmapped provider flags
|
|
79
|
+
|
|
80
|
+
`cdx set <name> --extra-args "--add-dir ../shared"` reaches the provider
|
|
81
|
+
command line unmodified, after the arguments cdx maps itself, so a passthrough
|
|
82
|
+
value overrides the setting it duplicates. These arguments are explicitly
|
|
83
|
+
unvalidated: `cdx schema --json` lists them under `unvalidated` rather than
|
|
84
|
+
describing them, and `cdx doctor --check-provider-flags` ignores them. They are
|
|
85
|
+
split into literal argv entries and never passed through a shell.
|
|
86
|
+
|
|
87
|
+
### Background delegation (opt-in)
|
|
88
|
+
|
|
89
|
+
With `CDX_EXPERIMENTAL_NATIVE_BG=1`, `cdx run --detach` hands the run to Claude
|
|
90
|
+
Code's background agents. The run keeps its cdx `run_id`, closes out with the
|
|
91
|
+
token usage and final answer read from the provider's own session transcript,
|
|
92
|
+
and the parked session is stopped once its task is done. Off by default for
|
|
93
|
+
this release: it works, but it has been exercised far less than the in-house
|
|
94
|
+
launcher every run takes today. Codex keeps the in-house path while its
|
|
95
|
+
`cloud` and `exec-server` surfaces are marked experimental.
|
|
96
|
+
|
|
97
|
+
### Onboarding and docs
|
|
98
|
+
|
|
99
|
+
`cdx` with no sessions registered now points at `cdx add` instead of listing
|
|
100
|
+
ten commands that all return nothing. The README leads with the quota routing
|
|
101
|
+
rather than with session management, and its screenshots are generated from a
|
|
102
|
+
synthetic home (`scripts/seed_demo_home.py`) so they carry no account names.
|
|
103
|
+
|
|
104
|
+
### Fixes
|
|
105
|
+
|
|
106
|
+
- A status merge adopted the candidate's timestamp even when it was older,
|
|
107
|
+
dating a record by its stalest contributor. Since `updated_at` drives the
|
|
108
|
+
freshness check, a just-resolved status read as expired and re-probed the
|
|
109
|
+
provider on every call.
|
|
110
|
+
|
|
111
|
+
## Validation
|
|
112
|
+
|
|
113
|
+
- `npm run lint`
|
|
114
|
+
- `npm test` — 629 tests.
|
|
115
|
+
- CI green on ubuntu-latest and windows-latest.
|
|
116
|
+
- Also run on a physical Windows machine: 623 passed, 5 skipped. One failure,
|
|
117
|
+
`test_concurrent_starts_do_not_lose_records`, reproduces identically on the
|
|
118
|
+
`v0.14.0` tag on that machine and passes in CI — a pre-existing contention
|
|
119
|
+
flake in the registry lock, not a regression.
|
|
120
|
+
- End-to-end across eleven live sessions and both providers: fourteen real
|
|
121
|
+
runs, usage extracted on every one, six Codex accounts each recording a
|
|
122
|
+
distinct conversation id and five Claude accounts recording theirs.
|
|
123
|
+
- End-to-end failover on a genuinely exhausted account: `work6` migrated to
|
|
124
|
+
`work1` and the task succeeded, recorded as one run.
|
|
125
|
+
- End-to-end resume by identity: a minted id accepted as `--session-id`, then
|
|
126
|
+
`--resume` on that id recalling the previous turn's answer.
|
|
127
|
+
- `--extra-args` and the budget/fallback settings exercised against real
|
|
128
|
+
provider CLIs, not only in tests.
|
|
129
|
+
- `logics-manager lint --require-status`
|
|
130
|
+
- `logics-manager audit --group-by-doc` — 0 blocking, 0 warnings.
|
|
131
|
+
|
|
132
|
+
## Known gaps
|
|
133
|
+
|
|
134
|
+
- The rate-limit wording of a personal-plan Codex account has not been
|
|
135
|
+
observed; only an enterprise workspace out of credits has. Claude's wording
|
|
136
|
+
was recovered from local transcripts after the release was cut - it reports
|
|
137
|
+
`type: rate_limit_error` under HTTP 429, which the matcher already covers.
|
|
138
|
+
Worth knowing: that same 429 is also how an org policy disabling a model is
|
|
139
|
+
reported, a case no failover can fix, and the status corroboration is what
|
|
140
|
+
stops a healthy account being abandoned for it. The
|
|
141
|
+
`account_status_exhausted` route covers whatever wording remains unseen, so
|
|
142
|
+
this costs the precise reason rather than the failover itself.
|
|
143
|
+
- Background delegation has been exercised on one account with one prompt
|
|
144
|
+
shape, which is why it is opt-in.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# CDX Manager 0.15.1
|
|
2
|
+
|
|
3
|
+
> Two fixes, both for failures that only appear under the conditions cdx is
|
|
4
|
+
> built for: an account whose credits ran out while its usage windows still
|
|
5
|
+
> read healthy, and twenty parallel runs writing the registry at once on
|
|
6
|
+
> Windows.
|
|
7
|
+
|
|
8
|
+
## Fixes
|
|
9
|
+
|
|
10
|
+
### Failover missed an exhausted account when its windows looked fine
|
|
11
|
+
|
|
12
|
+
`--failover` corroborates a rate-limited run against the account's own
|
|
13
|
+
refreshed status before migrating, so a healthy account is never abandoned on
|
|
14
|
+
the strength of a matched phrase. That check read only the 5-hour and weekly
|
|
15
|
+
percentages, which cannot see credit exhaustion: an account with 88% of its
|
|
16
|
+
5-hour window and 70% of its weekly window remaining, and no credits, was
|
|
17
|
+
reported as not spent. The run failed instead of moving, on an account that
|
|
18
|
+
could not have succeeded.
|
|
19
|
+
|
|
20
|
+
Codex publishes a typed reason for this — `rate_limit_reached_type`, null in
|
|
21
|
+
normal operation and set to a value such as `workspace_owner_credits_depleted`
|
|
22
|
+
when a limit is actually reached. cdx was already fetching that payload and
|
|
23
|
+
dropping the field; it survived only inside `raw_status_text`, unparsed. It is
|
|
24
|
+
now surfaced and treated as authoritative.
|
|
25
|
+
|
|
26
|
+
Any non-null value counts. The enum belongs to the provider, and carrying a
|
|
27
|
+
list of its members in cdx would reintroduce exactly the drift this removes —
|
|
28
|
+
a value nobody has seen yet would silently mean "not exhausted".
|
|
29
|
+
|
|
30
|
+
### Concurrent registry writes failed on Windows
|
|
31
|
+
|
|
32
|
+
`_registry_lock` used `msvcrt.locking(LK_LOCK)`, which does its own waiting —
|
|
33
|
+
ten attempts, one second apart — and then raises `EDEADLOCK`. POSIX
|
|
34
|
+
`fcntl.flock` blocks until the lock frees. Identical contention therefore
|
|
35
|
+
produced a wait on one supported platform and an `OSError` on the other.
|
|
36
|
+
|
|
37
|
+
Twenty parallel runs writing the run registry is not an edge case for this
|
|
38
|
+
tool; it is the load it exists to carry. Windows now takes the non-blocking
|
|
39
|
+
form and waits in a loop cdx controls, bounded by a deadline cdx chooses rather
|
|
40
|
+
than by `LK_LOCK`'s fixed budget. An exhausted wait raises a named error
|
|
41
|
+
instead of a raw `OSError`, so contention is distinguishable from a bug.
|
|
42
|
+
|
|
43
|
+
This predates 0.15.0 — it reproduces on the `v0.14.0` tag — and CI never showed
|
|
44
|
+
it, a hosted runner being less contended than a physical desktop. Which also
|
|
45
|
+
means CI could not be the evidence for the fix.
|
|
46
|
+
|
|
47
|
+
## Also in this release
|
|
48
|
+
|
|
49
|
+
- A test now fails if a flag is declared in a parser's schema and never used,
|
|
50
|
+
which is how `--failover` shipped in 0.15.0 parsing, validating and doing
|
|
51
|
+
nothing until an integration test caught it by chance.
|
|
52
|
+
- `scripts/measure_option_sites.py` records the measurement behind that
|
|
53
|
+
decision: of the nine declaration sites an option touches, six restate one
|
|
54
|
+
fact and three encode a real decision, so the answer was a check rather than
|
|
55
|
+
a consolidation.
|
|
56
|
+
|
|
57
|
+
## Validation
|
|
58
|
+
|
|
59
|
+
- `npm run lint`
|
|
60
|
+
- `npm test` — 639 tests.
|
|
61
|
+
- CI green on ubuntu-latest and windows-latest.
|
|
62
|
+
- On a physical Windows machine: 632 passed, 5 skipped, **no failures** —
|
|
63
|
+
including `test_concurrent_starts_do_not_lose_records`, which had failed
|
|
64
|
+
there on every run since before v0.14.0.
|
|
65
|
+
- The credit-exhaustion case verified against the real payload shape recovered
|
|
66
|
+
from local transcripts: with 88% and 70% of the windows remaining and a typed
|
|
67
|
+
reason present, corroboration now confirms where it previously did not.
|
|
68
|
+
|
|
69
|
+
## What closed from 0.15.0's known gaps
|
|
70
|
+
|
|
71
|
+
- **Claude's rate-limit wording** was unobserved and now is not. Recovered from
|
|
72
|
+
local session transcripts: `type: rate_limit_error` under HTTP 429, already
|
|
73
|
+
covered by the existing matcher. Worth knowing, and now pinned by tests: the
|
|
74
|
+
same 429 is how an org policy disabling a model is reported, a case no
|
|
75
|
+
failover can fix, and the status corroboration is what keeps a healthy
|
|
76
|
+
account from being abandoned for it.
|
|
77
|
+
|
|
78
|
+
## Still open
|
|
79
|
+
|
|
80
|
+
- The rate-limit wording of a personal-plan Codex account remains unobserved.
|
|
81
|
+
The `account_status_exhausted` route covers it, so this costs the precise
|
|
82
|
+
reason rather than the failover itself.
|
|
83
|
+
- Background delegation is still opt-in behind `CDX_EXPERIMENTAL_NATIVE_BG=1`.
|
|
84
|
+
It works and is verified end to end, but on one account with one prompt
|
|
85
|
+
shape — an exposure judgement, not a correctness one.
|
|
@@ -160,6 +160,22 @@
|
|
|
160
160
|
"v0.12.2": {
|
|
161
161
|
"github_tarball_sha256": "5214f9f85f6bdc2c53bded938c91e28411de3c573bbcff81ebecb20dea6d99c2",
|
|
162
162
|
"github_zip_sha256": "8f447cdea9e921f7428c1c72db5a19f40f447a877bb2bc8dc1c8bd5e34daf070"
|
|
163
|
+
},
|
|
164
|
+
"v0.12.3": {
|
|
165
|
+
"github_tarball_sha256": "b41cf8a99c36d3f03a3556db604316dcfdf14e2eb9090c86f25190b8a0db866a",
|
|
166
|
+
"github_zip_sha256": "6a8e384a88e736607dbc0b6e9953fb61192e77ded259fac2de35b03ad2fa71bd"
|
|
167
|
+
},
|
|
168
|
+
"v0.12.4": {
|
|
169
|
+
"github_tarball_sha256": "877e54c9f435acb41947c43c8618c84d88e0dc0c7cb77c5dd5dc56e13ee80b7f",
|
|
170
|
+
"github_zip_sha256": "7db7f28ab8b295446ba0a38e6e3cbbcc8855dd7333ebc9877917d14b4caaf903"
|
|
171
|
+
},
|
|
172
|
+
"v0.14.0": {
|
|
173
|
+
"github_tarball_sha256": "5da107556442d3566a7b0787708ae700e38afdf99e74209fffd2484ce3a33d94",
|
|
174
|
+
"github_zip_sha256": "bd4e070582bfc641e6176a8b8be8d7afd1ce9d76fc59c1dbeb238bae548bc699"
|
|
175
|
+
},
|
|
176
|
+
"v0.15.0": {
|
|
177
|
+
"github_tarball_sha256": "93d0a7305a5235f20e89a3e2a404029546926437952ee61d43a1f69c3ae98e4e",
|
|
178
|
+
"github_zip_sha256": "23e4e42fb746f4af5fa407f853e979ffce91e147dbb623c250e276fdcaa9638a"
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
181
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli_args.py
CHANGED
|
@@ -8,6 +8,7 @@ imported back there and used by the handlers.
|
|
|
8
8
|
from datetime import datetime, timedelta
|
|
9
9
|
|
|
10
10
|
from .config import (
|
|
11
|
+
MAX_LAUNCH_BUDGET_USD,
|
|
11
12
|
PERMISSION_ALIASES,
|
|
12
13
|
PERMISSION_INPUT_VALUES,
|
|
13
14
|
PERMISSION_VALUES,
|
|
@@ -30,8 +31,8 @@ CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set|append [text..
|
|
|
30
31
|
MEMORY_USAGE = "Usage: cdx memory [--global|--project NAME_OR_PATH] [show|view|path|init|edit|clear|set|append|list] [text...] [--json]"
|
|
31
32
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
32
33
|
LABEL_USAGE = "Usage: cdx label <name> <label> [--json] | cdx label <name> --clear [--json]"
|
|
33
|
-
SET_USAGE = "Usage: cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--priority 0..100] [--json]"
|
|
34
|
-
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--reasoning-effort|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]"
|
|
34
|
+
SET_USAGE = "Usage: cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--fallback-model MODEL[,MODEL...]] [--budget USD] [--extra-args 'ARGS'] [--priority 0..100] [--json]"
|
|
35
|
+
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--reasoning-effort|--permission|--fast|--rtk|--logics|--model|--fallback-model|--budget|--extra-args|--priority|--all) [--json]"
|
|
35
36
|
SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
|
|
36
37
|
CONFIG_USAGE = "Usage: cdx config <name> [--json]"
|
|
37
38
|
CONFIGS_USAGE = "Usage: cdx configs [--json]"
|
|
@@ -42,12 +43,13 @@ RESUME_USAGE = "Usage: cdx resume <name> [--json]"
|
|
|
42
43
|
CAN_RESUME_USAGE = "Usage: cdx can-resume <name> [--json]"
|
|
43
44
|
SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json"
|
|
44
45
|
NEXT_USAGE = "Usage: cdx next [--json] [--refresh]"
|
|
45
|
-
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--detach] [--refresh] --json"
|
|
46
|
+
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--detach] [--failover] [--refresh] --json"
|
|
46
47
|
RUN_JSON_REQUIRED = "cdx run: --json is required."
|
|
47
48
|
RUN_TARGET_REQUIRED = "cdx run: specify a session name or --provider PROVIDER."
|
|
48
49
|
RUN_SESSION_PROVIDER_CONFLICT = "cdx run: cannot specify both a session name and --provider."
|
|
49
50
|
RUN_CWD_REQUIRED = "cdx run: --cwd PATH is required."
|
|
50
51
|
RUN_PROMPT_SOURCE_REQUIRED = "cdx run: specify exactly one prompt source: --prompt TEXT or --prompt-file PATH."
|
|
52
|
+
RUN_DETACH_FAILOVER_CONFLICT = "cdx run: --failover needs to watch the run, which --detach gives up."
|
|
51
53
|
RUN_KIND_VALUES = ("assistant", "code-review")
|
|
52
54
|
# Aliases of the shared definitions in config, kept only so the existing
|
|
53
55
|
# RUN_* spellings in this module keep reading naturally. They are references,
|
|
@@ -228,6 +230,17 @@ def _parse_priority_value(value):
|
|
|
228
230
|
return priority
|
|
229
231
|
|
|
230
232
|
|
|
233
|
+
def _parse_budget_value(value):
|
|
234
|
+
try:
|
|
235
|
+
budget = float(value)
|
|
236
|
+
except (TypeError, ValueError) as error:
|
|
237
|
+
raise CdxError(SET_USAGE) from error
|
|
238
|
+
# NaN compares false against every bound, so reject it before the range check.
|
|
239
|
+
if budget != budget or budget <= 0 or budget > MAX_LAUNCH_BUDGET_USD:
|
|
240
|
+
raise CdxError(SET_USAGE)
|
|
241
|
+
return budget
|
|
242
|
+
|
|
243
|
+
|
|
231
244
|
def _parse_set_args(args):
|
|
232
245
|
parsed = _parse_flag_args(args, {
|
|
233
246
|
"--power": {"key": "power", "type": "str", "default": None},
|
|
@@ -236,6 +249,9 @@ def _parse_set_args(args):
|
|
|
236
249
|
"--rtk": {"key": "rtk", "type": "str", "default": None, "transform": _parse_fast_value},
|
|
237
250
|
"--logics": {"key": "logics", "type": "str", "default": None, "transform": _parse_fast_value},
|
|
238
251
|
"--model": {"key": "model", "type": "str", "default": None},
|
|
252
|
+
"--fallback-model": {"key": "fallback_model", "type": "str", "default": None},
|
|
253
|
+
"--budget": {"key": "budget", "type": "str", "default": None, "transform": _parse_budget_value},
|
|
254
|
+
"--extra-args": {"key": "extra_args", "type": "str", "default": None},
|
|
239
255
|
"--priority": {"key": "priority", "type": "str", "default": None, "transform": _parse_priority_value},
|
|
240
256
|
"--sessions": {"key": "sessions", "type": "str", "default": None, "transform": _parse_set_unset_sessions},
|
|
241
257
|
"--provider": {"key": "provider", "type": "str", "default": None, "transform": lambda value: _parse_provider_filter(value, SET_USAGE)},
|
|
@@ -253,7 +269,7 @@ def _parse_set_args(args):
|
|
|
253
269
|
raise CdxError(SET_USAGE)
|
|
254
270
|
settings = {
|
|
255
271
|
key: parsed[key]
|
|
256
|
-
for key in ("power", "permission", "fast", "rtk", "logics", "model", "priority")
|
|
272
|
+
for key in ("power", "permission", "fast", "rtk", "logics", "model", "fallback_model", "budget", "extra_args", "priority")
|
|
257
273
|
if parsed[key] is not None
|
|
258
274
|
}
|
|
259
275
|
if not settings:
|
|
@@ -276,6 +292,9 @@ def _parse_unset_args(args):
|
|
|
276
292
|
"--rtk": {"key": "rtk", "type": "bool", "default": False},
|
|
277
293
|
"--logics": {"key": "logics", "type": "bool", "default": False},
|
|
278
294
|
"--model": {"key": "model", "type": "bool", "default": False},
|
|
295
|
+
"--fallback-model": {"key": "fallback_model", "type": "bool", "default": False},
|
|
296
|
+
"--budget": {"key": "budget", "type": "bool", "default": False},
|
|
297
|
+
"--extra-args": {"key": "extra_args", "type": "bool", "default": False},
|
|
279
298
|
"--priority": {"key": "priority", "type": "bool", "default": False},
|
|
280
299
|
"--all": {"key": "all", "type": "bool", "default": False},
|
|
281
300
|
"--sessions": {"key": "sessions", "type": "str", "default": None, "transform": _parse_set_unset_sessions},
|
|
@@ -292,9 +311,11 @@ def _parse_unset_args(args):
|
|
|
292
311
|
raise CdxError(UNSET_USAGE)
|
|
293
312
|
if not parsed["names"] and not parsed["sessions"] and not parsed["provider"]:
|
|
294
313
|
raise CdxError(UNSET_USAGE)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
314
|
+
_UNSET_KEYS = (
|
|
315
|
+
"power", "reasoning_effort", "permission", "fast", "rtk", "logics",
|
|
316
|
+
"model", "fallback_model", "budget", "extra_args", "priority",
|
|
317
|
+
)
|
|
318
|
+
keys = list(_UNSET_KEYS) if parsed["all"] else [key for key in _UNSET_KEYS if parsed[key]]
|
|
298
319
|
if not keys:
|
|
299
320
|
raise CdxError(UNSET_USAGE)
|
|
300
321
|
return {
|
|
@@ -426,6 +447,16 @@ def cdx_schema():
|
|
|
426
447
|
"kind": {"accepted": list(RUN_KIND_VALUES)},
|
|
427
448
|
"provider": {"accepted": list(PROVIDERS)},
|
|
428
449
|
},
|
|
450
|
+
# Passthrough arguments are deliberately absent from this contract.
|
|
451
|
+
# `cdx set --extra-args` reaches the provider unchanged and cdx never
|
|
452
|
+
# validates it, so publishing it here would claim a guarantee that does
|
|
453
|
+
# not exist. A caller must treat it as opaque.
|
|
454
|
+
"unvalidated": {
|
|
455
|
+
"extra_args": {
|
|
456
|
+
"reason": "Passed to the provider CLI unchanged; cdx neither parses nor verifies these arguments.",
|
|
457
|
+
"not_checked_by": ["cdx schema", "cdx doctor --check-provider-flags"],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
429
460
|
"mutually_exclusive": [
|
|
430
461
|
{
|
|
431
462
|
"command": "run",
|
|
@@ -437,6 +468,11 @@ def cdx_schema():
|
|
|
437
468
|
"arguments": ["--prompt-file", "--prompt"],
|
|
438
469
|
"reason": "Exactly one prompt source.",
|
|
439
470
|
},
|
|
471
|
+
{
|
|
472
|
+
"command": "run",
|
|
473
|
+
"arguments": ["--detach", "--failover"],
|
|
474
|
+
"reason": "Failover has to watch the run to react to a rate limit; --detach stops watching.",
|
|
475
|
+
},
|
|
440
476
|
],
|
|
441
477
|
# Not mutually exclusive: both may be given, and cdx accepts them when
|
|
442
478
|
# they agree. A caller generating validation from `mutually_exclusive`
|
|
@@ -554,6 +590,7 @@ def _parse_run_args(args):
|
|
|
554
590
|
"--permission": {"key": "permission", "type": "str", "default": None, "transform": _normalize_run_permission},
|
|
555
591
|
"--timeout-seconds": {"key": "timeout_seconds", "type": "str", "default": None, "transform": _parse_run_timeout_seconds},
|
|
556
592
|
"--detach": {"key": "detach", "type": "bool", "default": False},
|
|
593
|
+
"--failover": {"key": "failover", "type": "bool", "default": False},
|
|
557
594
|
"--refresh": {"key": "refresh", "type": "bool", "default": False},
|
|
558
595
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
559
596
|
}, RUN_USAGE, positionals_key="names", max_positionals=1)
|
|
@@ -576,6 +613,12 @@ def _parse_run_args(args):
|
|
|
576
613
|
code=ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
577
614
|
arguments=["--prompt-file", "--prompt"],
|
|
578
615
|
)
|
|
616
|
+
if parsed["detach"] and parsed["failover"]:
|
|
617
|
+
raise CdxArgumentError(
|
|
618
|
+
RUN_DETACH_FAILOVER_CONFLICT,
|
|
619
|
+
code=ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
620
|
+
arguments=["--detach", "--failover"],
|
|
621
|
+
)
|
|
579
622
|
if not parsed["prompt_file"] and not parsed["prompt"]:
|
|
580
623
|
raise CdxArgumentError(
|
|
581
624
|
RUN_PROMPT_SOURCE_REQUIRED,
|
|
@@ -602,6 +645,7 @@ def _parse_run_args(args):
|
|
|
602
645
|
"permission": parsed["permission"],
|
|
603
646
|
"timeout_seconds": parsed["timeout_seconds"],
|
|
604
647
|
"detach": parsed["detach"],
|
|
648
|
+
"failover": parsed["failover"],
|
|
605
649
|
"refresh": parsed["refresh"],
|
|
606
650
|
"reasoning_effort": effort.get("reasoning_effort"),
|
|
607
651
|
"power": effort.get("power"),
|
package/src/cli_helpers.py
CHANGED
|
@@ -487,6 +487,9 @@ def _format_launch_config(session, use_color=False):
|
|
|
487
487
|
("rtk", "RTK"),
|
|
488
488
|
("logics", "Logics"),
|
|
489
489
|
("model", "Model"),
|
|
490
|
+
("fallback_model", "Fallback"),
|
|
491
|
+
("budget", "Budget"),
|
|
492
|
+
("extra_args", "Extra args"),
|
|
490
493
|
("priority", "Priority"),
|
|
491
494
|
]:
|
|
492
495
|
rows.append([
|
|
@@ -520,6 +523,10 @@ def _format_launch_setting_value(launch, key, use_color=False):
|
|
|
520
523
|
value = launch.get(key)
|
|
521
524
|
if value is None or value == "":
|
|
522
525
|
return _dim("default", use_color)
|
|
526
|
+
if key == "budget":
|
|
527
|
+
from .provider_runtime import _format_budget_arg
|
|
528
|
+
|
|
529
|
+
return _style(_format_budget_arg(value), "33", use_color)
|
|
523
530
|
if key == "priority":
|
|
524
531
|
return _style(str(value), "33", use_color)
|
|
525
532
|
if key == "power":
|
|
@@ -535,6 +542,12 @@ def _resume_capability_for_session(session, ctx):
|
|
|
535
542
|
"provider": session["provider"],
|
|
536
543
|
"resumable": bool(capability.get("resumable")),
|
|
537
544
|
"strategy": capability.get("strategy"),
|
|
545
|
+
# A caller deciding whether to trust a resume needs to tell a named
|
|
546
|
+
# conversation from "whatever ran here last", and where that name came
|
|
547
|
+
# from: Claude's is imposed at launch, Codex's is observed afterwards
|
|
548
|
+
# and is legitimately absent until the session has run once.
|
|
549
|
+
"identity": capability.get("identity"),
|
|
550
|
+
"provenance": capability.get("provenance"),
|
|
538
551
|
"reason": capability.get("reason"),
|
|
539
552
|
"command_preview": capability.get("command_preview") or [],
|
|
540
553
|
}
|
package/src/cli_render.py
CHANGED
|
@@ -120,8 +120,25 @@ def format_error(error, env=None, stderr=None):
|
|
|
120
120
|
return _style(str(error), "31", _should_use_color(env or os.environ, stderr or sys.stderr))
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
def _format_onboarding(use_color=False):
|
|
124
|
+
return "\n".join([
|
|
125
|
+
_style("No sessions yet.", "1", use_color),
|
|
126
|
+
"",
|
|
127
|
+
"A session is an isolated account profile for a provider CLI.",
|
|
128
|
+
"Register your first one, then launch it by name:",
|
|
129
|
+
"",
|
|
130
|
+
f" {_style('cdx add codex work', '36', use_color)} {_dim('register a Codex session named work', use_color)}",
|
|
131
|
+
f" {_style('cdx work', '36', use_color)} {_dim('log in if needed, then launch it', use_color)}",
|
|
132
|
+
"",
|
|
133
|
+
f"{_dim('Providers: codex, claude, antigravity, ollama (ollama needs --model).', use_color)}",
|
|
134
|
+
f"{_dim('Run cdx help for the full command list.', use_color)}",
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
|
|
123
138
|
def _format_sessions(service, use_color=False):
|
|
124
139
|
rows = service["format_list_rows"]()
|
|
140
|
+
if not rows:
|
|
141
|
+
return _format_onboarding(use_color=use_color)
|
|
125
142
|
has_provider = any(r.get("provider") for r in rows)
|
|
126
143
|
has_label = any(r.get("label") for r in rows)
|
|
127
144
|
has_launch = any(r.get("launch") for r in rows)
|