cdx-manager 0.12.2 → 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 +7 -5
- package/changelogs/CHANGELOGS_0_12_3.md +34 -0
- package/changelogs/CHANGELOGS_0_12_4.md +18 -0
- package/checksums/release-archives.json +4 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +2 -2
- package/src/cli_args.py +34 -10
- package/src/run_command.py +32 -1
- package/src/session_service.py +24 -0
- package/src/update_manager.py +2 -2
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
|
|
|
@@ -204,7 +204,9 @@ Release maintainer note:
|
|
|
204
204
|
- Before publishing npm or PyPI packages, run `npm run release:validate`.
|
|
205
205
|
- The release tag must match `package.json`, `pyproject.toml`, `src/cli.py`, and `VERSION`.
|
|
206
206
|
- `checksums/release-archives.json` must include the matching `vX.Y.Z` entry with both `github_tarball_sha256` and `github_zip_sha256`.
|
|
207
|
-
-
|
|
207
|
+
- Publishing a GitHub Release runs the checksum upload workflow: it regenerates `checksums/release-archives.json` for the release tag and uploads it as the `release-archives.json` asset with replacement enabled for safe reruns.
|
|
208
|
+
- If the asset is missing or stale, repair it with `python3 scripts/update_release_checksums.py --tag vX.Y.Z`, then `gh release upload vX.Y.Z checksums/release-archives.json --clobber`.
|
|
209
|
+
- Publish packages only after the checksum workflow succeeds and `npm run release:validate`, `npm run lint`, and `npm test` pass.
|
|
208
210
|
|
|
209
211
|
### Environment
|
|
210
212
|
|
|
@@ -371,7 +373,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
371
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 |
|
|
372
374
|
| `cdx reset <name> [--yes] [--json]` | Explicitly consume one available banked Codex rate-limit reset; confirmation is required unless `--yes` is supplied |
|
|
373
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 |
|
|
374
|
-
| `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` |
|
|
375
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 |
|
|
376
378
|
| `cdx repair [--dry-run] [--force] [--json]` | Plan or apply safe repairs for missing state files, quarantines, and orphan profiles |
|
|
377
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 |
|
|
@@ -525,10 +527,10 @@ cdx import backup-auth.cdx --passphrase-env CDX_BUNDLE_PASSPHRASE
|
|
|
525
527
|
|
|
526
528
|
Notes:
|
|
527
529
|
|
|
528
|
-
- `--include-auth` is encrypted, requires a passphrase, and exports only provider credential files rather than full profile caches or
|
|
530
|
+
- `--include-auth` is encrypted, requires a passphrase, and exports only provider credential files rather than full profile caches, logs, or installed plugins.
|
|
529
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.
|
|
530
532
|
- `--sessions work,perso` exports or imports only a subset.
|
|
531
|
-
- `--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.
|
|
532
534
|
- Auth bundles contain credentials. Treat them like secrets and delete them after transfer.
|
|
533
535
|
|
|
534
536
|
---
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# CDX Manager 0.12.3
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
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.
|
|
7
|
+
- `cdx run` validation errors now explain what is wrong and how to list or create matching sessions.
|
|
8
|
+
- Provider capacity or runtime failures now keep failed run records queryable instead of dropping them from the run registry.
|
|
9
|
+
|
|
10
|
+
## Changes
|
|
11
|
+
|
|
12
|
+
### Automated release checksum asset
|
|
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.
|
|
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
|
+
|
|
20
|
+
### More actionable run validation
|
|
21
|
+
|
|
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.
|
|
23
|
+
|
|
24
|
+
### Queryable failed provider runs
|
|
25
|
+
|
|
26
|
+
Provider run failures are now written to the run registry with failure metadata, preserving enough detail for later inspection even when the provider never starts successfully.
|
|
27
|
+
|
|
28
|
+
## Validation
|
|
29
|
+
|
|
30
|
+
- `npm run release:validate`
|
|
31
|
+
- `npm run lint`
|
|
32
|
+
- `npm test`
|
|
33
|
+
- `logics-manager lint --require-status`
|
|
34
|
+
- `logics-manager audit`
|
|
@@ -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`
|
|
@@ -156,6 +156,10 @@
|
|
|
156
156
|
"v0.12.1": {
|
|
157
157
|
"github_tarball_sha256": "87e3b11886a8e7fb10233d425f6a1986e62395c89049947678a50cfb20384278",
|
|
158
158
|
"github_zip_sha256": "249f75eb240de7cc421f17579e0040d35eb06e3213568673f743c92191dda0f0"
|
|
159
|
+
},
|
|
160
|
+
"v0.12.2": {
|
|
161
|
+
"github_tarball_sha256": "5214f9f85f6bdc2c53bded938c91e28411de3c573bbcff81ebecb20dea6d99c2",
|
|
162
|
+
"github_zip_sha256": "8f447cdea9e921f7428c1c72db5a19f40f447a877bb2bc8dc1c8bd5e34daf070"
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
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.
|
|
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)}",
|
package/src/cli_args.py
CHANGED
|
@@ -35,6 +35,13 @@ CAN_RESUME_USAGE = "Usage: cdx can-resume <name> [--json]"
|
|
|
35
35
|
SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json"
|
|
36
36
|
NEXT_USAGE = "Usage: cdx next [--json] [--refresh]"
|
|
37
37
|
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json"
|
|
38
|
+
RUN_JSON_REQUIRED = "cdx run: --json is required."
|
|
39
|
+
RUN_TARGET_REQUIRED = "cdx run: specify a session name or --provider PROVIDER."
|
|
40
|
+
RUN_SESSION_PROVIDER_CONFLICT = "cdx run: cannot specify both a session name and --provider."
|
|
41
|
+
RUN_CWD_REQUIRED = "cdx run: --cwd PATH is required."
|
|
42
|
+
RUN_PROMPT_SOURCE_REQUIRED = "cdx run: specify exactly one prompt source: --prompt TEXT or --prompt-file PATH."
|
|
43
|
+
RUN_KIND_VALUES = ("assistant", "code-review")
|
|
44
|
+
RUN_PERMISSION_VALUES = ("review", "default", "auto", "full", "workspace-write", "read-only", "danger-full-access")
|
|
38
45
|
RUNS_USAGE = "Usage: cdx runs [--limit N] --json"
|
|
39
46
|
RUN_STATUS_USAGE = "Usage: cdx run-status <run_id> --json"
|
|
40
47
|
RUN_REPORT_USAGE = "Usage: cdx run-report <run_id> --json"
|
|
@@ -327,6 +334,21 @@ def _parse_timeout_seconds(value, usage=RUN_USAGE):
|
|
|
327
334
|
return parsed
|
|
328
335
|
|
|
329
336
|
|
|
337
|
+
def _parse_run_timeout_seconds(value):
|
|
338
|
+
try:
|
|
339
|
+
return _parse_timeout_seconds(value, RUN_USAGE)
|
|
340
|
+
except CdxError as error:
|
|
341
|
+
raise CdxError(f"cdx run: --timeout-seconds must be a positive number; got {value!r}.") from error
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _parse_run_provider(value):
|
|
345
|
+
try:
|
|
346
|
+
return _parse_provider_filter(value, RUN_USAGE)
|
|
347
|
+
except CdxError as error:
|
|
348
|
+
allowed = "|".join(PROVIDERS)
|
|
349
|
+
raise CdxError(f"cdx run: invalid --provider {value!r}; allowed values: {allowed}.") from error
|
|
350
|
+
|
|
351
|
+
|
|
330
352
|
def _normalize_run_permission(value):
|
|
331
353
|
if value is None:
|
|
332
354
|
return None
|
|
@@ -338,7 +360,8 @@ def _normalize_run_permission(value):
|
|
|
338
360
|
}
|
|
339
361
|
text = aliases.get(text, text)
|
|
340
362
|
if text not in ("review", "default", "auto", "full"):
|
|
341
|
-
|
|
363
|
+
allowed = "|".join(RUN_PERMISSION_VALUES)
|
|
364
|
+
raise CdxError(f"cdx run: invalid --permission {value!r}; allowed values: {allowed}.")
|
|
342
365
|
return text
|
|
343
366
|
|
|
344
367
|
|
|
@@ -347,28 +370,29 @@ def _parse_run_args(args):
|
|
|
347
370
|
"--cwd": {"key": "cwd", "type": "str", "default": None},
|
|
348
371
|
"--prompt-file": {"key": "prompt_file", "type": "str", "default": None},
|
|
349
372
|
"--prompt": {"key": "prompt", "type": "str", "default": None},
|
|
350
|
-
"--provider": {"key": "provider", "type": "str", "default": None, "transform":
|
|
373
|
+
"--provider": {"key": "provider", "type": "str", "default": None, "transform": _parse_run_provider},
|
|
351
374
|
"--model": {"key": "model", "type": "str", "default": None},
|
|
352
375
|
"--kind": {"key": "kind", "type": "str", "default": "assistant"},
|
|
353
376
|
"--reasoning-effort": {"key": "reasoning_effort", "type": "str", "default": None},
|
|
354
377
|
"--power": {"key": "power", "type": "str", "default": None},
|
|
355
378
|
"--permission": {"key": "permission", "type": "str", "default": None, "transform": _normalize_run_permission},
|
|
356
|
-
"--timeout-seconds": {"key": "timeout_seconds", "type": "str", "default": None, "transform":
|
|
379
|
+
"--timeout-seconds": {"key": "timeout_seconds", "type": "str", "default": None, "transform": _parse_run_timeout_seconds},
|
|
357
380
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
358
381
|
}, RUN_USAGE, positionals_key="names", max_positionals=1)
|
|
359
382
|
if not parsed["json"]:
|
|
360
|
-
raise CdxError(
|
|
383
|
+
raise CdxError(RUN_JSON_REQUIRED)
|
|
361
384
|
name = parsed["names"][0] if parsed["names"] else None
|
|
362
385
|
if name and parsed["provider"]:
|
|
363
|
-
raise CdxError(
|
|
386
|
+
raise CdxError(RUN_SESSION_PROVIDER_CONFLICT)
|
|
364
387
|
if not name and not parsed["provider"]:
|
|
365
|
-
raise CdxError(
|
|
388
|
+
raise CdxError(RUN_TARGET_REQUIRED)
|
|
366
389
|
if not parsed["cwd"]:
|
|
367
|
-
raise CdxError(
|
|
390
|
+
raise CdxError(RUN_CWD_REQUIRED)
|
|
368
391
|
if bool(parsed["prompt_file"]) == bool(parsed["prompt"]):
|
|
369
|
-
raise CdxError(
|
|
370
|
-
if parsed["kind"] not in
|
|
371
|
-
|
|
392
|
+
raise CdxError(RUN_PROMPT_SOURCE_REQUIRED)
|
|
393
|
+
if parsed["kind"] not in RUN_KIND_VALUES:
|
|
394
|
+
allowed = "|".join(RUN_KIND_VALUES)
|
|
395
|
+
raise CdxError(f"cdx run: invalid --kind {parsed['kind']!r}; allowed values: {allowed}.")
|
|
372
396
|
effort = _normalize_reasoning_effort(
|
|
373
397
|
reasoning_effort=parsed["reasoning_effort"],
|
|
374
398
|
power=parsed["power"],
|
package/src/run_command.py
CHANGED
|
@@ -18,6 +18,8 @@ def run_cdx_error_code(error):
|
|
|
18
18
|
message = str(error)
|
|
19
19
|
if message.startswith("Usage:"):
|
|
20
20
|
return "invalid_request"
|
|
21
|
+
if message.startswith("cdx run:"):
|
|
22
|
+
return "invalid_request"
|
|
21
23
|
if message.startswith("Invalid cwd:"):
|
|
22
24
|
return "invalid_cwd"
|
|
23
25
|
if message.startswith("Session is disabled:"):
|
|
@@ -47,6 +49,30 @@ def run_payload_reasoning_effort(parsed, session):
|
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
|
|
52
|
+
def _tail_line(path, limit=4000):
|
|
53
|
+
if not path:
|
|
54
|
+
return None
|
|
55
|
+
try:
|
|
56
|
+
size = os.path.getsize(path)
|
|
57
|
+
with open(path, "rb") as handle:
|
|
58
|
+
handle.seek(max(0, size - limit))
|
|
59
|
+
text = handle.read().decode("utf-8", errors="replace")
|
|
60
|
+
except OSError:
|
|
61
|
+
return None
|
|
62
|
+
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
63
|
+
if not lines:
|
|
64
|
+
return None
|
|
65
|
+
return lines[-1][:500]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _provider_failure_message(error, run_info, error_code):
|
|
69
|
+
base = str(error) if error else "Run failed."
|
|
70
|
+
if error_code != "provider_failed":
|
|
71
|
+
return base
|
|
72
|
+
detail = _tail_line(run_info.get("stderr_path")) or _tail_line(run_info.get("stdout_path"))
|
|
73
|
+
return f"{base}: {detail}" if detail else base
|
|
74
|
+
|
|
75
|
+
|
|
50
76
|
def run_result_payload(api_schema_version, ok, parsed, session, run_info=None,
|
|
51
77
|
error=None, error_source=None, error_code=None):
|
|
52
78
|
run_info = run_info or {}
|
|
@@ -55,6 +81,11 @@ def run_result_payload(api_schema_version, ok, parsed, session, run_info=None,
|
|
|
55
81
|
if session else
|
|
56
82
|
empty_usage()
|
|
57
83
|
)
|
|
84
|
+
message = None if ok else (
|
|
85
|
+
_provider_failure_message(error, run_info, error_code)
|
|
86
|
+
if error_source == "provider"
|
|
87
|
+
else (str(error) if error else "Run failed.")
|
|
88
|
+
)
|
|
58
89
|
return {
|
|
59
90
|
"schema_version": api_schema_version,
|
|
60
91
|
"ok": bool(ok),
|
|
@@ -77,7 +108,7 @@ def run_result_payload(api_schema_version, ok, parsed, session, run_info=None,
|
|
|
77
108
|
"error": None if ok else {
|
|
78
109
|
"source": error_source or "cdx",
|
|
79
110
|
"code": error_code or "cdx_error",
|
|
80
|
-
"message":
|
|
111
|
+
"message": message,
|
|
81
112
|
"provider_code": run_info.get("returncode") if error_source == "provider" else None,
|
|
82
113
|
},
|
|
83
114
|
}
|
package/src/session_service.py
CHANGED
|
@@ -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
|
package/src/update_manager.py
CHANGED
|
@@ -63,10 +63,10 @@ def detect_installation(package_root=None, prefix=None, base_prefix=None):
|
|
|
63
63
|
return {"mode": "standalone", "package_root": str(root)}
|
|
64
64
|
if _is_source_checkout(root):
|
|
65
65
|
return {"mode": "source", "package_root": str(root)}
|
|
66
|
-
if _is_python_env(prefix=prefix, base_prefix=base_prefix):
|
|
67
|
-
return {"mode": "python", "package_root": str(root)}
|
|
68
66
|
if (root / "package.json").exists():
|
|
69
67
|
return {"mode": "npm", "package_root": str(root)}
|
|
68
|
+
if _is_python_env(prefix=prefix, base_prefix=base_prefix):
|
|
69
|
+
return {"mode": "python", "package_root": str(root)}
|
|
70
70
|
return {"mode": "unknown", "package_root": str(root)}
|
|
71
71
|
|
|
72
72
|
|