cdx-manager 0.12.3 → 0.12.4

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.12.3-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.12.4-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
 
@@ -373,7 +373,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
373
373
  | `cdx disk [profiles] [--candidates] [--json]` | Measure `CDX_HOME`; `profiles` includes per-profile breakdown, and `--candidates` reports reclaimable temporary caches and old logs with evidence |
374
374
  | `cdx reset <name> [--yes] [--json]` | Explicitly consume one available banked Codex rate-limit reset; confirmation is required unless `--yes` is supplied |
375
375
  | `cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR\|--passphrase-stdin] [--force] [--json]` | Export sessions to a portable bundle; `--include-auth` encrypts auth data with a passphrase |
376
- | `cdx import <file> [--sessions a,b] [--passphrase-env VAR\|--passphrase-stdin] [--force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
376
+ | `cdx import <file> [--sessions a,b] [--passphrase-env VAR\|--passphrase-stdin] [--force\|--merge] [--allow-authless-force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
377
377
  | `cdx doctor [--severity OK|WARN|FAIL[,OK|WARN|FAIL...]] [--json]` | Inspect CLI dependencies, provider CLI versions/capability hints, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines; optionally filter issue severities |
378
378
  | `cdx repair [--dry-run] [--force] [--json]` | Plan or apply safe repairs for missing state files, quarantines, and orphan profiles |
379
379
  | `cdx view [--json] [--lan] [--lan-rw] [--focus <ref>] [--read] [--port <port>] [--host <host>] [--refresh-interval <s>] [--tls] [--tls-cert <path>] [--tls-key <path>] [--open] [--no-open]` | Open the Logics browser/focus viewer by delegating to `logics-manager view`; all viewer flags are forwarded; JSON mode reports diagnostics without launching it |
@@ -527,10 +527,10 @@ cdx import backup-auth.cdx --passphrase-env CDX_BUNDLE_PASSPHRASE
527
527
 
528
528
  Notes:
529
529
 
530
- - `--include-auth` is encrypted, requires a passphrase, and exports only provider credential files rather than full profile caches or logs.
530
+ - `--include-auth` is encrypted, requires a passphrase, and exports only provider credential files rather than full profile caches, logs, or installed plugins.
531
531
  - Without `--passphrase-env`, `cdx` prompts in an interactive terminal. Non-interactive callers can pass `--passphrase-stdin` to read the passphrase from stdin (one line) instead of exposing it in the child environment.
532
532
  - `--sessions work,perso` exports or imports only a subset.
533
- - `--force` allows overwriting existing destination sessions during import or replacing an existing bundle file during export. Import refuses to overwrite existing sessions from a bundle without auth payloads unless `--allow-authless-force` is also passed.
533
+ - `--force` allows overwriting existing destination sessions during import or replacing an existing bundle file during export. Import preserves detected local `plugins/` state when the bundle does not provide it, and refuses to overwrite existing sessions from a bundle without auth payloads unless `--allow-authless-force` is also passed.
534
534
  - Auth bundles contain credentials. Treat them like secrets and delete them after transfer.
535
535
 
536
536
  ---
@@ -3,6 +3,7 @@
3
3
  ## Highlights
4
4
 
5
5
  - GitHub Releases now trigger an automatic checksum asset workflow so `release-archives.json` is regenerated and uploaded for the published tag.
6
+ - `cdx import --force` now preserves detected local Codex plugin state while refreshing sessions from auth-bearing bundles.
6
7
  - `cdx run` validation errors now explain what is wrong and how to list or create matching sessions.
7
8
  - Provider capacity or runtime failures now keep failed run records queryable instead of dropping them from the run registry.
8
9
 
@@ -12,6 +13,10 @@
12
13
 
13
14
  Publishing a GitHub Release now runs `.github/workflows/upload-release-checksums.yml`. The workflow checks out the release tag, regenerates `checksums/release-archives.json`, and uploads it as the `release-archives.json` release asset with replacement enabled for reruns.
14
15
 
16
+ ### Force import plugin preservation
17
+
18
+ Auth-bearing force imports now keep local `plugins/` state when the incoming bundle does not include plugin files, so refreshing credentials does not silently remove locally installed Codex plugins.
19
+
15
20
  ### More actionable run validation
16
21
 
17
22
  `cdx run` now returns specific guidance for unknown session references and invalid provider selections so users can recover with `cdx configs`, `cdx add`, or a corrected provider argument.
@@ -0,0 +1,18 @@
1
+ # CDX Manager 0.12.4
2
+
3
+ ## Highlights
4
+
5
+ - `cdx import --force` now preserves local Codex plugins when refreshing from bundles that do not include plugin files.
6
+
7
+ ## Changes
8
+
9
+ ### Force import plugin preservation
10
+
11
+ Force imports of auth-bearing bundles keep detected local `plugins/` state instead of deleting it when the incoming bundle omits plugin files. This keeps locally installed Codex plugins available after credential refresh imports.
12
+
13
+ ## Validation
14
+
15
+ - `npm run lint`
16
+ - `npm test`
17
+ - `logics-manager lint --require-status`
18
+ - `logics-manager audit`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
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.12.3"
7
+ version = "0.12.4"
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
@@ -78,7 +78,7 @@ from .status_view import (
78
78
  )
79
79
  from .update_check import check_for_update, check_logics_manager_for_update
80
80
 
81
- VERSION = "0.12.3"
81
+ VERSION = "0.12.4"
82
82
 
83
83
  # Public surface: this module is a facade. Names below are imported above
84
84
  # purely to be re-exported (consumed by tests and external callers); listing
@@ -215,7 +215,7 @@ def _print_help(use_color=False):
215
215
  f" {_style('cdx clean [name] [--yes] [--json]', '36', use_color)}",
216
216
  f" {_style('cdx clean profiles (--tmp|--old-logs DAYS) [--yes] [--json]', '36', use_color)}",
217
217
  f" {_style('cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR|--passphrase-stdin] [--force] [--json]', '36', use_color)}",
218
- f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR|--passphrase-stdin] [--force|--merge] [--json]', '36', use_color)}",
218
+ f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR|--passphrase-stdin] [--force|--merge] [--allow-authless-force] [--json]', '36', use_color)}",
219
219
  f" {_style('cdx doctor [--severity OK|WARN|FAIL[,OK|WARN|FAIL...]] [--json]', '36', use_color)}",
