cdx-manager 0.3.4 → 0.4.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 +68 -11
- package/bin/cdx.js +9 -0
- package/bin/python-runner.js +165 -0
- package/changelogs/CHANGELOGS_0_4_0.md +36 -0
- package/changelogs/CHANGELOGS_0_4_1.md +28 -0
- package/checksums/release-archives.json +13 -0
- package/install.ps1 +21 -1
- package/install.sh +51 -0
- package/package.json +5 -4
- package/pyproject.toml +1 -1
- package/src/backup_bundle.py +134 -0
- package/src/cli.py +54 -4
- package/src/cli_commands.py +242 -13
- package/src/session_service.py +185 -2
- package/src/session_store.py +11 -0
- package/src/status_source.py +3 -1
- package/src/update_check.py +107 -0
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
+
[](LICENSE)  
|
|
4
|
+
|
|
3
5
|
**Run multiple Codex and Claude sessions from one terminal. Switch between accounts instantly.**
|
|
4
6
|
|
|
5
7
|
If you use AI coding tools at scale ; multiple accounts, multiple providers : you know the friction: re-authenticating, losing context, juggling environment variables. `cdx` removes all of that.
|
|
6
8
|
|
|
7
9
|
One command to launch any session. Zero auth juggling.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
<img width="213" height="227" alt="image" src="https://github.com/user-attachments/assets/f15f449c-d23e-47fe-a455-17c7386f9be2" />
|
|
12
|
+
<img width="645" height="129" alt="image" src="https://github.com/user-attachments/assets/34bcb395-f832-4da6-9247-3e5022e75e56" />
|
|
10
13
|
|
|
11
14
|
---
|
|
12
15
|
|
|
@@ -66,10 +69,12 @@ One command to launch any session. Zero auth juggling.
|
|
|
66
69
|
|
|
67
70
|
### Prerequisites
|
|
68
71
|
|
|
72
|
+
- Node.js 18+ with npm
|
|
69
73
|
- Python 3.9+
|
|
70
|
-
- npm
|
|
71
74
|
- `codex` and/or `claude` CLI installed and available in your PATH
|
|
72
75
|
|
|
76
|
+
On Windows, the npm launcher looks for Python in this order: `py -3`, `python`, then `python3`. Make sure at least one of those commands resolves to Python 3.
|
|
77
|
+
|
|
73
78
|
### Install
|
|
74
79
|
|
|
75
80
|
From npm:
|
|
@@ -99,19 +104,24 @@ npm install -g cdx-manager
|
|
|
99
104
|
With the standalone PowerShell installer:
|
|
100
105
|
|
|
101
106
|
```powershell
|
|
102
|
-
|
|
107
|
+
Invoke-WebRequest https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.ps1 -OutFile install.ps1
|
|
108
|
+
# Optional: set CDX_SHA256 before running if you have a trusted checksum
|
|
109
|
+
powershell -ExecutionPolicy Bypass -File .\install.ps1
|
|
103
110
|
```
|
|
104
111
|
|
|
105
112
|
With the standalone GitHub installer:
|
|
106
113
|
|
|
107
114
|
```bash
|
|
108
|
-
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh
|
|
115
|
+
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
|
|
116
|
+
# Optional: set CDX_SHA256 before running if you have a trusted checksum
|
|
117
|
+
sh install.sh
|
|
109
118
|
```
|
|
110
119
|
|
|
111
120
|
For a specific version:
|
|
112
121
|
|
|
113
122
|
```bash
|
|
114
|
-
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh
|
|
123
|
+
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
|
|
124
|
+
CDX_VERSION=v0.4.1 sh install.sh
|
|
115
125
|
```
|
|
116
126
|
|
|
117
127
|
From source:
|
|
@@ -150,6 +160,13 @@ Alternatively, for a non-symlinked global source install:
|
|
|
150
160
|
npm install -g .
|
|
151
161
|
```
|
|
152
162
|
|
|
163
|
+
Security note:
|
|
164
|
+
|
|
165
|
+
- The standalone installers try to resolve official release checksums from `checksums/release-archives.json`.
|
|
166
|
+
- You can still override verification explicitly through `CDX_SHA256`.
|
|
167
|
+
- Prefer `npm`, `pipx`, or `uv` when you want registry-backed install flows.
|
|
168
|
+
- If you use the standalone script, download it first, inspect it, and prefer a release with an official checksum entry.
|
|
169
|
+
|
|
153
170
|
### Environment
|
|
154
171
|
|
|
155
172
|
By default, `cdx` stores all data under `~/.cdx/`. Override with:
|
|
@@ -220,11 +237,13 @@ cdx status
|
|
|
220
237
|
| `cdx logout <name> [--json]` | Log out of a session |
|
|
221
238
|
| `cdx rmv <name> [--force] [--json]` | Remove a session and its auth data (prompts for confirmation unless `--force`) |
|
|
222
239
|
| `cdx clean [name] [--json]` | Clear launch transcript logs for one session or all sessions |
|
|
240
|
+
| `cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR] [--force] [--json]` | Export sessions to a portable bundle; `--include-auth` encrypts auth data with a passphrase |
|
|
241
|
+
| `cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
|
|
223
242
|
| `cdx doctor [--json]` | Inspect CLI dependencies, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines |
|
|
224
243
|
| `cdx repair [--dry-run] [--force] [--json]` | Plan or apply safe repairs for missing state files, quarantines, and orphan profiles |
|
|
225
|
-
| `cdx notify <name> --at-reset [--poll seconds] [--once]` | Wait for a session reset time and send a desktop notification when due |
|
|
226
|
-
| `cdx notify --next-ready [--poll seconds] [--once]` | Wait until the recommended session is usable or needs a refresh after reset |
|
|
227
|
-
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions; JSON
|
|
244
|
+
| `cdx notify <name> --at-reset [--poll seconds] [--once] [--json]` | Wait for a session reset time and send a desktop notification when due |
|
|
245
|
+
| `cdx notify --next-ready [--poll seconds] [--once] [--json]` | Wait until the recommended session is usable or needs a refresh after reset |
|
|
246
|
+
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions; JSON returns a versioned payload with structured warnings |
|
|
228
247
|
| `cdx status --small [--refresh]` / `cdx status -s [--refresh]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
229
248
|
| `cdx status <name> [--json] [--refresh]` | Show detailed usage breakdown for one session |
|
|
230
249
|
| `cdx --help` | Show usage |
|
|
@@ -246,6 +265,8 @@ Commands with machine-readable output:
|
|
|
246
265
|
- `cdx ren ... --json`
|
|
247
266
|
- `cdx rmv ... --json`
|
|
248
267
|
- `cdx clean ... --json`
|
|
268
|
+
- `cdx export ... --json`
|
|
269
|
+
- `cdx import ... --json`
|
|
249
270
|
- `cdx login ... --json`
|
|
250
271
|
- `cdx logout ... --json`
|
|
251
272
|
- `cdx doctor --json`
|
|
@@ -256,6 +277,7 @@ Success payloads follow a shared envelope:
|
|
|
256
277
|
|
|
257
278
|
```json
|
|
258
279
|
{
|
|
280
|
+
"schema_version": 1,
|
|
259
281
|
"ok": true,
|
|
260
282
|
"action": "add",
|
|
261
283
|
"message": "Created session work (codex)",
|
|
@@ -270,6 +292,7 @@ Errors use a shared stderr JSON envelope whenever `--json` is present:
|
|
|
270
292
|
|
|
271
293
|
```json
|
|
272
294
|
{
|
|
295
|
+
"schema_version": 1,
|
|
273
296
|
"ok": false,
|
|
274
297
|
"error": {
|
|
275
298
|
"code": "invalid_usage",
|
|
@@ -279,15 +302,44 @@ Errors use a shared stderr JSON envelope whenever `--json` is present:
|
|
|
279
302
|
}
|
|
280
303
|
```
|
|
281
304
|
|
|
305
|
+
`status --json` and similar commands also use the same envelope and place non-fatal issues in `warnings` instead of mixing plain-text diagnostics into `stderr`.
|
|
306
|
+
|
|
282
307
|
This makes `cdx-manager` usable from editor plugins, scripts, and desktop apps without scraping human-readable terminal output.
|
|
283
308
|
|
|
284
309
|
---
|
|
285
310
|
|
|
311
|
+
## Backup And Restore
|
|
312
|
+
|
|
313
|
+
You can move sessions between machines with portable bundles:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
cdx export backup.cdx
|
|
317
|
+
cdx import backup.cdx
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
To migrate auth and avoid logging in again, include auth data in an encrypted bundle:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
export CDX_BUNDLE_PASSPHRASE='choose-a-strong-passphrase'
|
|
324
|
+
cdx export backup-auth.cdx --include-auth --passphrase-env CDX_BUNDLE_PASSPHRASE
|
|
325
|
+
cdx import backup-auth.cdx --passphrase-env CDX_BUNDLE_PASSPHRASE
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Notes:
|
|
329
|
+
|
|
330
|
+
- `--include-auth` is encrypted and requires a passphrase.
|
|
331
|
+
- Without `--passphrase-env`, `cdx` prompts in an interactive terminal.
|
|
332
|
+
- `--sessions work,perso` exports or imports only a subset.
|
|
333
|
+
- `--force` allows overwriting existing destination sessions during import or replacing an existing bundle file during export.
|
|
334
|
+
- Auth bundles contain credentials. Treat them like secrets and delete them after transfer.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
286
338
|
## Available Scripts
|
|
287
339
|
|
|
288
340
|
- `npm test`: run the Python test suite
|
|
289
|
-
- `npm run test:py`: run the Python unit tests
|
|
290
|
-
- `npm run lint`: byte-compile the Python sources and tests
|
|
341
|
+
- `npm run test:py`: run the Python unit tests through the portable launcher
|
|
342
|
+
- `npm run lint`: check the Node launcher and byte-compile the Python sources and tests
|
|
291
343
|
- `npm run link`: link `cdx` globally for local development (`npm link`)
|
|
292
344
|
- `npm run unlink`: remove the global link
|
|
293
345
|
|
|
@@ -300,6 +352,7 @@ This makes `cdx-manager` usable from editor plugins, scripts, and desktop apps w
|
|
|
300
352
|
- `pipx install cdx-manager`
|
|
301
353
|
- `uv tool install cdx-manager`
|
|
302
354
|
- `install.ps1`
|
|
355
|
+
- The npm launcher resolves Python via `py -3`, `python`, then `python3`, so a global npm install works even when `python3.exe` is missing.
|
|
303
356
|
- `install.sh` is Unix-only.
|
|
304
357
|
- `make install` and `make uninstall` are Unix-oriented convenience commands, not the default Windows path.
|
|
305
358
|
- `cdx` isolates Claude sessions on Windows by setting `HOME`, `USERPROFILE`, `HOMEDRIVE`, and `HOMEPATH`.
|
|
@@ -315,12 +368,15 @@ This makes `cdx-manager` usable from editor plugins, scripts, and desktop apps w
|
|
|
315
368
|
|
|
316
369
|
```text
|
|
317
370
|
bin/
|
|
318
|
-
cdx
|
|
371
|
+
cdx.js # Node launcher used by npm
|
|
372
|
+
python-runner.js # Shared Python resolver and process wrapper
|
|
373
|
+
cdx # Python entrypoint invoked by the launcher
|
|
319
374
|
|
|
320
375
|
src/
|
|
321
376
|
cli.py # Top-level command router
|
|
322
377
|
cli_commands.py # Command handlers and argument handling
|
|
323
378
|
cli_render.py # Terminal formatting, tables, colors, and errors
|
|
379
|
+
backup_bundle.py # Portable session bundle encoding/decoding + auth encryption
|
|
324
380
|
status_view.py # Status table/detail rendering and priority ranking
|
|
325
381
|
provider_runtime.py # Provider launch/auth commands, transcripts, signals
|
|
326
382
|
claude_refresh.py # Claude usage refresh orchestration
|
|
@@ -368,6 +424,7 @@ Session names are URL-encoded when used as directory or file names. CLI command
|
|
|
368
424
|
## Troubleshooting
|
|
369
425
|
|
|
370
426
|
- **`cdx <name>` fails with "not authenticated"** — run `cdx login <name>` first.
|
|
427
|
+
- **`cdx` says no compatible Python 3 interpreter was found** — install Python 3 and make `py -3`, `python`, or `python3` available on PATH.
|
|
371
428
|
- **`cdx add` succeeds but the session does not appear** — check that `CDX_HOME` is consistent between calls; a mismatch creates two separate registries.
|
|
372
429
|
- **Status shows `n/a` for all fields** — the session has not been launched yet, or the provider has not written any status output to its history files. Launch the session and run `/status` inside it at least once.
|
|
373
430
|
- **`cdx rmv` says "Removal requires confirmation in an interactive terminal"** — pass `--force` to bypass the prompt in non-interactive environments (scripts, CI).
|
package/bin/cdx.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { runPython } = require("./python-runner");
|
|
5
|
+
|
|
6
|
+
const scriptPath = path.join(__dirname, "cdx");
|
|
7
|
+
const exitCode = runPython([scriptPath, ...process.argv.slice(2)], { expandGlobs: false });
|
|
8
|
+
|
|
9
|
+
process.exit(exitCode);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const PYTHON_VERSION_CHECK = "import sys; sys.exit(0 if sys.version_info[0] == 3 else 1)";
|
|
9
|
+
const PYTHON_CACHE_PREFIX = path.join(os.tmpdir(), "cdx-manager-pycache");
|
|
10
|
+
const SIGNAL_EXIT_CODES = {
|
|
11
|
+
SIGHUP: 129,
|
|
12
|
+
SIGINT: 130,
|
|
13
|
+
SIGTERM: 143,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const WINDOWS_CANDIDATES = [
|
|
17
|
+
{ command: "py", args: ["-3"], label: "py -3" },
|
|
18
|
+
{ command: "python", args: [], label: "python" },
|
|
19
|
+
{ command: "python3", args: [], label: "python3" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const UNIX_CANDIDATES = [
|
|
23
|
+
{ command: "python3", args: [], label: "python3" },
|
|
24
|
+
{ command: "python", args: [], label: "python" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function getCandidates(platform = process.platform) {
|
|
28
|
+
return platform === "win32" ? WINDOWS_CANDIDATES : UNIX_CANDIDATES;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function probeCandidate(candidate) {
|
|
32
|
+
const result = spawnSync(
|
|
33
|
+
candidate.command,
|
|
34
|
+
[...candidate.args, "-c", PYTHON_VERSION_CHECK],
|
|
35
|
+
{ stdio: "ignore", windowsHide: true }
|
|
36
|
+
);
|
|
37
|
+
return result.status === 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findPython(platform = process.platform) {
|
|
41
|
+
for (const candidate of getCandidates(platform)) {
|
|
42
|
+
if (probeCandidate(candidate)) {
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasGlobCharacters(value) {
|
|
50
|
+
return value.includes("*") || value.includes("?") || value.includes("[");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function escapeRegex(value) {
|
|
54
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function segmentToRegExp(segment) {
|
|
58
|
+
const pattern = escapeRegex(segment).replace(/\\\*/g, ".*").replace(/\\\?/g, ".");
|
|
59
|
+
return new RegExp(`^${pattern}$`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function expandGlob(pattern) {
|
|
63
|
+
const pathLike = pattern.includes("/") || pattern.includes("\\") || path.isAbsolute(pattern);
|
|
64
|
+
if (!hasGlobCharacters(pattern) || !pathLike) {
|
|
65
|
+
return [pattern];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const absolute = path.isAbsolute(pattern);
|
|
69
|
+
const root = absolute ? path.parse(pattern).root : process.cwd();
|
|
70
|
+
const relativePattern = absolute ? path.relative(root, pattern) : pattern;
|
|
71
|
+
const segments = relativePattern.split(/[\\/]+/).filter(Boolean);
|
|
72
|
+
const matches = [];
|
|
73
|
+
|
|
74
|
+
const walk = (currentPath, index) => {
|
|
75
|
+
if (index >= segments.length) {
|
|
76
|
+
if (fs.existsSync(currentPath)) {
|
|
77
|
+
matches.push(currentPath);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const segment = segments[index];
|
|
83
|
+
const nextIndex = index + 1;
|
|
84
|
+
|
|
85
|
+
if (!hasGlobCharacters(segment)) {
|
|
86
|
+
walk(path.join(currentPath, segment), nextIndex);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!fs.existsSync(currentPath) || !fs.statSync(currentPath).isDirectory()) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const matcher = segmentToRegExp(segment);
|
|
95
|
+
for (const entry of fs.readdirSync(currentPath, { withFileTypes: true })) {
|
|
96
|
+
if (matcher.test(entry.name)) {
|
|
97
|
+
walk(path.join(currentPath, entry.name), nextIndex);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
walk(root, 0);
|
|
103
|
+
return matches.length > 0 ? matches : [pattern];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function expandArgs(args) {
|
|
107
|
+
const expanded = [];
|
|
108
|
+
for (const arg of args) {
|
|
109
|
+
expanded.push(...expandGlob(arg));
|
|
110
|
+
}
|
|
111
|
+
return expanded;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function prepareEnv(env = process.env) {
|
|
115
|
+
const nextEnv = { ...env };
|
|
116
|
+
if (!nextEnv.PYTHONPYCACHEPREFIX) {
|
|
117
|
+
fs.mkdirSync(PYTHON_CACHE_PREFIX, { recursive: true });
|
|
118
|
+
nextEnv.PYTHONPYCACHEPREFIX = PYTHON_CACHE_PREFIX;
|
|
119
|
+
}
|
|
120
|
+
return nextEnv;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function runPython(args, options = {}) {
|
|
124
|
+
const platform = options.platform || process.platform;
|
|
125
|
+
const candidate = findPython(platform);
|
|
126
|
+
|
|
127
|
+
if (!candidate) {
|
|
128
|
+
const tried = getCandidates(platform).map((item) => item.label).join(", ");
|
|
129
|
+
console.error(`cdx: no compatible Python 3 interpreter found. Tried: ${tried}.`);
|
|
130
|
+
console.error("Install Python 3 and make one of those commands available on PATH.");
|
|
131
|
+
return 127;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const finalArgs = options.expandGlobs === false ? args.slice() : expandArgs(args);
|
|
135
|
+
const result = spawnSync(
|
|
136
|
+
candidate.command,
|
|
137
|
+
[...candidate.args, ...finalArgs],
|
|
138
|
+
{
|
|
139
|
+
env: prepareEnv(options.env),
|
|
140
|
+
stdio: "inherit",
|
|
141
|
+
windowsHide: true,
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (result.error) {
|
|
146
|
+
console.error(`cdx: failed to launch ${candidate.label}: ${result.error.message}`);
|
|
147
|
+
return 127;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (result.signal) {
|
|
151
|
+
return SIGNAL_EXIT_CODES[result.signal] || 128;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return typeof result.status === "number" ? result.status : 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (require.main === module) {
|
|
158
|
+
process.exit(runPython(process.argv.slice(2)));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
expandArgs,
|
|
163
|
+
findPython,
|
|
164
|
+
runPython,
|
|
165
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# CHANGELOGS_0_4_0
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-16
|
|
4
|
+
|
|
5
|
+
## CDX Manager 0.4.0
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
### Portable session bundles
|
|
10
|
+
|
|
11
|
+
- Added `cdx export <file>` and `cdx import <file>` for moving sessions between machines.
|
|
12
|
+
- Added optional encrypted auth export with `--include-auth` and interactive or environment-driven passphrase handling.
|
|
13
|
+
- Added subset export/import support with `--sessions`.
|
|
14
|
+
- Preserved per-session state alongside session records so imported environments keep their local metadata.
|
|
15
|
+
- Added bundle schema validation and integrity checks during import.
|
|
16
|
+
|
|
17
|
+
### Update awareness and installer hardening
|
|
18
|
+
|
|
19
|
+
- 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
|
+
- Surfaced update notices in interactive output and structured JSON warnings.
|
|
21
|
+
- Hardened the standalone install scripts to consume official release-archive checksums when available.
|
|
22
|
+
- Documented the checksum-backed installer flow and backup/restore usage in the README.
|
|
23
|
+
|
|
24
|
+
### Status isolation fix
|
|
25
|
+
|
|
26
|
+
- Fixed Codex status parsing so boxed blank lines in TUI transcripts no longer drop the `Account:` context line.
|
|
27
|
+
- Restored account-aware status selection when multiple sessions contain similar `/status` blocks.
|
|
28
|
+
- Added regression coverage for mixed-account transcript selection and bundle export/import flows.
|
|
29
|
+
|
|
30
|
+
### Validation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run lint
|
|
34
|
+
npm test
|
|
35
|
+
python3 -m build --no-isolation
|
|
36
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# CHANGELOGS_0_4_1
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-19
|
|
4
|
+
|
|
5
|
+
## CDX Manager 0.4.1
|
|
6
|
+
|
|
7
|
+
CDX Manager 0.4.1 fixes the Windows npm entrypoint 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 entrypoint.
|
|
8
|
+
|
|
9
|
+
### Windows npm launcher fix
|
|
10
|
+
|
|
11
|
+
- Replaced the npm-facing `bin.cdx` target with a Node launcher at `bin/cdx.js`.
|
|
12
|
+
- Added Python discovery that tries `py -3`, then `python`, then `python3` on Windows.
|
|
13
|
+
- Kept the Python script under `bin/cdx` as the logical CLI entrypoint.
|
|
14
|
+
- Added a clear error message when no compatible Python 3 interpreter is available.
|
|
15
|
+
|
|
16
|
+
### Documentation and packaging
|
|
17
|
+
|
|
18
|
+
- Updated the README with Windows Python prerequisites and the new launcher behavior.
|
|
19
|
+
- Added a portable shared Node wrapper for the npm test and lint scripts.
|
|
20
|
+
- Bumped the package version for both npm and PyPI release flows.
|
|
21
|
+
|
|
22
|
+
### Validation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm run lint
|
|
26
|
+
npm test
|
|
27
|
+
node bin/cdx.js --version
|
|
28
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"releases": {
|
|
4
|
+
"v0.3.4": {
|
|
5
|
+
"github_tarball_sha256": "8e8111d6ec41b819fc0249800f175b5741cd11b1439c7e88a3feec770774b12d",
|
|
6
|
+
"github_zip_sha256": "e0bd79f731d86b83787e99b8f2220c8c5fbaa3d7507ebc52823aa5b8d11f0666"
|
|
7
|
+
},
|
|
8
|
+
"v0.4.0": {
|
|
9
|
+
"github_tarball_sha256": "47a99ff2663f4fee33339098d2cfe7c27fbd0f74a16a1f6711f3003287409ae8",
|
|
10
|
+
"github_zip_sha256": "ae7eff748e569c621ef203abb6b395fbf893370471e962252e8c781e7751e5c8"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/install.ps1
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
param(
|
|
2
2
|
[string]$Version = $env:CDX_VERSION,
|
|
3
|
-
[string]$Prefix = $env:CDX_PREFIX
|
|
3
|
+
[string]$Prefix = $env:CDX_PREFIX,
|
|
4
|
+
[string]$Sha256 = $env:CDX_SHA256,
|
|
5
|
+
[string]$ChecksumsUrl = $env:CDX_CHECKSUMS_URL
|
|
4
6
|
)
|
|
5
7
|
|
|
6
8
|
$ErrorActionPreference = "Stop"
|
|
7
9
|
|
|
8
10
|
$repo = "AlexAgo83/cdx-manager"
|
|
11
|
+
if (-not $ChecksumsUrl) {
|
|
12
|
+
$ChecksumsUrl = "https://raw.githubusercontent.com/$repo/main/checksums/release-archives.json"
|
|
13
|
+
}
|
|
9
14
|
|
|
10
15
|
function Require-Command {
|
|
11
16
|
param([string]$Name)
|
|
@@ -44,6 +49,21 @@ New-Item -ItemType Directory -Force -Path $tmpRoot, $extractRoot, $binDir, $inst
|
|
|
44
49
|
|
|
45
50
|
try {
|
|
46
51
|
Invoke-WebRequest -Uri $archiveUrl -OutFile $archivePath
|
|
52
|
+
if (-not $Sha256) {
|
|
53
|
+
try {
|
|
54
|
+
$checksums = Invoke-RestMethod -Uri $ChecksumsUrl
|
|
55
|
+
$Sha256 = $checksums.releases.$tag.github_zip_sha256
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if ($Sha256) {
|
|
60
|
+
$actualSha256 = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash.ToLowerInvariant()
|
|
61
|
+
if ($actualSha256 -ne $Sha256.ToLowerInvariant()) {
|
|
62
|
+
throw "cdx install: checksum mismatch for $tag`nexpected: $Sha256`nactual: $actualSha256"
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
Write-Warning "No official checksum available for $tag; continuing without verification."
|
|
66
|
+
}
|
|
47
67
|
Expand-Archive -Path $archivePath -DestinationPath $extractRoot -Force
|
|
48
68
|
|
|
49
69
|
$sourceDir = Get-ChildItem -Path $extractRoot -Directory | Select-Object -First 1
|
package/install.sh
CHANGED
|
@@ -6,6 +6,7 @@ VERSION="${CDX_VERSION:-}"
|
|
|
6
6
|
PREFIX="${PREFIX:-$HOME/.local}"
|
|
7
7
|
BIN_DIR="${BIN_DIR:-$PREFIX/bin}"
|
|
8
8
|
INSTALL_ROOT="${CDX_INSTALL_ROOT:-$PREFIX/share/cdx-manager}"
|
|
9
|
+
CHECKSUMS_URL="${CDX_CHECKSUMS_URL:-https://raw.githubusercontent.com/$REPO/main/checksums/release-archives.json}"
|
|
9
10
|
|
|
10
11
|
need() {
|
|
11
12
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
@@ -18,6 +19,38 @@ need curl
|
|
|
18
19
|
need tar
|
|
19
20
|
need python3
|
|
20
21
|
|
|
22
|
+
sha256_file() {
|
|
23
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
24
|
+
sha256sum "$1" | awk '{print $1}'
|
|
25
|
+
return
|
|
26
|
+
fi
|
|
27
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
28
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
29
|
+
return
|
|
30
|
+
fi
|
|
31
|
+
echo "cdx install: missing checksum tool (sha256sum or shasum)" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
resolve_expected_sha256() {
|
|
36
|
+
curl -fsSL "$CHECKSUMS_URL" |
|
|
37
|
+
python3 - "$1" <<'PY'
|
|
38
|
+
import json
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
tag = sys.argv[1]
|
|
42
|
+
try:
|
|
43
|
+
payload = json.load(sys.stdin)
|
|
44
|
+
except Exception:
|
|
45
|
+
raise SystemExit(1)
|
|
46
|
+
|
|
47
|
+
release = (payload.get("releases") or {}).get(tag) or {}
|
|
48
|
+
value = release.get("github_tarball_sha256")
|
|
49
|
+
if value:
|
|
50
|
+
print(value)
|
|
51
|
+
PY
|
|
52
|
+
}
|
|
53
|
+
|
|
21
54
|
if [ -z "$VERSION" ]; then
|
|
22
55
|
VERSION="$(
|
|
23
56
|
curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" |
|
|
@@ -38,6 +71,24 @@ trap cleanup EXIT INT TERM
|
|
|
38
71
|
|
|
39
72
|
ARCHIVE_URL="https://github.com/$REPO/archive/refs/tags/$TAG.tar.gz"
|
|
40
73
|
curl -fsSL "$ARCHIVE_URL" -o "$TMP_DIR/cdx-manager.tar.gz"
|
|
74
|
+
|
|
75
|
+
EXPECTED_SHA256="${CDX_SHA256:-}"
|
|
76
|
+
if [ -z "$EXPECTED_SHA256" ]; then
|
|
77
|
+
EXPECTED_SHA256="$(resolve_expected_sha256 "$TAG" 2>/dev/null || true)"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [ -n "$EXPECTED_SHA256" ]; then
|
|
81
|
+
ACTUAL_SHA256="$(sha256_file "$TMP_DIR/cdx-manager.tar.gz")"
|
|
82
|
+
if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
|
|
83
|
+
echo "cdx install: checksum mismatch for $TAG" >&2
|
|
84
|
+
echo "expected: $EXPECTED_SHA256" >&2
|
|
85
|
+
echo "actual: $ACTUAL_SHA256" >&2
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
else
|
|
89
|
+
echo "cdx install: warning: no official checksum available for $TAG; continuing without verification" >&2
|
|
90
|
+
fi
|
|
91
|
+
|
|
41
92
|
tar -xzf "$TMP_DIR/cdx-manager.tar.gz" -C "$TMP_DIR"
|
|
42
93
|
|
|
43
94
|
SRC_DIR="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdx-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Terminal session manager for Codex and Claude accounts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexandre Agostini",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"bin",
|
|
28
|
+
"checksums",
|
|
28
29
|
"changelogs",
|
|
29
30
|
"src",
|
|
30
31
|
"install.sh",
|
|
@@ -34,12 +35,12 @@
|
|
|
34
35
|
"LICENSE"
|
|
35
36
|
],
|
|
36
37
|
"bin": {
|
|
37
|
-
"cdx": "bin/cdx"
|
|
38
|
+
"cdx": "bin/cdx.js"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
41
|
"test": "npm run test:py",
|
|
41
|
-
"test:py": "
|
|
42
|
-
"lint": "
|
|
42
|
+
"test:py": "node bin/python-runner.js -m unittest discover -s test -p test_*_py.py",
|
|
43
|
+
"lint": "node --check bin/cdx.js && node --check bin/python-runner.js && node bin/python-runner.js -m py_compile bin/cdx src/*.py test/test_*_py.py",
|
|
43
44
|
"prepublishOnly": "npm run lint && npm test",
|
|
44
45
|
"link": "npm link",
|
|
45
46
|
"unlink": "npm unlink -g cdx-manager"
|
package/pyproject.toml
CHANGED