cdx-manager 0.4.0 → 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 +12 -6
- package/bin/cdx.js +9 -0
- package/bin/python-runner.js +165 -0
- package/changelogs/CHANGELOGS_0_4_1.md +28 -0
- package/checksums/release-archives.json +4 -0
- package/package.json +4 -4
- package/pyproject.toml +1 -1
- package/src/cli.py +1 -1
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
|
|
|
@@ -69,10 +69,12 @@ One command to launch any session. Zero auth juggling.
|
|
|
69
69
|
|
|
70
70
|
### Prerequisites
|
|
71
71
|
|
|
72
|
+
- Node.js 18+ with npm
|
|
72
73
|
- Python 3.9+
|
|
73
|
-
- npm
|
|
74
74
|
- `codex` and/or `claude` CLI installed and available in your PATH
|
|
75
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
|
+
|
|
76
78
|
### Install
|
|
77
79
|
|
|
78
80
|
From npm:
|
|
@@ -119,7 +121,7 @@ For a specific version:
|
|
|
119
121
|
|
|
120
122
|
```bash
|
|
121
123
|
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
|
|
122
|
-
CDX_VERSION=v0.4.
|
|
124
|
+
CDX_VERSION=v0.4.1 sh install.sh
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
From source:
|
|
@@ -336,8 +338,8 @@ Notes:
|
|
|
336
338
|
## Available Scripts
|
|
337
339
|
|
|
338
340
|
- `npm test`: run the Python test suite
|
|
339
|
-
- `npm run test:py`: run the Python unit tests
|
|
340
|
-
- `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
|
|
341
343
|
- `npm run link`: link `cdx` globally for local development (`npm link`)
|
|
342
344
|
- `npm run unlink`: remove the global link
|
|
343
345
|
|
|
@@ -350,6 +352,7 @@ Notes:
|
|
|
350
352
|
- `pipx install cdx-manager`
|
|
351
353
|
- `uv tool install cdx-manager`
|
|
352
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.
|
|
353
356
|
- `install.sh` is Unix-only.
|
|
354
357
|
- `make install` and `make uninstall` are Unix-oriented convenience commands, not the default Windows path.
|
|
355
358
|
- `cdx` isolates Claude sessions on Windows by setting `HOME`, `USERPROFILE`, `HOMEDRIVE`, and `HOMEPATH`.
|
|
@@ -365,7 +368,9 @@ Notes:
|
|
|
365
368
|
|
|
366
369
|
```text
|
|
367
370
|
bin/
|
|
368
|
-
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
|
|
369
374
|
|
|
370
375
|
src/
|
|
371
376
|
cli.py # Top-level command router
|
|
@@ -419,6 +424,7 @@ Session names are URL-encoded when used as directory or file names. CLI command
|
|
|
419
424
|
## Troubleshooting
|
|
420
425
|
|
|
421
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.
|
|
422
428
|
- **`cdx add` succeeds but the session does not appear** — check that `CDX_HOME` is consistent between calls; a mismatch creates two separate registries.
|
|
423
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.
|
|
424
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,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
|
+
```
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
"v0.3.4": {
|
|
5
5
|
"github_tarball_sha256": "8e8111d6ec41b819fc0249800f175b5741cd11b1439c7e88a3feec770774b12d",
|
|
6
6
|
"github_zip_sha256": "e0bd79f731d86b83787e99b8f2220c8c5fbaa3d7507ebc52823aa5b8d11f0666"
|
|
7
|
+
},
|
|
8
|
+
"v0.4.0": {
|
|
9
|
+
"github_tarball_sha256": "47a99ff2663f4fee33339098d2cfe7c27fbd0f74a16a1f6711f3003287409ae8",
|
|
10
|
+
"github_zip_sha256": "ae7eff748e569c621ef203abb6b395fbf893370471e962252e8c781e7751e5c8"
|
|
7
11
|
}
|
|
8
12
|
}
|
|
9
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdx-manager",
|
|
3
|
-
"version": "0.4.
|
|
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",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"LICENSE"
|
|
36
36
|
],
|
|
37
37
|
"bin": {
|
|
38
|
-
"cdx": "bin/cdx"
|
|
38
|
+
"cdx": "bin/cdx.js"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"test": "npm run test:py",
|
|
42
|
-
"test:py": "
|
|
43
|
-
"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",
|
|
44
44
|
"prepublishOnly": "npm run lint && npm test",
|
|
45
45
|
"link": "npm link",
|
|
46
46
|
"unlink": "npm unlink -g cdx-manager"
|
package/pyproject.toml
CHANGED