cdx-manager 0.9.10 → 0.9.12

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.9.10-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.9.12-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
4
4
 
5
5
  **Run multiple Codex, Claude, Antigravity, and Ollama sessions from one terminal. Switch between accounts instantly.**
6
6
 
@@ -0,0 +1,44 @@
1
+ # CDX Manager 0.9.11
2
+
3
+ ## Highlights
4
+
5
+ - Hardened transcript cleanup so captured handoff logs strip ANSI and control-sequence noise before reuse.
6
+ - Broadened lint coverage with Ruff's bugbear rules and fixed the newly surfaced issues.
7
+ - Split the CLI facade into dedicated argument and helper modules, reducing the size of the main command surface without changing user-facing commands.
8
+
9
+ ## Changes
10
+
11
+ ### Transcript and status reliability
12
+
13
+ Script-captured transcripts now remove additional terminal control noise before they are parsed or copied into handoff context.
14
+
15
+ Status and session helper internals also gained focused validation coverage for merge behavior, malformed records, and rate-limit parsing.
16
+
17
+ ### CLI maintainability
18
+
19
+ The CLI command layer now reuses shared JSON, table, and parser helpers from extracted modules. The public CLI entrypoint remains stable, while tests can exercise smaller units directly.
20
+
21
+ Shared OAuth token cleanup and JWT decoding helpers were deduplicated into the Claude usage path so provider status code uses the same parsing behavior in one place.
22
+
23
+ ### Validation coverage
24
+
25
+ Ruff now checks a wider rule set, including bugbear rules, while keeping only deferred stylistic exceptions out of the release gate.
26
+
27
+ New regression tests cover:
28
+
29
+ - Codex rate-limit payload parsing
30
+ - provider launch argument construction
31
+ - session status merge and validation helpers
32
+ - handoff transcript control-sequence cleanup
33
+
34
+ ## Validation
35
+
36
+ - `npm run release:validate`
37
+ - `npm run lint`
38
+ - `npm test`
39
+ - `logics-manager lint --require-status`
40
+ - `logics-manager audit`
41
+ - `git diff --check`
42
+ - `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
43
+ - `python -m build`
44
+ - `python -m twine check dist/*`
@@ -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/*`
@@ -104,6 +104,14 @@
104
104
  "v0.9.9": {
105
105
  "github_tarball_sha256": "092c564803dd9a674250040ac308055de3701aa993237f257676403c24eb1318",
106
106
  "github_zip_sha256": "0fbd8de7a97cde5aef14f23036def2e34dd690c2aa78ef93ddfeef0bb3e9547b"
107
+ },
108
+ "v0.9.10": {
109
+ "github_tarball_sha256": "4e876d3552eca1b441492a36606f2f0583bff9523801a923d834fe2a4da3c644",
110
+ "github_zip_sha256": "b6c9435f7e36558ac8698dbd6d6c4888e58c64a08d2b06c3bcbb45ce2cd25f7c"
111
+ },
112
+ "v0.9.11": {
113
+ "github_tarball_sha256": "8ab8f673af4ad9c0e4fd64110d47285a1de02c095fd0f6d79eb67e061255093d",
114
+ "github_zip_sha256": "ee5fffa543b8a02c082a180c624d31951c5ff662d492132cd83d4f099da70441"
107
115
  }
108
116
  }
109
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
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.9.10"
7
+ version = "0.9.12"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -67,4 +67,7 @@ target-version = "py39"
67
67
  line-length = 140
68
68
 
69
69
  [tool.ruff.lint]
70
- select = ["E9", "F63", "F7", "F82"]
70
+ select = ["E", "F", "W", "I", "UP", "B"]
71
+ # Deferred / stylistic-only: E501 (140-col overflows), E741/E731 naming & lambda
72
+ # style. Not chasing these now; logic-catching rules (F, bugbear B) stay on.
73
+ ignore = ["E501", "E741", "E731"]
@@ -7,7 +7,6 @@ from datetime import datetime, timezone
7
7
 
8
8
  from .errors import CdxError
9
9
 
10
-
11
10
  BUNDLE_SCHEMA_VERSION = 1
12
11
  _SALT_BYTES = 16
13
12
  _NONCE_BYTES = 16
@@ -1,10 +1,11 @@
1
+ import base64
1
2
  import json
2
3
  import os
3
4
  import re
4
5
  import subprocess
5
6
  import sys
6
- import urllib.request
7
7
  import urllib.error
8
+ import urllib.request
8
9
  from datetime import datetime, timezone
9
10
 
10
11
  from .errors import CdxError
@@ -28,10 +29,25 @@ def _clean_oauth_token(token):
28
29
  return text
29
30
 
30
31
 
32
+ def _decode_jwt_claims(token):
33
+ if not token or "." not in str(token):
34
+ return {}
35
+ parts = str(token).split(".")
36
+ if len(parts) < 2:
37
+ return {}
38
+ payload = parts[1]
39
+ padding = "=" * (-len(payload) % 4)
40
+ try:
41
+ decoded = base64.urlsafe_b64decode(payload + padding)
42
+ return json.loads(decoded.decode("utf-8"))
43
+ except (ValueError, json.JSONDecodeError, UnicodeDecodeError):
44
+ return {}
45
+
46
+
31
47
  def _read_claude_credentials(auth_home):
32
48
  cred_path = os.path.join(auth_home, ".claude", ".credentials.json")
33
49
  try:
34
- with open(cred_path, "r", encoding="utf-8") as f:
50
+ with open(cred_path, encoding="utf-8") as f:
35
51
  data = json.load(f)
36
52
  creds = data.get("claudeAiOauth") if isinstance(data, dict) else None
37
53
  token = _clean_oauth_token(creds.get("accessToken")) if isinstance(creds, dict) else None
@@ -42,7 +58,7 @@ def _read_claude_credentials(auth_home):
42
58
 
43
59
  anthropic_cred_path = os.path.join(auth_home, "credentials", "default.json")
44
60
  try:
45
- with open(anthropic_cred_path, "r", encoding="utf-8") as f:
61
+ with open(anthropic_cred_path, encoding="utf-8") as f:
46
62
  data = json.load(f)
47
63
  token = _clean_oauth_token(data.get("access_token") if isinstance(data, dict) else None)
48
64
  if token:
package/src/cli.py CHANGED
@@ -8,38 +8,38 @@ from .cli_commands import (
8
8
  API_SCHEMA_VERSION,
9
9
  STATUS_USAGE,
10
10
  handle_add,
11
+ handle_can_resume,
11
12
  handle_clean,
12
13
  handle_config,
13
14
  handle_configs,
14
- handle_can_resume,
15
15
  handle_context,
16
16
  handle_copy,
17
- handle_doctor,
18
17
  handle_disable,
18
+ handle_doctor,
19
19
  handle_enable,
20
20
  handle_export,
21
- handle_import,
22
21
  handle_handoff,
23
22
  handle_history,
23
+ handle_import,
24
24
  handle_last,
25
25
  handle_launch,
26
+ handle_launch_setting_alias,
26
27
  handle_login,
27
28
  handle_logout,
28
- handle_launch_setting_alias,
29
- handle_notify,
30
29
  handle_next,
30
+ handle_notify,
31
31
  handle_remove,
32
- handle_repair,
33
32
  handle_rename,
33
+ handle_repair,
34
34
  handle_resume,
35
35
  handle_run,
36
36
  handle_run_report,
37
37
  handle_run_status,
38
38
  handle_runs,
39
39
  handle_select,
40
+ handle_set,
40
41
  handle_stats,
41
42
  handle_status,
42
- handle_set,
43
43
  handle_unset,
44
44
  handle_update,
45
45
  handle_view,
@@ -66,7 +66,25 @@ 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.10"
69
+ VERSION = "0.9.12"
70
+
71
+ # Public surface: this module is a facade. Names below are imported above
72
+ # purely to be re-exported (consumed by tests and external callers); listing
73
+ # them here documents that and stops F401 from treating them as dead imports.
74
+ __all__ = [
75
+ "STATUS_USAGE",
76
+ "LOG_ROTATE_BYTES",
77
+ "_format_blocking_quota",
78
+ "_format_reset_time",
79
+ "_format_status_detail",
80
+ "_format_status_rows",
81
+ "_pad_table",
82
+ "_rotate_log_if_needed",
83
+ "_visible_len",
84
+ "cli_entry",
85
+ "format_json_error",
86
+ "main",
87
+ ]
70
88
 
71
89
 
72
90
  _COMMAND_HANDLERS = {
@@ -427,7 +445,7 @@ def cli_entry():
427
445
  sys.stderr.write(f"{format_json_error(error)}\n")
428
446
  else:
429
447
  sys.stderr.write(f"{format_error(error)}\n")
430
- raise SystemExit(error.exit_code)
448
+ raise SystemExit(error.exit_code) from None
431
449
 
432
450
 
433
451
  if __name__ == "__main__":