cdx-manager 0.9.11 → 0.9.13
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 +1 -1
- package/changelogs/CHANGELOGS_0_9_12.md +36 -0
- package/changelogs/CHANGELOGS_0_9_13.md +27 -0
- package/checksums/release-archives.json +8 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +1 -1
- package/src/cli_commands.py +53 -25
- package/src/provider_runtime.py +2 -1
- package/src/session_service.py +1 -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, Claude, Antigravity, and Ollama sessions from one terminal. Switch between accounts instantly.**
|
|
6
6
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# CDX Manager 0.9.12
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Prevented accidental concurrent launches that reuse the same provider profile and can invalidate shared OAuth credentials.
|
|
6
|
+
- Made Claude add/login prefer `claude setup-token` so new Claude sessions use the long-lived token flow by default.
|
|
7
|
+
|
|
8
|
+
## Changes
|
|
9
|
+
|
|
10
|
+
### Session launch safety
|
|
11
|
+
|
|
12
|
+
`cdx` now detects when another live session already uses the same provider profile before launching a new one.
|
|
13
|
+
|
|
14
|
+
On an interactive terminal, the CLI asks for confirmation before starting the second session. In non-interactive contexts, the launch fails with a clear message so automation does not silently create conflicting sessions.
|
|
15
|
+
|
|
16
|
+
This protects profiles that share one credentials file, where concurrent token refreshes can rotate credentials and log out the other session.
|
|
17
|
+
|
|
18
|
+
### Claude token setup
|
|
19
|
+
|
|
20
|
+
Claude add and login flows now route through `claude setup-token` by default, which produces a longer-lived token than the standard login flow.
|
|
21
|
+
|
|
22
|
+
When this setup-token flow succeeds, stale Claude credentials are removed so the newly configured token is the one used at launch. The existing `--setup-token` flag remains accepted for compatibility.
|
|
23
|
+
|
|
24
|
+
Regression coverage verifies the default Claude setup-token behavior, stale credential cleanup, and compatibility of the retained flag.
|
|
25
|
+
|
|
26
|
+
## Validation
|
|
27
|
+
|
|
28
|
+
- `npm run release:validate`
|
|
29
|
+
- `npm run lint`
|
|
30
|
+
- `npm test`
|
|
31
|
+
- `logics-manager lint --require-status`
|
|
32
|
+
- `logics-manager audit`
|
|
33
|
+
- `git diff --check`
|
|
34
|
+
- `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
|
|
35
|
+
- `python -m build`
|
|
36
|
+
- `python -m twine check dist/*`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# CDX Manager 0.9.13
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Fixed Claude `setup-token` capture on Linux by using the util-linux `script` flush flag.
|
|
6
|
+
|
|
7
|
+
## Changes
|
|
8
|
+
|
|
9
|
+
### Claude setup-token capture
|
|
10
|
+
|
|
11
|
+
Linux terminals now run transcript capture with `script -f` instead of the BSD/macOS-only `script -F` spelling.
|
|
12
|
+
|
|
13
|
+
This prevents util-linux `script` from exiting with `invalid option -- 'F'`, which previously made `cdx` fall back to running `claude setup-token` without transcript capture and miss the printed token.
|
|
14
|
+
|
|
15
|
+
macOS keeps the existing `script -F` behavior.
|
|
16
|
+
|
|
17
|
+
## Validation
|
|
18
|
+
|
|
19
|
+
- `npm run release:validate`
|
|
20
|
+
- `npm run lint`
|
|
21
|
+
- `npm test`
|
|
22
|
+
- `logics-manager lint --require-status`
|
|
23
|
+
- `logics-manager audit`
|
|
24
|
+
- `git diff --check`
|
|
25
|
+
- `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
|
|
26
|
+
- `python -m build`
|
|
27
|
+
- `python -m twine check dist/*`
|
|
@@ -108,6 +108,14 @@
|
|
|
108
108
|
"v0.9.10": {
|
|
109
109
|
"github_tarball_sha256": "4e876d3552eca1b441492a36606f2f0583bff9523801a923d834fe2a4da3c644",
|
|
110
110
|
"github_zip_sha256": "b6c9435f7e36558ac8698dbd6d6c4888e58c64a08d2b06c3bcbb45ce2cd25f7c"
|
|
111
|
+
},
|
|
112
|
+
"v0.9.11": {
|
|
113
|
+
"github_tarball_sha256": "8ab8f673af4ad9c0e4fd64110d47285a1de02c095fd0f6d79eb67e061255093d",
|
|
114
|
+
"github_zip_sha256": "ee5fffa543b8a02c082a180c624d31951c5ff662d492132cd83d4f099da70441"
|
|
115
|
+
},
|
|
116
|
+
"v0.9.12": {
|
|
117
|
+
"github_tarball_sha256": "ed9de98e1b4df5e42a1e389580f03d4b4b2e09ee8f63d23110090e6f164b8ae6",
|
|
118
|
+
"github_zip_sha256": "55f9ead146411ae740db1cae3f91c56d6ebe33c03345f9d4bfd487e45962e412"
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -66,7 +66,7 @@ from .status_view import (
|
|
|
66
66
|
)
|
|
67
67
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
68
68
|
|
|
69
|
-
VERSION = "0.9.
|
|
69
|
+
VERSION = "0.9.13"
|
|
70
70
|
|
|
71
71
|
# Public surface: this module is a facade. Names below are imported above
|
|
72
72
|
# purely to be re-exported (consumed by tests and external callers); listing
|
package/src/cli_commands.py
CHANGED
|
@@ -209,7 +209,14 @@ def _bootstrap_claude_setup_token(session, ctx):
|
|
|
209
209
|
os.remove(transcript_path)
|
|
210
210
|
except OSError:
|
|
211
211
|
pass
|
|
212
|
-
|
|
212
|
+
auth_home = session.get("authHome") or ""
|
|
213
|
+
cred_path = _write_claude_oauth_token(auth_home, token)
|
|
214
|
+
# ponytail: drop short-lived claude login creds so the ~1yr token wins at launch (provider_runtime _read_claude_launch_oauth_token)
|
|
215
|
+
try:
|
|
216
|
+
os.remove(os.path.join(auth_home, ".claude", ".credentials.json"))
|
|
217
|
+
except OSError:
|
|
218
|
+
pass
|
|
219
|
+
return cred_path
|
|
213
220
|
|
|
214
221
|
|
|
215
222
|
def handle_add(rest, ctx):
|
|
@@ -223,16 +230,20 @@ def handle_add(rest, ctx):
|
|
|
223
230
|
if parsed.get("model"):
|
|
224
231
|
session = ctx["service"]["set_launch_settings"](parsed["name"], {"model": parsed["model"]})
|
|
225
232
|
message = f"Created session {parsed['name']} ({parsed['provider']})"
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
ctx
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
if session["provider"] == PROVIDER_CLAUDE and ctx["stdin_is_tty"]:
|
|
234
|
+
# ponytail: mint a ~1yr setup-token instead of the ~daily claude login token
|
|
235
|
+
_bootstrap_claude_setup_token(session, ctx)
|
|
236
|
+
else:
|
|
237
|
+
_ensure_session_authentication(
|
|
238
|
+
session,
|
|
239
|
+
ctx["service"],
|
|
240
|
+
spawn=ctx.get("spawn"),
|
|
241
|
+
spawn_sync=ctx.get("spawn_sync"),
|
|
242
|
+
env_override=ctx.get("env"),
|
|
243
|
+
stdin_is_tty=ctx["stdin_is_tty"],
|
|
244
|
+
behavior="bootstrap",
|
|
245
|
+
signal_emitter=ctx.get("signal_emitter"),
|
|
246
|
+
)
|
|
236
247
|
now = _local_now_iso()
|
|
237
248
|
ctx["service"]["update_auth_state"](parsed["name"], lambda auth: {
|
|
238
249
|
**auth,
|
|
@@ -1809,6 +1820,7 @@ def handle_import(rest, ctx):
|
|
|
1809
1820
|
|
|
1810
1821
|
|
|
1811
1822
|
def handle_login(rest, ctx):
|
|
1823
|
+
rest = [arg for arg in rest if arg != "--setup-token"] # accepted for back-compat; setup-token is now the default for Claude
|
|
1812
1824
|
json_flag, args = _parse_json_flag(rest)
|
|
1813
1825
|
if len(args) != 1:
|
|
1814
1826
|
raise CdxError("Usage: cdx login <name> [--json]")
|
|
@@ -1817,10 +1829,14 @@ def handle_login(rest, ctx):
|
|
|
1817
1829
|
session = ctx["service"]["get_session"](args[0])
|
|
1818
1830
|
if not session:
|
|
1819
1831
|
raise CdxError(f"Unknown session: {args[0]}")
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1832
|
+
if session["provider"] == PROVIDER_CLAUDE:
|
|
1833
|
+
# ponytail: setup-token mints a ~1yr token; claude login mints a ~daily one that forces reconnects
|
|
1834
|
+
_bootstrap_claude_setup_token(session, ctx)
|
|
1835
|
+
else:
|
|
1836
|
+
_run_interactive_provider_command(
|
|
1837
|
+
session, "login", spawn=ctx.get("spawn"), env_override=ctx.get("env"),
|
|
1838
|
+
signal_emitter=ctx.get("signal_emitter")
|
|
1839
|
+
)
|
|
1824
1840
|
auth_probe = _ensure_session_authentication(
|
|
1825
1841
|
session,
|
|
1826
1842
|
ctx["service"],
|
|
@@ -1829,16 +1845,6 @@ def handle_login(rest, ctx):
|
|
|
1829
1845
|
behavior="probe-only",
|
|
1830
1846
|
trust_local_credentials=False,
|
|
1831
1847
|
)
|
|
1832
|
-
if not auth_probe.get("authenticated") and session["provider"] == PROVIDER_CLAUDE:
|
|
1833
|
-
_bootstrap_claude_setup_token(session, ctx)
|
|
1834
|
-
auth_probe = _ensure_session_authentication(
|
|
1835
|
-
session,
|
|
1836
|
-
ctx["service"],
|
|
1837
|
-
spawn_sync=ctx.get("spawn_sync"),
|
|
1838
|
-
env_override=ctx.get("env"),
|
|
1839
|
-
behavior="probe-only",
|
|
1840
|
-
trust_local_credentials=False,
|
|
1841
|
-
)
|
|
1842
1848
|
if not auth_probe.get("authenticated"):
|
|
1843
1849
|
raise CdxError(
|
|
1844
1850
|
f"Login command completed, but session {session['name']} is still not authenticated."
|
|
@@ -2054,11 +2060,33 @@ def handle_resume(rest, ctx):
|
|
|
2054
2060
|
return handle_launch(args[0], ctx, resume=True, force_json=json_flag)
|
|
2055
2061
|
|
|
2056
2062
|
|
|
2063
|
+
def _warn_if_session_already_running(name, ctx):
|
|
2064
|
+
# ponytail: concurrent sessions on one profile share its single OAuth
|
|
2065
|
+
# .credentials.json; both refresh near token expiry and the rotated refresh
|
|
2066
|
+
# token invalidates the other session -> surprise logout. Warn before adding
|
|
2067
|
+
# a second live session on the same profile.
|
|
2068
|
+
active = ctx["service"].get("active_session_runtime")
|
|
2069
|
+
runtime = active(name) if active else None
|
|
2070
|
+
if not runtime:
|
|
2071
|
+
return
|
|
2072
|
+
pid = runtime.get("pid")
|
|
2073
|
+
ctx["out"](f"{_warn(f'A session named {name} is already running (pid {pid}).', ctx['use_color'])}\n")
|
|
2074
|
+
ctx["out"](
|
|
2075
|
+
f"{_dim('Two sessions on one profile share its login and can log each other out. Use a different profile instead.', ctx['use_color'])}\n"
|
|
2076
|
+
)
|
|
2077
|
+
if not ctx["stdin_is_tty"]:
|
|
2078
|
+
return
|
|
2079
|
+
answer = input(f"Launch a second {name} session anyway? [y/N] ")
|
|
2080
|
+
if answer.strip().lower() not in ("y", "yes"):
|
|
2081
|
+
raise CdxError("Launch cancelled.")
|
|
2082
|
+
|
|
2083
|
+
|
|
2057
2084
|
def handle_launch(command, ctx, initial_prompt=None, resume=False, force_json=None):
|
|
2058
2085
|
json_flag = "--json" in ctx.get("raw_args", ctx["options"].get("raw_args", []))
|
|
2059
2086
|
if force_json is not None:
|
|
2060
2087
|
json_flag = force_json
|
|
2061
2088
|
warnings = _update_notice_warnings(ctx)
|
|
2089
|
+
_warn_if_session_already_running(command, ctx)
|
|
2062
2090
|
session = ctx["service"]["launch_session"](command)
|
|
2063
2091
|
capability = _resume_capability_for_session(session, ctx) if resume else None
|
|
2064
2092
|
if capability and not capability["resumable"]:
|
package/src/provider_runtime.py
CHANGED
|
@@ -375,8 +375,9 @@ def _wrap_launch_with_transcript(session, spec, capture_transcript=True, env=Non
|
|
|
375
375
|
|
|
376
376
|
def _default_script_args(transcript_path, spec):
|
|
377
377
|
if sys.platform.startswith("linux"):
|
|
378
|
+
# util-linux `script` flushes with -f (lowercase); -F is the BSD/macOS spelling and errors here.
|
|
378
379
|
command = shlex.join([spec["command"]] + spec["args"])
|
|
379
|
-
return ["-q", "-
|
|
380
|
+
return ["-q", "-f", "-c", command, transcript_path]
|
|
380
381
|
return ["-q", "-F", transcript_path, spec["command"]] + spec["args"]
|
|
381
382
|
|
|
382
383
|
|
package/src/session_service.py
CHANGED
|
@@ -1391,6 +1391,7 @@ def create_session_service(options=None):
|
|
|
1391
1391
|
"copy_session": copy_session,
|
|
1392
1392
|
"rename_session": rename_session,
|
|
1393
1393
|
"launch_session": launch_session,
|
|
1394
|
+
"active_session_runtime": _session_runtime,
|
|
1394
1395
|
"start_session_runtime": start_session_runtime,
|
|
1395
1396
|
"finish_session_runtime": finish_session_runtime,
|
|
1396
1397
|
"ensure_session_state": ensure_session_state,
|