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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # CDX Manager
2
2
 
3
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.4.0-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
3
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.4.1-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
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.0 sh install.sh
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 directly
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 # Entry point shebang + main() call
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.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",
@@ -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": "PYTHONPYCACHEPREFIX=/tmp/pycache python3 -m unittest discover -s test -p 'test_*_py.py'",
43
- "lint": "PYTHONPYCACHEPREFIX=/tmp/pycache python3 -m py_compile bin/cdx src/*.py test/test_*_py.py",
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
package/src/cli.py CHANGED
@@ -44,7 +44,7 @@ from .status_view import (
44
44
  )
45
45
  from .update_check import check_for_update
46
46
 
47
- VERSION = "0.4.0"
47
+ VERSION = "0.4.1"
48
48
 
49
49
 
50
50
  # ---------------------------------------------------------------------------