220
220
  f" {_style('cdx repair [--dry-run] [--force] [--json]', '36', use_color)}",
221
221
  f" {_style('cdx view [--json]', '36', use_color)}",
@@ -78,6 +78,7 @@ CLAUDE_STATUS_CACHE_TTL_SECONDS = 10 * 60
78
78
  # Each live Codex probe spawns app-server, which can refresh+rotate the OAuth
79
79
  # token. Cache longer so routine status calls don't keep triggering refreshes.
80
80
  CODEX_STATUS_CACHE_TTL_SECONDS = 5 * 60
81
+ _FORCE_IMPORT_PRESERVED_PROFILE_PATHS = ("plugins",)
81
82
  STATUS_PROBE_TIMEOUT_SECONDS = 5
82
83
  MAX_STATUS_WORKERS = 8
83
84
  LAUNCH_POWER_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
@@ -1427,6 +1428,26 @@ def create_session_service(options=None):
1427
1428
  else:
1428
1429
  store["remove_session"](name)
1429
1430
 
1431
+ def _force_import_preserved_profile_paths(session_root):
1432
+ return [rel_path for rel_path in _FORCE_IMPORT_PRESERVED_PROFILE_PATHS if os.path.exists(os.path.join(session_root, rel_path))]
1433
+
1434
+ def _restore_force_import_profile_paths(name, backup_root, session_root, rel_paths):
1435
+ if not backup_root:
1436
+ return
1437
+ for rel_path in rel_paths:
1438
+ source_path = os.path.join(backup_root, rel_path)
1439
+ dest_path = os.path.join(session_root, rel_path)
1440
+ if not os.path.exists(source_path) or os.path.exists(dest_path):
1441
+ continue
1442
+ try:
1443
+ if os.path.isdir(source_path) and not os.path.islink(source_path):
1444
+ shutil.copytree(source_path, dest_path)
1445
+ else:
1446
+ _ensure_private_dir(os.path.dirname(dest_path))
1447
+ shutil.copy2(source_path, dest_path)
1448
+ except Exception as error:
1449
+ raise CdxError(f"Could not restore local plugin state for session {name}: {rel_path}") from error
1450
+
1430
1451
  def import_bundle(
1431
1452
  file_path,
1432
1453
  passphrase=None,
@@ -1479,9 +1500,11 @@ def create_session_service(options=None):
1479
1500
  backup_root = None
1480
1501
  old_record = None
1481
1502
  old_state = None
1503
+ preserved_profile_paths = []
1482
1504
  if is_existing and force:
1483
1505
  old_record = store["get_session"](name)
1484
1506
  old_state = store["read_session_state"](name)
1507
+ preserved_profile_paths = _force_import_preserved_profile_paths(session_root)
1485
1508
  if os.path.exists(session_root):
1486
1509
  backup_root = tempfile.mkdtemp(prefix=f".{_encode(name)}.import.", dir=os.path.dirname(session_root))
1487
1510
  os.rmdir(backup_root)
@@ -1535,6 +1558,7 @@ def create_session_service(options=None):
1535
1558
  _ensure_private_dir(os.path.dirname(dest_path))
1536
1559
  # Decrypted credentials: 0o600 from creation, no umask window.
1537
1560
  atomic_write(dest_path, item["content"], mode=0o600)
1561
+ _restore_force_import_profile_paths(name, backup_root, session_root, preserved_profile_paths)
1538
1562
  except Exception:
1539
1563
  _restore_import_backup(name, backup_root, session_root, old_record, old_state)
1540
1564
  raise