cdx-manager 0.4.2 → 0.4.4
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 +10 -2
- package/changelogs/CHANGELOGS_0_1_1.md +16 -16
- package/changelogs/CHANGELOGS_0_2_0.md +11 -10
- package/changelogs/CHANGELOGS_0_2_1.md +7 -6
- package/changelogs/CHANGELOGS_0_3_0.md +7 -6
- package/changelogs/CHANGELOGS_0_3_1.md +7 -6
- package/changelogs/CHANGELOGS_0_3_2.md +6 -5
- package/changelogs/CHANGELOGS_0_3_3.md +7 -6
- package/changelogs/CHANGELOGS_0_3_4.md +7 -6
- package/changelogs/CHANGELOGS_0_4_0.md +8 -7
- package/changelogs/CHANGELOGS_0_4_1.md +7 -6
- package/changelogs/CHANGELOGS_0_4_2.md +8 -7
- package/changelogs/CHANGELOGS_0_4_3.md +36 -0
- package/changelogs/CHANGELOGS_0_4_4.md +33 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +8 -2
- package/src/cli_commands.py +127 -0
- package/src/session_service.py +13 -0
- package/src/status_source.py +101 -3
- package/src/update_check.py +13 -3
- package/src/update_manager.py +208 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
-
[](LICENSE) ](LICENSE)  
|
|
4
4
|
|
|
5
5
|
**Run multiple Codex and Claude sessions from one terminal. Switch between accounts instantly.**
|
|
6
6
|
|
|
@@ -122,7 +122,7 @@ For a specific version:
|
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
|
|
125
|
-
CDX_VERSION=v0.4.
|
|
125
|
+
CDX_VERSION=v0.4.4 sh install.sh
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
From source:
|
|
@@ -143,6 +143,12 @@ npm install -g .
|
|
|
143
143
|
|
|
144
144
|
`cdx` is now available globally. Changes to the source take effect immediately — no reinstall needed.
|
|
145
145
|
|
|
146
|
+
To update an installed copy later:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
cdx update
|
|
150
|
+
```
|
|
151
|
+
|
|
146
152
|
To uninstall:
|
|
147
153
|
|
|
148
154
|
```bash
|
|
@@ -242,6 +248,7 @@ cdx status
|
|
|
242
248
|
| `cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
|
|
243
249
|
| `cdx doctor [--json]` | Inspect CLI dependencies, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines |
|
|
244
250
|
| `cdx repair [--dry-run] [--force] [--json]` | Plan or apply safe repairs for missing state files, quarantines, and orphan profiles |
|
|
251
|
+
| `cdx update [--check] [--yes] [--json] [--version TAG]` | Update cdx-manager using the installer that matches how it was installed |
|
|
245
252
|
| `cdx notify <name> --at-reset [--poll seconds] [--once] [--json]` | Wait for a session reset time and send a desktop notification when due |
|
|
246
253
|
| `cdx notify --next-ready [--poll seconds] [--once] [--json]` | Wait until the recommended session is usable or needs a refresh after reset |
|
|
247
254
|
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions; JSON returns a versioned payload with structured warnings |
|
|
@@ -272,6 +279,7 @@ Commands with machine-readable output:
|
|
|
272
279
|
- `cdx logout ... --json`
|
|
273
280
|
- `cdx doctor --json`
|
|
274
281
|
- `cdx repair --json`
|
|
282
|
+
- `cdx update --json`
|
|
275
283
|
- `cdx notify ... --json`
|
|
276
284
|
|
|
277
285
|
Success payloads follow a shared envelope:
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.1.0 -> 0.1.1`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.1.1 is the first packaged release of the multi-provider terminal session manager for Codex and Claude accounts.
|
|
7
|
+
- It turns the initial Logics-scoped idea into a usable CLI: isolated per-session auth homes, interactive provider launch, status extraction from local artifacts, Claude usage refresh through Anthropic rate-limit headers, log capture, cleanup commands, and release-ready package metadata.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Generated Commit Summary
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
### At a glance
|
|
11
|
+
## At a glance
|
|
12
12
|
|
|
13
13
|
- Added the `cdx` CLI entrypoint with conventional `--help`, `--version`, session listing, creation, launch, copy, removal, login, logout, clean, and status commands
|
|
14
14
|
- Added persistent session storage under `CDX_HOME`, with URL-encoded session names and per-session rehydration state
|
|
@@ -22,7 +22,7 @@ It turns the initial Logics-scoped idea into a usable CLI: isolated per-session
|
|
|
22
22
|
- Migrated the implementation to Python while keeping npm-based install and validation scripts
|
|
23
23
|
- Added root packaging metadata, MIT license, README, contribution guidance, and release notes
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
## Session management
|
|
26
26
|
|
|
27
27
|
- `cdx add <name>` creates Codex sessions by default.
|
|
28
28
|
- `cdx add claude <name>` creates Claude sessions with a dedicated `claude-home`.
|
|
@@ -31,7 +31,7 @@ It turns the initial Logics-scoped idea into a usable CLI: isolated per-session
|
|
|
31
31
|
- `cdx rmv <name> [--force]` removes the session registry entry and its auth directory.
|
|
32
32
|
- Session state is stored in a versioned JSON registry and per-session state files under `CDX_HOME`.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
## Authentication and launch flow
|
|
35
35
|
|
|
36
36
|
- Codex sessions use `codex login status`, `codex login`, and `codex logout`.
|
|
37
37
|
- Claude sessions use `claude auth status`, `claude auth login`, and `claude auth logout`.
|
|
@@ -39,7 +39,7 @@ It turns the initial Logics-scoped idea into a usable CLI: isolated per-session
|
|
|
39
39
|
- Signals from the wrapper process are forwarded to the child provider process.
|
|
40
40
|
- Codex launches show a reminder to run `/status` inside Codex when usage data needs refreshing.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
## Usage and status reporting
|
|
43
43
|
|
|
44
44
|
- `cdx status` renders an aligned table for all sessions.
|
|
45
45
|
- `cdx status <name>` renders a detailed view for one session.
|
|
@@ -48,7 +48,7 @@ It turns the initial Logics-scoped idea into a usable CLI: isolated per-session
|
|
|
48
48
|
- Available percentage is computed from the stricter remaining quota window.
|
|
49
49
|
- Cached statuses are enriched from newer or more detailed local artifacts when available.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
## Codex status extraction
|
|
52
52
|
|
|
53
53
|
- Parses Codex `/status` blocks from logs and JSONL history.
|
|
54
54
|
- Handles ANSI / terminal control sequences and narrow terminal layouts.
|
|
@@ -56,7 +56,7 @@ It turns the initial Logics-scoped idea into a usable CLI: isolated per-session
|
|
|
56
56
|
- Uses account identity in Codex status blocks to avoid accepting pasted status output from another account.
|
|
57
57
|
- Prefers direct launch logs over noisier conversational rollout JSONL artifacts.
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
## Claude status extraction
|
|
60
60
|
|
|
61
61
|
- Parses `Current session` and `Current week` blocks from Claude transcripts.
|
|
62
62
|
- Extracts 5-hour reset values from `Current session`, including AM/PM formats such as `Resets at 5:00 AM`.
|
|
@@ -64,26 +64,26 @@ It turns the initial Logics-scoped idea into a usable CLI: isolated per-session
|
|
|
64
64
|
- Keeps Claude 5-hour and weekly reset values separate in status output.
|
|
65
65
|
- Refreshes Claude usage automatically from Anthropic rate-limit headers when credentials are available.
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
## Local time behavior
|
|
68
68
|
|
|
69
69
|
- Claude API reset timestamps are formatted in the machine's local timezone.
|
|
70
70
|
- Log-derived time-only reset values are inferred in the local timezone.
|
|
71
71
|
- Status `updated_at` values from API, JSONL, and file metadata are normalized to local ISO timestamps.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
## Logging and cleanup
|
|
74
74
|
|
|
75
75
|
- Provider launches are captured to local transcript logs via `script`.
|
|
76
76
|
- Launch logs include unique timestamped filenames to avoid overwriting active or recent transcripts.
|
|
77
77
|
- Logs are rotated around the 10 MB threshold.
|
|
78
78
|
- `cdx clean [name]` clears launch transcript logs for one session or all sessions.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
## Packaging
|
|
81
81
|
|
|
82
82
|
- Published package version is `0.1.1`.
|
|
83
83
|
- The npm tarball is restricted to the CLI entrypoint, source package, README, license, and release changelogs.
|
|
84
84
|
- Local project-only files such as `.claude`, Logics workflow docs, and tests are excluded from the package tarball.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
## Validation and Regression Evidence
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
npm run lint
|
|
@@ -92,7 +92,7 @@ python3 bin/cdx --version
|
|
|
92
92
|
npm --cache /tmp/cdx-npm-cache pack --dry-run
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
## Notes
|
|
96
96
|
|
|
97
97
|
- This is the first release changelog, so it summarizes the full history from the initial Logics bootstrap through the 0.1.1 release preparation.
|
|
98
98
|
- The package remains marked `private` in `package.json`; publication requires intentionally changing that release policy.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.1.1 -> 0.2.0`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.2.0 focuses on operational reliability. It adds health checks, safe repair workflows, reset notifications, stronger session-store behavior, and clearer status output for choosing the best account.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## At a glance
|
|
10
11
|
|
|
11
12
|
- Added `cdx doctor` to inspect local CLI dependencies, `CDX_HOME` writability, missing session state, orphan profiles, and pending quarantines.
|
|
12
13
|
- Added `cdx repair` with dry-run-by-default behavior and `--force` for safe repairs.
|
|
@@ -18,7 +19,7 @@ CDX Manager 0.2.0 focuses on operational reliability. It adds health checks, saf
|
|
|
18
19
|
- Decorated CLI output with terminal-native color support while respecting `NO_COLOR`, `CLICOLOR`, and TTY behavior.
|
|
19
20
|
- Split the CLI implementation into smaller modules for status rendering, provider runtime, commands, and storage.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
## Doctor and repair
|
|
22
23
|
|
|
23
24
|
- `cdx doctor [--json]` reports installation and data-layout health.
|
|
24
25
|
- `cdx repair [--dry-run] [--force] [--json]` plans repairs by default.
|
|
@@ -26,14 +27,14 @@ CDX Manager 0.2.0 focuses on operational reliability. It adds health checks, saf
|
|
|
26
27
|
- Pending quarantine profile directories can be cleaned up.
|
|
27
28
|
- Orphan profiles are moved to quarantine with `--force` instead of being deleted directly.
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
## Notifications
|
|
30
31
|
|
|
31
32
|
- `cdx notify <name> --at-reset` waits for the selected session reset time.
|
|
32
33
|
- `cdx notify --next-ready` waits for the recommended session to become usable or due for refresh.
|
|
33
34
|
- `--poll seconds`, `--once`, and `--json` are supported.
|
|
34
35
|
- macOS desktop notifications are sent through `osascript` when available, with terminal output as the consistent fallback.
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
## Status improvements
|
|
37
38
|
|
|
38
39
|
- `cdx status` now includes a direct priority line before the usage tip.
|
|
39
40
|
- The priority line prefers immediately usable accounts, accounts with earlier relevant resets, and accounts without credit fallback when appropriate.
|
|
@@ -41,7 +42,7 @@ CDX Manager 0.2.0 focuses on operational reliability. It adds health checks, saf
|
|
|
41
42
|
- The main `cdx` session list now formats updated timestamps as relative ages.
|
|
42
43
|
- JSON status output keeps a stable shape even when live Claude refresh fails; refresh warnings are written to stderr.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
## Reliability and safety
|
|
45
46
|
|
|
46
47
|
- Session names now reject reserved command names including `doctor`, `repair`, and `notify`.
|
|
47
48
|
- Session removal uses a quarantine flow and surfaces cleanup failures instead of silently ignoring them.
|
|
@@ -51,7 +52,7 @@ CDX Manager 0.2.0 focuses on operational reliability. It adds health checks, saf
|
|
|
51
52
|
- Codex launch falls back without transcript capture when the `script` wrapper is unavailable or fails before producing a transcript.
|
|
52
53
|
- Signal interruptions no longer trigger transcript fallback relaunches.
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
## Validation and Regression Evidence
|
|
55
56
|
|
|
56
57
|
```bash
|
|
57
58
|
npm run lint
|
|
@@ -62,7 +63,7 @@ npm test
|
|
|
62
63
|
./bin/cdx --version
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
## Notes
|
|
66
67
|
|
|
67
68
|
- The package remains marked `private` in `package.json`; npm publication still requires an explicit policy change.
|
|
68
69
|
- No Git remote is configured in the current local repository, so this release is prepared locally and tagged locally only.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.2.0 -> 0.2.1`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.2.1 prepares the package for npm publication.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Packaging
|
|
10
11
|
|
|
11
12
|
- Removed the private package flag so npm publication is possible.
|
|
12
13
|
- Added npm package metadata: license, author, repository, bugs, homepage, keywords, and Node engine declaration.
|
|
@@ -14,7 +15,7 @@ CDX Manager 0.2.1 prepares the package for npm publication.
|
|
|
14
15
|
- Normalized the `bin.cdx` path with `npm pkg fix`.
|
|
15
16
|
- Documented the future npm install command in the README.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Validation and Regression Evidence
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
21
|
npm run lint
|
|
@@ -23,7 +24,7 @@ npm --cache /tmp/cdx-npm-cache publish --dry-run
|
|
|
23
24
|
npm view cdx-manager name version --registry https://registry.npmjs.org/
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
## Notes
|
|
27
28
|
|
|
28
29
|
- `npm view cdx-manager` currently returns `E404`, so the package name appears available on the npm registry.
|
|
29
30
|
- This release is prepared for npm publication but has not been published yet.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.2.1 -> 0.3.0`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.3.0 adds Python-native and standalone installation paths in addition to npm.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Packaging
|
|
10
11
|
|
|
11
12
|
- Added `pyproject.toml` so the CLI can be installed with `pipx`, `pip`, or `uv tool`.
|
|
12
13
|
- Added the Python console entrypoint `cdx = "src.cli:cli_entry"`.
|
|
@@ -15,7 +16,7 @@ CDX Manager 0.3.0 adds Python-native and standalone installation paths in additi
|
|
|
15
16
|
- Documented npm, pipx, uv, curl installer, and source installation paths.
|
|
16
17
|
- Added GitHub Actions automation for PyPI publication when a GitHub Release is published.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
## Validation and Regression Evidence
|
|
19
20
|
|
|
20
21
|
```bash
|
|
21
22
|
npm run lint
|
|
@@ -26,7 +27,7 @@ python3 -m venv /tmp/cdx-pyinstall
|
|
|
26
27
|
npm --cache /tmp/cdx-npm-cache publish --dry-run
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
## Notes
|
|
30
31
|
|
|
31
32
|
- PyPI publication is now technically possible, but still requires PyPI credentials and an explicit publish step.
|
|
32
33
|
- The standalone installer defaults to the latest GitHub Release and supports `CDX_VERSION=vX.Y.Z` for pinned installs.
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.3.0 -> 0.3.1`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.3.1 is a release-channel synchronization update.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Packaging
|
|
10
11
|
|
|
11
12
|
- Uses npm Trusted Publishing through GitHub Actions OIDC instead of long-lived npm tokens.
|
|
12
13
|
- Keeps npm, PyPI, GitHub Releases, pipx, uv, and the standalone installer aligned on the same release version.
|
|
13
14
|
- Retains the Python-native packaging and standalone install support introduced in 0.3.0.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## Validation and Regression Evidence
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
19
|
npm run lint
|
|
@@ -22,6 +23,6 @@ python -m build
|
|
|
22
23
|
python -m twine check dist/*
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
## Notes
|
|
26
27
|
|
|
27
28
|
- This release exists because the npm Trusted Publishing workflow was added after the `v0.3.0` tag. A fresh release is required for GitHub Actions to run the updated workflow definition for npm publishing.
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.3.1 -> 0.3.2`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.3.2 updates the npm release workflow to use the npm CLI version required for Trusted Publishing.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Packaging
|
|
10
11
|
|
|
11
12
|
- Runs the npm publish workflow on Node 24.
|
|
12
13
|
- Installs npm 11 before publishing so GitHub Actions OIDC trusted publishing is supported.
|
|
13
14
|
- Keeps npm, PyPI, GitHub Releases, pipx, uv, and the standalone installer aligned on the same release version.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## Validation and Regression Evidence
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
19
|
npm run lint
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.3.2 -> 0.3.3`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.3.3 adds Windows compatibility across the full codebase.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Windows support
|
|
10
11
|
|
|
11
12
|
- **Session store locking**: replaced `fcntl.flock` (Unix-only) with `msvcrt.locking` on Windows, with `seek(0)` to ensure consistent byte-range locking.
|
|
12
13
|
- **Signal handling**: guarded `signal.SIGHUP` references behind `hasattr` checks — `SIGHUP` does not exist on Windows.
|
|
@@ -15,11 +16,11 @@ CDX Manager 0.3.3 adds Windows compatibility across the full codebase.
|
|
|
15
16
|
- **ANSI colors**: `cli_entry` enables VT processing via `ctypes.windll.kernel32.SetConsoleMode` on Windows so color output works in terminals that support it.
|
|
16
17
|
- **Console encoding**: `cli_entry` reconfigures `stdout`/`stderr` to UTF-8 on Windows to prevent `UnicodeEncodeError` on non-ASCII session names.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
## Maintenance
|
|
19
20
|
|
|
20
21
|
- Expanded `.gitignore` with standard Python build artifacts (`__pycache__/`, `*.egg-info/`, `dist/`, `build/`), virtual environments, coverage output, and OS-specific files (`.DS_Store`, `Thumbs.db`, `desktop.ini`).
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
## Validation and Regression Evidence
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
26
|
npm run lint
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.3.3 -> 0.3.4`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.3.4 makes the CLI consumable by other applications through a structured JSON contract and rounds out the Windows release surface.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## JSON CLI API
|
|
10
11
|
|
|
11
12
|
- Added `cdx --json` to list known sessions as a machine-readable payload.
|
|
12
13
|
- Added `--json` support for session-management commands: `add`, `cp`, `ren`, `rmv`, `clean`, `login`, and `logout`.
|
|
@@ -14,14 +15,14 @@ CDX Manager 0.3.4 makes the CLI consumable by other applications through a struc
|
|
|
14
15
|
- Added a shared stderr error envelope for JSON mode with machine-readable `code`, `message`, and `exit_code`.
|
|
15
16
|
- Documented the JSON contract in the README so editor plugins and desktop apps can integrate without scraping human-readable terminal output.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Windows release hardening
|
|
18
19
|
|
|
19
20
|
- Added a native `install.ps1` installer for Windows.
|
|
20
21
|
- Documented supported Windows install paths and the optional transcript-capture fallback.
|
|
21
22
|
- Added targeted `win32` unit coverage for CLI startup, provider environment isolation, notifications, and session-store locking.
|
|
22
23
|
- Added a Windows CI smoke flow that installs the package and exercises core CLI commands with shimmed providers.
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
## Validation and Regression Evidence
|
|
25
26
|
|
|
26
27
|
```bash
|
|
27
28
|
npm run lint
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.3.4 -> 0.4.0`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-16
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.4.0 adds portable session backup and restore, surfaces cached release-update notices inside the CLI, and tightens Codex status isolation across multiple accounts.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Portable session bundles
|
|
10
11
|
|
|
11
12
|
- Added `cdx export <file>` and `cdx import <file>` for moving sessions between machines.
|
|
12
13
|
- Added optional encrypted auth export with `--include-auth` and interactive or environment-driven passphrase handling.
|
|
@@ -14,20 +15,20 @@ CDX Manager 0.4.0 adds portable session backup and restore, surfaces cached rele
|
|
|
14
15
|
- Preserved per-session state alongside session records so imported environments keep their local metadata.
|
|
15
16
|
- Added bundle schema validation and integrity checks during import.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Update awareness and installer hardening
|
|
18
19
|
|
|
19
20
|
- Added cached GitHub release checks so the CLI can warn when a newer `cdx-manager` release is available without hitting the network on every command.
|
|
20
21
|
- Surfaced update notices in interactive output and structured JSON warnings.
|
|
21
22
|
- Hardened the standalone install scripts to consume official release-archive checksums when available.
|
|
22
23
|
- Documented the checksum-backed installer flow and backup/restore usage in the README.
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
## Status isolation fix
|
|
25
26
|
|
|
26
27
|
- Fixed Codex status parsing so boxed blank lines in TUI transcripts no longer drop the `Account:` context line.
|
|
27
28
|
- Restored account-aware status selection when multiple sessions contain similar `/status` blocks.
|
|
28
29
|
- Added regression coverage for mixed-account transcript selection and bundle export/import flows.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
## Validation and Regression Evidence
|
|
31
32
|
|
|
32
33
|
```bash
|
|
33
34
|
npm run lint
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.4.0 -> 0.4.1`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-19
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.4.1 fixes the Windows npm entry point so `cdx` no longer depends on `python3.exe` being present on PATH. It now ships a Node launcher that resolves a usable Python 3 interpreter cross-platform before invoking the existing Python CLI entry point.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Windows npm launcher
|
|
10
11
|
|
|
11
12
|
- Replaced the npm-facing `bin.cdx` target with a Node launcher at `bin/cdx.js`.
|
|
12
13
|
- Added Python discovery that tries `py -3`, then `python`, then `python3` on Windows.
|
|
13
14
|
- Kept the existing Python script under `bin/cdx` as the primary CLI entry point.
|
|
14
15
|
- Added a clear error message when no compatible Python 3 interpreter is available.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
## Documentation and packaging
|
|
17
18
|
|
|
18
19
|
- Updated the README with Windows Python prerequisites and the new launcher behavior.
|
|
19
20
|
- Added a shared portable Node wrapper for the npm test and lint scripts.
|
|
20
21
|
- Bumped the package versions for the npm and PyPI release workflows.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
## Validation and Regression Evidence
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
26
|
npm run lint
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog (`0.4.1 -> 0.4.2`)
|
|
2
2
|
|
|
3
3
|
Release date: 2026-04-19
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Major Highlights
|
|
6
|
+
- CDX Manager 0.4.2 fixes Codex session bootstrap on Windows so `cdx add` can reuse an existing logged-in Codex CLI without requiring a second manual login flow. It also hardens executable resolution for the Codex probe so Windows shell shims and direct process spawning behave consistently.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Generated Commit Summary
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Codex auth bootstrap
|
|
10
11
|
|
|
11
12
|
- Seeded new Codex session auth homes from the global `~/.codex/auth.json` when available.
|
|
12
13
|
- Short-circuited the Codex auth probe when a session already has an auth file in its isolated home.
|
|
13
14
|
- Kept the per-session auth directory model intact after bootstrap so session isolation still applies.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## Windows command resolution
|
|
16
17
|
|
|
17
18
|
- Resolved `codex` through the active `PATH` before invoking the login-status probe.
|
|
18
19
|
- Applied the same command-resolution path to interactive provider launches when the default process spawner is used.
|
|
19
20
|
- Added regression coverage for Codex auth bootstrap and resolved-command spawning on Windows.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
## Documentation
|
|
22
23
|
|
|
23
24
|
- Documented the Codex auth bootstrap behavior in the README.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
## Validation and Regression Evidence
|
|
26
27
|
|
|
27
28
|
```bash
|
|
28
29
|
npm run lint
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog (`0.4.2 -> 0.4.3`)
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-19
|
|
4
|
+
|
|
5
|
+
## Major Highlights
|
|
6
|
+
|
|
7
|
+
- Generated from 1 code commit between `v0.4.2` and `HEAD` on 2026-04-19.
|
|
8
|
+
- Touched areas: Windows Codex status discovery, status formatting, and regression coverage.
|
|
9
|
+
- `cdx status` now reads Codex's structured Windows `rate_limits` payloads instead of falling back to `n/a`.
|
|
10
|
+
- Reset dates now render in the same short format as the rest of the UI, so the table stays readable.
|
|
11
|
+
- When a session profile has no usable Codex status artifact, `cdx` can fall back to the global Codex home on Windows.
|
|
12
|
+
|
|
13
|
+
## Generated Commit Summary
|
|
14
|
+
|
|
15
|
+
## Codex Status on Windows
|
|
16
|
+
|
|
17
|
+
- Added support for structured `rate_limits` data emitted by Codex's JSONL session history.
|
|
18
|
+
- Kept support for the older transcript-style status blocks, so existing artifacts still resolve.
|
|
19
|
+
- Narrowed the global-home fallback to real `cdx` runs on the default home, which avoids cross-contaminating temporary test homes.
|
|
20
|
+
|
|
21
|
+
## Status Formatting
|
|
22
|
+
|
|
23
|
+
- Normalized structured reset timestamps to the short `Apr 25 18:52` style used elsewhere in `cdx status`.
|
|
24
|
+
- Preserved the existing relative labels such as `in 3h 57m` in the rendered table.
|
|
25
|
+
|
|
26
|
+
## Validation and Regression Coverage
|
|
27
|
+
|
|
28
|
+
- Added a regression test for structured Codex rollout rate limits.
|
|
29
|
+
- Kept the existing session-service and CLI validation suite green.
|
|
30
|
+
|
|
31
|
+
## Validation and Regression Evidence
|
|
32
|
+
|
|
33
|
+
- `python -m unittest discover -s test -p "test_session_service_py.py"`
|
|
34
|
+
- `python -m unittest discover -s test -p "test_cli_py.py"`
|
|
35
|
+
- `python -m unittest discover -s test -p "test_runtime_py.py"`
|
|
36
|
+
- `node bin/cdx.js status`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog (`0.4.3 -> 0.4.4`)
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-20
|
|
4
|
+
|
|
5
|
+
## Major Highlights
|
|
6
|
+
|
|
7
|
+
- Generated from the release work on `cdx update`, the first built-in self-update path for `cdx-manager`.
|
|
8
|
+
- Added a version-aware update command that can check for a newer release, confirm interactively, and delegate to the right installer for the current installation type.
|
|
9
|
+
- Kept the existing update warning behavior intact so the CLI still surfaces newer releases on startup.
|
|
10
|
+
- Reserved `update` as a session name to avoid collisions with the new command.
|
|
11
|
+
|
|
12
|
+
## `cdx update`
|
|
13
|
+
|
|
14
|
+
- Added `cdx update --check` for a quick release check without applying changes.
|
|
15
|
+
- Added `cdx update --yes` for non-interactive environments.
|
|
16
|
+
- Added `cdx update --version TAG` so maintainers can target a specific release.
|
|
17
|
+
- Routed standalone installs through `install.sh` / `install.ps1`.
|
|
18
|
+
- Routed npm installs through `npm install -g cdx-manager@...`.
|
|
19
|
+
- Routed Python environment installs through `python -m pip install --upgrade ...`.
|
|
20
|
+
- Routed source checkouts through `git pull --ff-only` or an explicit tag checkout when a version is requested.
|
|
21
|
+
- Refused source updates when the checkout contains uncommitted changes.
|
|
22
|
+
|
|
23
|
+
## Validation and Regression Coverage
|
|
24
|
+
|
|
25
|
+
- Added CLI coverage for update checks, update execution, and version-aware help text.
|
|
26
|
+
- Added session-service coverage for the new reserved command name.
|
|
27
|
+
- Added unit coverage for installation detection and source-checkout safety in the update planner.
|
|
28
|
+
- Kept the existing CLI and session-service test suites green.
|
|
29
|
+
|
|
30
|
+
## Validation and Regression Evidence
|
|
31
|
+
|
|
32
|
+
- `python3 -m unittest test.test_cli_py test.test_session_service_py test.test_update_manager_py`
|
|
33
|
+
- `npm run lint`
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -21,6 +21,7 @@ from .cli_commands import (
|
|
|
21
21
|
handle_repair,
|
|
22
22
|
handle_rename,
|
|
23
23
|
handle_status,
|
|
24
|
+
handle_update,
|
|
24
25
|
)
|
|
25
26
|
from .cli_render import (
|
|
26
27
|
_format_sessions,
|
|
@@ -44,7 +45,7 @@ from .status_view import (
|
|
|
44
45
|
)
|
|
45
46
|
from .update_check import check_for_update
|
|
46
47
|
|
|
47
|
-
VERSION = "0.4.
|
|
48
|
+
VERSION = "0.4.4"
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
# ---------------------------------------------------------------------------
|
|
@@ -72,6 +73,7 @@ def _print_help(use_color=False):
|
|
|
72
73
|
f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]', '36', use_color)}",
|
|
73
74
|
f" {_style('cdx doctor [--json]', '36', use_color)}",
|
|
74
75
|
f" {_style('cdx repair [--dry-run] [--force] [--json]', '36', use_color)}",
|
|
76
|
+
f" {_style('cdx update [--check] [--yes] [--json] [--version TAG]', '36', use_color)}",
|
|
75
77
|
f" {_style('cdx notify <name> --at-reset [--json]', '36', use_color)}",
|
|
76
78
|
f" {_style('cdx notify --next-ready [--json]', '36', use_color)}",
|
|
77
79
|
f" {_style('cdx <name> [--json]', '36', use_color)}",
|
|
@@ -205,8 +207,9 @@ def main(argv, options=None):
|
|
|
205
207
|
"spawn": spawn,
|
|
206
208
|
"spawn_sync": spawn_sync,
|
|
207
209
|
"stdin_is_tty": stdin_is_tty,
|
|
210
|
+
"version": VERSION,
|
|
208
211
|
"update_notice": _get_update_notice(service, env, options) if command not in (
|
|
209
|
-
"add", "cp", "ren", "rename", "mv", "rmv", "clean", "doctor", "repair", "notify", "status", "login", "logout", "export", "import", "help", "version"
|
|
212
|
+
"add", "cp", "ren", "rename", "mv", "rmv", "clean", "doctor", "repair", "update", "notify", "status", "login", "logout", "export", "import", "help", "version"
|
|
210
213
|
) else None,
|
|
211
214
|
"use_color": use_color,
|
|
212
215
|
}
|
|
@@ -238,6 +241,9 @@ def main(argv, options=None):
|
|
|
238
241
|
if command == "repair":
|
|
239
242
|
return handle_repair(rest, ctx)
|
|
240
243
|
|
|
244
|
+
if command == "update":
|
|
245
|
+
return handle_update(rest, ctx)
|
|
246
|
+
|
|
241
247
|
if command == "notify":
|
|
242
248
|
return handle_notify(rest, ctx)
|
|
243
249
|
|
package/src/cli_commands.py
CHANGED
|
@@ -22,11 +22,14 @@ from .provider_runtime import (
|
|
|
22
22
|
from .repair import format_repair_report, repair_health
|
|
23
23
|
from .backup_bundle import read_bundle_meta
|
|
24
24
|
from .status_view import _format_status_detail, _format_status_rows
|
|
25
|
+
from .update_check import fetch_latest_release, is_newer_version
|
|
26
|
+
from .update_manager import build_update_plan, format_update_failure, run_update_plan
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
STATUS_USAGE = "Usage: cdx status [--json] [--refresh] | cdx status --small|-s [--refresh] | cdx status <name> [--json] [--refresh]"
|
|
28
30
|
DOCTOR_USAGE = "Usage: cdx doctor [--json]"
|
|
29
31
|
REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
|
|
32
|
+
UPDATE_USAGE = "Usage: cdx update [--check] [--yes] [--json] [--version TAG]"
|
|
30
33
|
EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
31
34
|
IMPORT_USAGE = "Usage: cdx import <file> [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
32
35
|
API_SCHEMA_VERSION = 1
|
|
@@ -102,6 +105,45 @@ def _parse_session_names(value):
|
|
|
102
105
|
return names
|
|
103
106
|
|
|
104
107
|
|
|
108
|
+
def _parse_update_args(args):
|
|
109
|
+
parsed = {
|
|
110
|
+
"check": False,
|
|
111
|
+
"json": False,
|
|
112
|
+
"yes": False,
|
|
113
|
+
"version": None,
|
|
114
|
+
}
|
|
115
|
+
index = 0
|
|
116
|
+
while index < len(args):
|
|
117
|
+
arg = args[index]
|
|
118
|
+
if arg == "--check":
|
|
119
|
+
parsed["check"] = True
|
|
120
|
+
index += 1
|
|
121
|
+
continue
|
|
122
|
+
if arg == "--json":
|
|
123
|
+
parsed["json"] = True
|
|
124
|
+
index += 1
|
|
125
|
+
continue
|
|
126
|
+
if arg == "--yes":
|
|
127
|
+
parsed["yes"] = True
|
|
128
|
+
index += 1
|
|
129
|
+
continue
|
|
130
|
+
if arg == "--version":
|
|
131
|
+
value, index = _read_option_value(args, index, UPDATE_USAGE)
|
|
132
|
+
parsed["version"] = value
|
|
133
|
+
continue
|
|
134
|
+
if arg.startswith("--version="):
|
|
135
|
+
parsed["version"] = arg.split("=", 1)[1]
|
|
136
|
+
index += 1
|
|
137
|
+
continue
|
|
138
|
+
raise CdxError(UPDATE_USAGE)
|
|
139
|
+
|
|
140
|
+
if parsed["check"] and parsed["version"]:
|
|
141
|
+
raise CdxError("Usage: cdx update --check cannot be combined with --version.")
|
|
142
|
+
if parsed["version"] is not None and not parsed["version"].strip():
|
|
143
|
+
raise CdxError("Usage: cdx update [--check] [--yes] [--json] [--version TAG]")
|
|
144
|
+
return parsed
|
|
145
|
+
|
|
146
|
+
|
|
105
147
|
def _parse_export_args(args):
|
|
106
148
|
parsed = {
|
|
107
149
|
"file_path": None,
|
|
@@ -649,6 +691,91 @@ def handle_logout(rest, ctx):
|
|
|
649
691
|
return 0
|
|
650
692
|
|
|
651
693
|
|
|
694
|
+
def handle_update(rest, ctx):
|
|
695
|
+
parsed = _parse_update_args(rest)
|
|
696
|
+
json_flag = parsed["json"]
|
|
697
|
+
current_version = str(ctx.get("version") or "").strip()
|
|
698
|
+
release_fetcher = ctx["options"].get("fetchLatestRelease") or fetch_latest_release
|
|
699
|
+
target_version = None
|
|
700
|
+
release_url = None
|
|
701
|
+
update_available = False
|
|
702
|
+
|
|
703
|
+
if parsed["version"] is not None:
|
|
704
|
+
target_version = str(parsed["version"]).strip().lstrip("v")
|
|
705
|
+
else:
|
|
706
|
+
latest = release_fetcher()
|
|
707
|
+
if not latest:
|
|
708
|
+
raise CdxError("Unable to check for the latest cdx-manager release. Check your network and try again.")
|
|
709
|
+
target_version = str(latest.get("latest_version") or "").strip()
|
|
710
|
+
release_url = latest.get("url")
|
|
711
|
+
if not target_version:
|
|
712
|
+
raise CdxError("Unable to determine the latest cdx-manager release.")
|
|
713
|
+
update_available = is_newer_version(current_version, target_version)
|
|
714
|
+
if parsed["check"] or not update_available:
|
|
715
|
+
message = (
|
|
716
|
+
f"Update available: cdx-manager {target_version} (current {current_version})"
|
|
717
|
+
if update_available
|
|
718
|
+
else f"cdx-manager {current_version} is already up to date."
|
|
719
|
+
)
|
|
720
|
+
if json_flag:
|
|
721
|
+
_write_json(ctx, _json_success(
|
|
722
|
+
"update",
|
|
723
|
+
message,
|
|
724
|
+
checked=True,
|
|
725
|
+
update_available=update_available,
|
|
726
|
+
current_version=current_version,
|
|
727
|
+
target_version=target_version,
|
|
728
|
+
release_url=release_url,
|
|
729
|
+
warnings=[{
|
|
730
|
+
"code": "update_available",
|
|
731
|
+
"message": message,
|
|
732
|
+
"latest_version": target_version,
|
|
733
|
+
"url": release_url,
|
|
734
|
+
}] if update_available else [],
|
|
735
|
+
))
|
|
736
|
+
return 0
|
|
737
|
+
ctx["out"](f"{_warn(message, ctx['use_color']) if update_available else _success(message, ctx['use_color'])}\n")
|
|
738
|
+
return 0
|
|
739
|
+
|
|
740
|
+
if not parsed["yes"]:
|
|
741
|
+
if not ctx["stdin_is_tty"]:
|
|
742
|
+
raise CdxError("Update requires an interactive terminal or --yes in non-interactive mode.")
|
|
743
|
+
answer = input(f"Update cdx-manager to {target_version}? [y/N] ")
|
|
744
|
+
if answer.strip().lower() not in ("y", "yes"):
|
|
745
|
+
message = "Cancelled."
|
|
746
|
+
if json_flag:
|
|
747
|
+
_write_json(ctx, _json_success("update", message, cancelled=True, current_version=current_version, target_version=target_version))
|
|
748
|
+
return 0
|
|
749
|
+
ctx["out"](f"{_warn(message, ctx['use_color'])}\n")
|
|
750
|
+
return 0
|
|
751
|
+
|
|
752
|
+
plan = build_update_plan(
|
|
753
|
+
target_version=target_version,
|
|
754
|
+
package_root=ctx["options"].get("packageRoot"),
|
|
755
|
+
prefix=ctx["options"].get("prefix"),
|
|
756
|
+
base_prefix=ctx["options"].get("basePrefix"),
|
|
757
|
+
)
|
|
758
|
+
results = run_update_plan(plan, runner=ctx["options"].get("runUpdate"), env=ctx.get("env"))
|
|
759
|
+
failed = any((result.get("returncode") not in (0, None)) for result in results)
|
|
760
|
+
if failed:
|
|
761
|
+
raise CdxError(format_update_failure(results))
|
|
762
|
+
|
|
763
|
+
message = f"Updated cdx-manager to {target_version}"
|
|
764
|
+
if json_flag:
|
|
765
|
+
_write_json(ctx, _json_success(
|
|
766
|
+
"update",
|
|
767
|
+
message,
|
|
768
|
+
updated=True,
|
|
769
|
+
current_version=current_version,
|
|
770
|
+
target_version=target_version,
|
|
771
|
+
mode=plan["mode"],
|
|
772
|
+
steps=results,
|
|
773
|
+
))
|
|
774
|
+
return 0
|
|
775
|
+
ctx["out"](f"{_success(message, ctx['use_color'])}\n")
|
|
776
|
+
return 0
|
|
777
|
+
|
|
778
|
+
|
|
652
779
|
def handle_launch(command, ctx):
|
|
653
780
|
json_flag = "--json" in ctx["options"].get("raw_args", [])
|
|
654
781
|
update_notice = ctx.get("update_notice")
|
package/src/session_service.py
CHANGED
|
@@ -33,6 +33,7 @@ RESERVED_SESSION_NAMES = {
|
|
|
33
33
|
"rename",
|
|
34
34
|
"rmv",
|
|
35
35
|
"status",
|
|
36
|
+
"update",
|
|
36
37
|
"version",
|
|
37
38
|
"--help",
|
|
38
39
|
"-h",
|
|
@@ -540,6 +541,18 @@ def create_session_service(options=None):
|
|
|
540
541
|
session["provider"],
|
|
541
542
|
expected_account_email=expected_account_email,
|
|
542
543
|
)
|
|
544
|
+
if (
|
|
545
|
+
session["provider"] == "codex"
|
|
546
|
+
and not artifact
|
|
547
|
+
and os.path.abspath(base_dir) == os.path.abspath(get_cdx_home(env))
|
|
548
|
+
):
|
|
549
|
+
global_root = _get_global_codex_home(env)
|
|
550
|
+
if global_root and os.path.abspath(global_root) != os.path.abspath(source_root):
|
|
551
|
+
artifact = find_latest_status_artifact(
|
|
552
|
+
global_root,
|
|
553
|
+
session["provider"],
|
|
554
|
+
expected_account_email=expected_account_email,
|
|
555
|
+
)
|
|
543
556
|
if not artifact:
|
|
544
557
|
if _is_low_confidence_status_source(current_status):
|
|
545
558
|
return None
|
package/src/status_source.py
CHANGED
|
@@ -47,6 +47,89 @@ def _safe_stat(file_path):
|
|
|
47
47
|
return None
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _format_local_reset_timestamp(value):
|
|
51
|
+
if value is None or value == "":
|
|
52
|
+
return None
|
|
53
|
+
try:
|
|
54
|
+
timestamp = float(value)
|
|
55
|
+
except (TypeError, ValueError):
|
|
56
|
+
return None
|
|
57
|
+
if timestamp > 1_000_000_000_000:
|
|
58
|
+
timestamp /= 1000.0
|
|
59
|
+
try:
|
|
60
|
+
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc).astimezone()
|
|
61
|
+
except (OverflowError, OSError, ValueError):
|
|
62
|
+
return None
|
|
63
|
+
return f"{MONTH_ABBR[dt.month - 1]} {dt.day} {dt.strftime('%H:%M')}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _normalize_timestamp_value(value):
|
|
67
|
+
return _format_local_reset_timestamp(value)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _coerce_percentage(value):
|
|
71
|
+
if value is None or value == "":
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
return max(0, min(100, int(float(value))))
|
|
75
|
+
except (TypeError, ValueError):
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _extract_structured_rate_limits(record):
|
|
80
|
+
if not isinstance(record, dict):
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
rate_limits = record.get("rate_limits")
|
|
84
|
+
if not isinstance(rate_limits, dict):
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
primary = rate_limits.get("primary") or {}
|
|
88
|
+
secondary = rate_limits.get("secondary") or {}
|
|
89
|
+
credits = rate_limits.get("credits") or {}
|
|
90
|
+
|
|
91
|
+
primary_used = _coerce_percentage(primary.get("used_percent"))
|
|
92
|
+
secondary_used = _coerce_percentage(secondary.get("used_percent"))
|
|
93
|
+
remaining_5h_pct = None if primary_used is None else max(0, 100 - primary_used)
|
|
94
|
+
remaining_week_pct = None if secondary_used is None else max(0, 100 - secondary_used)
|
|
95
|
+
|
|
96
|
+
result = {
|
|
97
|
+
"usage_pct": primary_used if primary_used is not None else secondary_used,
|
|
98
|
+
"remaining_5h_pct": remaining_5h_pct,
|
|
99
|
+
"remaining_week_pct": remaining_week_pct,
|
|
100
|
+
"credits": None,
|
|
101
|
+
"reset_5h_at": _normalize_timestamp_value(primary.get("resets_at")),
|
|
102
|
+
"reset_week_at": _normalize_timestamp_value(secondary.get("resets_at")),
|
|
103
|
+
"reset_at": None,
|
|
104
|
+
"raw_status_text": json.dumps(rate_limits, sort_keys=True),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
balance = credits.get("balance")
|
|
108
|
+
if balance not in (None, ""):
|
|
109
|
+
result["credits"] = str(balance).strip()
|
|
110
|
+
|
|
111
|
+
result["reset_at"] = result["reset_week_at"] or result["reset_5h_at"]
|
|
112
|
+
if result["usage_pct"] is None and result["remaining_5h_pct"] is None and result["remaining_week_pct"] is None:
|
|
113
|
+
return None
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _collect_structured_rate_limit_statuses(value, output=None):
|
|
118
|
+
if output is None:
|
|
119
|
+
output = []
|
|
120
|
+
if isinstance(value, dict):
|
|
121
|
+
structured = _extract_structured_rate_limits(value)
|
|
122
|
+
if structured:
|
|
123
|
+
output.append(structured)
|
|
124
|
+
else:
|
|
125
|
+
for item in value.values():
|
|
126
|
+
_collect_structured_rate_limit_statuses(item, output)
|
|
127
|
+
elif isinstance(value, list):
|
|
128
|
+
for item in value:
|
|
129
|
+
_collect_structured_rate_limit_statuses(item, output)
|
|
130
|
+
return output
|
|
131
|
+
|
|
132
|
+
|
|
50
133
|
def _collect_text_values(value, output=None):
|
|
51
134
|
if output is None:
|
|
52
135
|
output = []
|
|
@@ -162,6 +245,13 @@ def _extract_jsonl_texts(file_path, provider=None):
|
|
|
162
245
|
continue
|
|
163
246
|
try:
|
|
164
247
|
record = json.loads(line)
|
|
248
|
+
for structured in _collect_structured_rate_limit_statuses(record):
|
|
249
|
+
items.append({
|
|
250
|
+
"source_ref": f"{file_path}:{line_index + 1}",
|
|
251
|
+
"timestamp": record.get("timestamp"),
|
|
252
|
+
"text": structured["raw_status_text"],
|
|
253
|
+
"structured": structured,
|
|
254
|
+
})
|
|
165
255
|
payload_texts = _collect_text_values(record.get("payload") or {})
|
|
166
256
|
for candidate in payload_texts:
|
|
167
257
|
if isinstance(candidate, str) and candidate.strip():
|
|
@@ -523,9 +613,6 @@ def find_latest_status_artifact(root_dir, provider=None, expected_account_email=
|
|
|
523
613
|
)
|
|
524
614
|
records = []
|
|
525
615
|
for fp in candidates:
|
|
526
|
-
normalized_fp = fp.replace(os.sep, "/")
|
|
527
|
-
if "/sessions/" in normalized_fp and os.path.basename(fp).startswith("rollout"):
|
|
528
|
-
continue
|
|
529
616
|
if fp.endswith(".jsonl"):
|
|
530
617
|
records.extend(_extract_jsonl_texts(fp, provider))
|
|
531
618
|
elif fp.endswith(".log"):
|
|
@@ -538,6 +625,17 @@ def find_latest_status_artifact(root_dir, provider=None, expected_account_email=
|
|
|
538
625
|
):
|
|
539
626
|
continue
|
|
540
627
|
parsed = extract_named_statuses_from_text(candidate["text"])
|
|
628
|
+
if not parsed and candidate.get("structured"):
|
|
629
|
+
parsed = {
|
|
630
|
+
"usage_pct": candidate["structured"].get("usage_pct"),
|
|
631
|
+
"remaining_5h_pct": candidate["structured"].get("remaining_5h_pct"),
|
|
632
|
+
"remaining_week_pct": candidate["structured"].get("remaining_week_pct"),
|
|
633
|
+
"credits": candidate["structured"].get("credits"),
|
|
634
|
+
"reset_5h_at": candidate["structured"].get("reset_5h_at"),
|
|
635
|
+
"reset_week_at": candidate["structured"].get("reset_week_at"),
|
|
636
|
+
"reset_at": candidate["structured"].get("reset_at"),
|
|
637
|
+
"raw_status_text": candidate["structured"].get("raw_status_text"),
|
|
638
|
+
}
|
|
541
639
|
if not parsed:
|
|
542
640
|
continue
|
|
543
641
|
ts = candidate.get("timestamp")
|
package/src/update_check.py
CHANGED
|
@@ -28,6 +28,10 @@ def _is_newer_version(current_version, latest_version):
|
|
|
28
28
|
return latest > current
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def is_newer_version(current_version, latest_version):
|
|
32
|
+
return _is_newer_version(current_version, latest_version)
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
def _cache_path(base_dir):
|
|
32
36
|
return os.path.join(base_dir, "state", "update-check.json")
|
|
33
37
|
|
|
@@ -63,6 +67,13 @@ def _fetch_latest_release():
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
|
|
70
|
+
def fetch_latest_release():
|
|
71
|
+
try:
|
|
72
|
+
return _fetch_latest_release()
|
|
73
|
+
except (urllib.error.URLError, TimeoutError, ValueError, OSError):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
66
77
|
def check_for_update(base_dir, current_version, env=None, now_fn=None):
|
|
67
78
|
env = env or os.environ
|
|
68
79
|
now_fn = now_fn or (lambda: datetime.now(timezone.utc).timestamp())
|
|
@@ -83,9 +94,8 @@ def check_for_update(base_dir, current_version, env=None, now_fn=None):
|
|
|
83
94
|
}
|
|
84
95
|
return None
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
except (urllib.error.URLError, TimeoutError, ValueError, OSError):
|
|
97
|
+
latest = fetch_latest_release()
|
|
98
|
+
if not latest:
|
|
89
99
|
return None
|
|
90
100
|
|
|
91
101
|
payload = {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .errors import CdxError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _package_root(path=None):
|
|
10
|
+
if path is not None:
|
|
11
|
+
return Path(path).resolve()
|
|
12
|
+
return Path(__file__).resolve().parents[1]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _normalize_version(value):
|
|
16
|
+
if value is None:
|
|
17
|
+
return None
|
|
18
|
+
text = str(value).strip()
|
|
19
|
+
if not text:
|
|
20
|
+
return None
|
|
21
|
+
return text.lstrip("v")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_standalone_install(package_root):
|
|
25
|
+
return package_root.parent.name == "versions"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _is_source_checkout(package_root):
|
|
29
|
+
return (package_root / ".git").exists()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _is_git_dirty(package_root):
|
|
33
|
+
try:
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
["git", "-C", str(package_root), "status", "--porcelain"],
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True,
|
|
38
|
+
check=False,
|
|
39
|
+
)
|
|
40
|
+
except FileNotFoundError as error:
|
|
41
|
+
raise CdxError("git is required to update a source checkout.") from error
|
|
42
|
+
return bool((result.stdout or "").strip())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_python_env(prefix=None, base_prefix=None):
|
|
46
|
+
prefix = prefix or sys.prefix
|
|
47
|
+
base_prefix = base_prefix or sys.base_prefix
|
|
48
|
+
return prefix != base_prefix
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def detect_installation(package_root=None, prefix=None, base_prefix=None):
|
|
52
|
+
root = _package_root(package_root)
|
|
53
|
+
if _is_standalone_install(root):
|
|
54
|
+
return {"mode": "standalone", "package_root": str(root)}
|
|
55
|
+
if _is_source_checkout(root):
|
|
56
|
+
return {"mode": "source", "package_root": str(root)}
|
|
57
|
+
if _is_python_env(prefix=prefix, base_prefix=base_prefix):
|
|
58
|
+
return {"mode": "python", "package_root": str(root)}
|
|
59
|
+
if (root / "package.json").exists():
|
|
60
|
+
return {"mode": "npm", "package_root": str(root)}
|
|
61
|
+
return {"mode": "unknown", "package_root": str(root)}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _join_command(*parts):
|
|
65
|
+
return [str(part) for part in parts if part is not None]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _build_standalone_step(package_root, target_version):
|
|
69
|
+
package_root = _package_root(package_root)
|
|
70
|
+
env = {}
|
|
71
|
+
if target_version:
|
|
72
|
+
env["CDX_VERSION"] = target_version
|
|
73
|
+
if sys.platform == "win32":
|
|
74
|
+
return {
|
|
75
|
+
"label": "standalone installer",
|
|
76
|
+
"command": _join_command("powershell", "-ExecutionPolicy", "Bypass", "-File", package_root / "install.ps1"),
|
|
77
|
+
"cwd": str(package_root),
|
|
78
|
+
"env": env,
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
"label": "standalone installer",
|
|
82
|
+
"command": _join_command("sh", package_root / "install.sh"),
|
|
83
|
+
"cwd": str(package_root),
|
|
84
|
+
"env": env,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _build_source_steps(package_root, target_version):
|
|
89
|
+
package_root = _package_root(package_root)
|
|
90
|
+
if _is_git_dirty(package_root):
|
|
91
|
+
raise CdxError(
|
|
92
|
+
"Your source checkout has uncommitted changes. "
|
|
93
|
+
"Commit or stash them before running cdx update."
|
|
94
|
+
)
|
|
95
|
+
if target_version:
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
"label": "fetch tags",
|
|
99
|
+
"command": _join_command("git", "-C", package_root, "fetch", "--tags", "--force"),
|
|
100
|
+
"cwd": str(package_root),
|
|
101
|
+
"env": {},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"label": f"checkout v{target_version}",
|
|
105
|
+
"command": _join_command("git", "-C", package_root, "checkout", f"v{target_version}"),
|
|
106
|
+
"cwd": str(package_root),
|
|
107
|
+
"env": {},
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
return [
|
|
111
|
+
{
|
|
112
|
+
"label": "git pull --ff-only",
|
|
113
|
+
"command": _join_command("git", "-C", package_root, "pull", "--ff-only"),
|
|
114
|
+
"cwd": str(package_root),
|
|
115
|
+
"env": {},
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _build_python_step(target_version):
|
|
121
|
+
command = [sys.executable, "-m", "pip", "install", "--upgrade"]
|
|
122
|
+
if target_version:
|
|
123
|
+
command.append(f"cdx-manager=={target_version}")
|
|
124
|
+
else:
|
|
125
|
+
command.append("cdx-manager")
|
|
126
|
+
return {
|
|
127
|
+
"label": "python package upgrade",
|
|
128
|
+
"command": command,
|
|
129
|
+
"cwd": None,
|
|
130
|
+
"env": {},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _build_npm_step(target_version):
|
|
135
|
+
spec = f"cdx-manager@{target_version}" if target_version else "cdx-manager@latest"
|
|
136
|
+
return {
|
|
137
|
+
"label": "npm global upgrade",
|
|
138
|
+
"command": ["npm", "install", "-g", spec],
|
|
139
|
+
"cwd": None,
|
|
140
|
+
"env": {},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def build_update_plan(target_version=None, package_root=None, env=None, prefix=None, base_prefix=None):
|
|
145
|
+
root = _package_root(package_root)
|
|
146
|
+
version = _normalize_version(target_version)
|
|
147
|
+
detection = detect_installation(root, prefix=prefix, base_prefix=base_prefix)
|
|
148
|
+
mode = detection["mode"]
|
|
149
|
+
if mode == "standalone":
|
|
150
|
+
steps = [_build_standalone_step(root, version)]
|
|
151
|
+
elif mode == "source":
|
|
152
|
+
steps = _build_source_steps(root, version)
|
|
153
|
+
elif mode == "python":
|
|
154
|
+
steps = [_build_python_step(version)]
|
|
155
|
+
elif mode == "npm":
|
|
156
|
+
steps = [_build_npm_step(version)]
|
|
157
|
+
else:
|
|
158
|
+
raise CdxError(
|
|
159
|
+
"Unable to determine how cdx-manager was installed. "
|
|
160
|
+
"Set CDX_UPDATE_METHOD or update it manually."
|
|
161
|
+
)
|
|
162
|
+
return {
|
|
163
|
+
"mode": mode,
|
|
164
|
+
"package_root": str(root),
|
|
165
|
+
"target_version": version,
|
|
166
|
+
"steps": steps,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _result_code(result):
|
|
171
|
+
if isinstance(result, dict):
|
|
172
|
+
return result.get("returncode") if result.get("returncode") is not None else result.get("status")
|
|
173
|
+
return getattr(result, "returncode", getattr(result, "status", None))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _result_text(result, attr):
|
|
177
|
+
if isinstance(result, dict):
|
|
178
|
+
return result.get(attr)
|
|
179
|
+
return getattr(result, attr, "")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def run_update_plan(plan, runner=None, env=None):
|
|
183
|
+
runner = runner or subprocess.run
|
|
184
|
+
results = []
|
|
185
|
+
for step in plan["steps"]:
|
|
186
|
+
step_env = {**(env or os.environ), **(step.get("env") or {})}
|
|
187
|
+
kwargs = {"cwd": step.get("cwd"), "env": step_env, "check": False}
|
|
188
|
+
result = runner(step["command"], **kwargs)
|
|
189
|
+
code = _result_code(result)
|
|
190
|
+
results.append({
|
|
191
|
+
"label": step["label"],
|
|
192
|
+
"command": step["command"],
|
|
193
|
+
"cwd": step.get("cwd"),
|
|
194
|
+
"returncode": code,
|
|
195
|
+
"stdout": _result_text(result, "stdout"),
|
|
196
|
+
"stderr": _result_text(result, "stderr"),
|
|
197
|
+
})
|
|
198
|
+
if code not in (0, None):
|
|
199
|
+
break
|
|
200
|
+
return results
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def format_update_failure(results):
|
|
204
|
+
if not results:
|
|
205
|
+
return "Update failed."
|
|
206
|
+
last = results[-1]
|
|
207
|
+
message = last.get("stderr") or last.get("stdout") or "Update failed."
|
|
208
|
+
return f"{last['label']} failed: {str(message).strip()}"
|