cdx-manager 0.9.5 → 0.9.7

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.5-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.7-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,27 @@
1
+ # CDX Manager 0.9.6
2
+
3
+ ## Highlights
4
+
5
+ - Kept Claude launch authentication separate from Claude quota-refresh failures.
6
+ - Reduced periodic update-check caching from 12 hours to 1 hour.
7
+
8
+ ## Changes
9
+
10
+ ### Claude auth stays usable after quota refresh errors
11
+
12
+ `cdx status --refresh` no longer marks a Claude session as logged out when the Anthropic quota-refresh request returns an authentication error but the Claude CLI auth probe succeeds.
13
+
14
+ This keeps `cdx <session>` launch readiness tied to `claude auth status`, while still surfacing quota-refresh failures as warnings for status and automation.
15
+
16
+ ### Hourly update checks
17
+
18
+ Periodic update checks now reuse cached release data for 1 hour instead of 12 hours. The same interval applies to both the `cdx-manager` release check and the companion `logics-manager` update check.
19
+
20
+ ## Validation
21
+
22
+ - `python3 -m unittest discover -s test -p 'test_update_check_py.py'`
23
+ - `python3 -m unittest discover -s test -p 'test_*.py' -k 'claude_auth'`
24
+ - `python3 -m unittest discover -s test -p 'test_*.py' -k 'login_claude'`
25
+ - `npm run lint`
26
+ - `npm test`
27
+ - `cdx status claw --json --refresh`
@@ -0,0 +1,25 @@
1
+ # CDX Manager 0.9.7
2
+
3
+ ## Highlights
4
+
5
+ - Hardened profile cleanup so read-only provider cache files do not block session removal or repair cleanup.
6
+
7
+ ## Changes
8
+
9
+ ### Read-only profile cleanup
10
+
11
+ `cdx rmv`, session overwrite rollback cleanup, and repair quarantine cleanup now make profile trees user-writable before deleting them.
12
+
13
+ This fixes failures seen when provider-managed caches contain read-only files, including nested Go module cache paths created under session-specific provider homes.
14
+
15
+ ## Validation
16
+
17
+ - `npm run release:validate`
18
+ - `npm run lint`
19
+ - `npm test`
20
+ - `logics-manager lint --require-status`
21
+ - `logics-manager audit`
22
+ - `git diff --check`
23
+ - `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
24
+ - `python -m build`
25
+ - `python -m twine check dist/*`
@@ -84,6 +84,14 @@
84
84
  "v0.9.4": {
85
85
  "github_tarball_sha256": "fe1c96e612392130a98ec6d9856ec41fb0c055c1a274ee77f143b5e4b8c1fb2f",
86
86
  "github_zip_sha256": "494b84aef93f796d78ef164c7b7670ba3dec1d81f66be966cb7a84d77d5ed6e3"
87
+ },
88
+ "v0.9.5": {
89
+ "github_tarball_sha256": "29c512600ed366e06bcaa8ae12f105cc198f85c6887d46c9032d677c5335203b",
90
+ "github_zip_sha256": "a7be652ba39b929eae6e90c48410ce8b7c9c122625da55bfcd50edb755d6d609"
91
+ },
92
+ "v0.9.6": {
93
+ "github_tarball_sha256": "7706ba127f247dfab50404ca175921c7874624e835c920799b3b122a45f7dc4b",
94
+ "github_zip_sha256": "be11246f65eae006512247067bff22863a6ebe8d5738b056fe17f654fd5d9b5c"
87
95
  }
88
96
  }
89
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
@@ -42,6 +42,7 @@
42
42
  "cdx": "bin/cdx.js"
43
43
  },
44
44
  "scripts": {
45
+ "status": "node bin/cdx.js status",
45
46
  "test": "npm run test:py",
46
47
  "test:py": "node bin/python-runner.js -m unittest discover -s test -p test_*_py.py",
47
48
  "lint": "node --check bin/cdx.js && node --check bin/python-runner.js && node bin/python-runner.js scripts/verify_project_docs.py && node bin/python-runner.js -m py_compile bin/cdx src/*.py scripts/*.py test/test_*_py.py",
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.5"
7
+ version = "0.9.7"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -2,7 +2,7 @@ import inspect
2
2
  import threading
3
3
  from datetime import datetime, timezone
4
4
 
5
- from .claude_usage import ClaudeAuthInvalidError, refresh_claude_session_status
5
+ from .claude_usage import refresh_claude_session_status
6
6
  from .config import PROVIDER_CLAUDE
7
7
  from .errors import CdxError
8
8
 
@@ -83,24 +83,6 @@ def _refresh_claude_sessions(service, refresh_fn=None, target_names=None, force=
83
83
  for t in threads:
84
84
  t.join(timeout=10)
85
85
 
86
- invalid_auth = sorted({
87
- item.get("session")
88
- for item in errors
89
- if item.get("session") and isinstance(item.get("error"), ClaudeAuthInvalidError)
90
- })
91
- if invalid_auth and service.get("update_auth_state"):
92
- now = datetime.now(timezone.utc).astimezone().isoformat()
93
- for name in invalid_auth:
94
- try:
95
- service["update_auth_state"](name, lambda auth: {
96
- **auth,
97
- "status": "logged_out",
98
- "lastCheckedAt": now,
99
- "lastLoggedOutAt": now,
100
- })
101
- except CdxError:
102
- pass
103
-
104
86
  for name, usage in results.items():
105
87
  try:
106
88
  service["record_status"](name, usage)
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.5"
69
+ VERSION = "0.9.7"
70
70
 
71
71
 
72
72
  # ---------------------------------------------------------------------------
@@ -0,0 +1,50 @@
1
+ import os
2
+ import shutil
3
+ import stat
4
+
5
+
6
+ def _make_user_writable(path):
7
+ if os.path.islink(path):
8
+ return
9
+ try:
10
+ current = os.stat(path).st_mode
11
+ except OSError:
12
+ return
13
+ mode = current | stat.S_IRUSR | stat.S_IWUSR
14
+ if stat.S_ISDIR(current):
15
+ mode |= stat.S_IXUSR
16
+ try:
17
+ os.chmod(path, mode)
18
+ except OSError:
19
+ pass
20
+
21
+
22
+ def _make_tree_user_writable(path):
23
+ if not os.path.exists(path):
24
+ return
25
+ for root, dirs, files in os.walk(path, topdown=False):
26
+ for name in files:
27
+ file_path = os.path.join(root, name)
28
+ if not os.path.islink(file_path):
29
+ _make_user_writable(file_path)
30
+ for name in dirs:
31
+ dir_path = os.path.join(root, name)
32
+ if not os.path.islink(dir_path):
33
+ _make_user_writable(dir_path)
34
+ _make_user_writable(root)
35
+
36
+
37
+ def remove_tree(path, ignore_errors=False):
38
+ def onerror(func, failing_path, _exc_info):
39
+ parent = os.path.dirname(failing_path)
40
+ if parent:
41
+ _make_user_writable(parent)
42
+ _make_user_writable(failing_path)
43
+ try:
44
+ func(failing_path)
45
+ except OSError:
46
+ if not ignore_errors:
47
+ raise
48
+
49
+ _make_tree_user_writable(path)
50
+ shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
@@ -335,8 +335,6 @@ def _launch_config_args(session):
335
335
  args += ["--dangerously-skip-permissions"]
336
336
  return args
337
337
  if provider == PROVIDER_OLLAMA:
338
- if power:
339
- args += ["--think", power if power in ("low", "medium", "high") else "high"]
340
338
  if permission == "full":
341
339
  args += ["--experimental-yolo"]
342
340
  return args
@@ -424,6 +422,8 @@ def _logics_enabled(session, env=None):
424
422
 
425
423
 
426
424
  def _with_launch_preferences(session, initial_prompt=None, env=None):
425
+ if session["provider"] == PROVIDER_OLLAMA:
426
+ return initial_prompt
427
427
  prompts = []
428
428
  if _rtk_enabled(session):
429
429
  prompts.append(RTK_PROMPT)
package/src/repair.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import os
3
- import shutil
4
3
 
4
+ from .fs_utils import remove_tree
5
5
  from .health import collect_health_report
6
6
 
7
7
 
@@ -69,7 +69,7 @@ def _apply_recreate_state(service, name):
69
69
 
70
70
 
71
71
  def _apply_remove_path(path):
72
- shutil.rmtree(path)
72
+ remove_tree(path)
73
73
  return "applied"
74
74
 
75
75
 
@@ -13,6 +13,7 @@ from .backup_bundle import decode_bundle, encode_bundle
13
13
  from .config import PROVIDER_ANTIGRAVITY, PROVIDER_CLAUDE, PROVIDER_CODEX, PROVIDERS, get_cdx_home
14
14
  from .codex_usage import fetch_codex_rate_limits
15
15
  from .errors import CdxError
16
+ from .fs_utils import remove_tree
16
17
  from .session_store import create_session_store
17
18
  from .status_source import find_latest_status_artifact
18
19
 
@@ -641,7 +642,7 @@ def create_session_service(options=None):
641
642
  raise CdxError(f"Unknown session: {name}")
642
643
  if quarantine_root:
643
644
  try:
644
- shutil.rmtree(quarantine_root)
645
+ remove_tree(quarantine_root)
645
646
  except OSError as error:
646
647
  raise CdxError(
647
648
  f"Removed session {name}, but failed to delete archived profile {quarantine_root}: {error}"
@@ -701,14 +702,14 @@ def create_session_service(options=None):
701
702
  overwritten = bool(existing)
702
703
  except Exception:
703
704
  if moved_temp and os.path.exists(dest_root):
704
- shutil.rmtree(dest_root, ignore_errors=True)
705
+ remove_tree(dest_root, ignore_errors=True)
705
706
  if backup_root and os.path.exists(backup_root) and not os.path.exists(dest_root):
706
707
  os.rename(backup_root, dest_root)
707
708
  raise
708
709
  finally:
709
710
  if backup_root and os.path.exists(backup_root):
710
- shutil.rmtree(backup_root, ignore_errors=True)
711
- shutil.rmtree(temp_parent, ignore_errors=True)
711
+ remove_tree(backup_root, ignore_errors=True)
712
+ remove_tree(temp_parent, ignore_errors=True)
712
713
  if not result["ok"]:
713
714
  raise CdxError(f"Failed to create session: {dest_name}")
714
715
  return {"session": result["session"], "overwritten": overwritten}
@@ -7,7 +7,7 @@ import urllib.request
7
7
  from datetime import datetime, timezone
8
8
 
9
9
 
10
- UPDATE_CHECK_TTL_SECONDS = 12 * 60 * 60
10
+ UPDATE_CHECK_TTL_SECONDS = 60 * 60
11
11
  LATEST_RELEASE_URL = "https://api.github.com/repos/AlexAgo83/cdx-manager/releases/latest"
12
12
  LOGICS_MANAGER_LATEST_URL = "https://registry.npmjs.org/@grifhinz%2Flogics-manager/latest"
13
13