cdx-manager 0.9.0 → 0.9.2
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 +3 -3
- package/changelogs/CHANGELOGS_0_9_1.md +36 -0
- package/changelogs/CHANGELOGS_0_9_2.md +33 -0
- package/checksums/release-archives.json +8 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +2 -2
- package/src/cli_commands.py +5 -1
- package/src/cli_view.py +33 -7
- package/src/logics_view.py +9 -7
- package/src/provider_runtime.py +1 -1
- package/src/session_service.py +38 -12
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
|
|
|
@@ -42,7 +42,7 @@ One command to launch any session. Zero auth juggling.
|
|
|
42
42
|
- **Persistent launch settings.** Pin per-session power, permission, and fast-mode preferences once; `cdx` reapplies them on every launch until you unset them.
|
|
43
43
|
- **Launch history.** Inspect recent launches with provider, result, duration, working directory, launch settings, and transcript path.
|
|
44
44
|
- **Update prompts.** Periodic update checks surface `cdx update` directly in the `cdx`, `cdx status`, and launch output when a newer release is available. When `logics-manager` is installed, `cdx` can also suggest `logics-manager self-update`.
|
|
45
|
-
- **Logics viewer shortcut.** `cdx view` opens the Logics browser/focus viewer through `logics-manager view` when the companion CLI is installed
|
|
45
|
+
- **Logics viewer shortcut.** `cdx view` opens the Logics browser/focus viewer through `logics-manager view` when the companion CLI is installed. All viewer flags are forwarded: `--lan`, `--lan-rw`, `--focus <ref>`, `--read`, `--port`, `--host`, `--refresh-interval`, `--tls`, `--tls-cert`, `--tls-key`, `--open`, `--no-open`. `cdx view --json` reports availability and update diagnostics without opening the viewer.
|
|
46
46
|
- **Shared handoff context.** Keep a per-workspace Markdown context, or build one from a source session transcript, and install it into another assistant session before switching providers or accounts.
|
|
47
47
|
- **Passive status resolution.** Codex status is read from the local Codex app-server rate-limit API when available, with legacy transcript/history parsing kept as a fallback.
|
|
48
48
|
- **Session transcript capture.** Every launch is recorded to a local log file via `script`, giving you a full terminal transcript for each session.
|
|
@@ -352,7 +352,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
352
352
|
| `cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
|
|
353
353
|
| `cdx doctor [--json]` | Inspect CLI dependencies, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines |
|
|
354
354
|
| `cdx repair [--dry-run] [--force] [--json]` | Plan or apply safe repairs for missing state files, quarantines, and orphan profiles |
|
|
355
|
-
| `cdx view [--json]` | Open the Logics browser/focus viewer by delegating to `logics-manager view`; JSON mode reports diagnostics without launching it |
|
|
355
|
+
| `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 |
|
|
356
356
|
| `cdx update [--check] [--yes] [--json] [--version TAG]` | Update cdx-manager using the installer that matches how it was installed |
|
|
357
357
|
| `cdx ready [--refresh] [--json]` | Schedule an OS notification for the next cooling-down assistant that becomes ready, then return immediately |
|
|
358
358
|
| `cdx notify <name> --at-reset [--poll seconds] [--once] [--schedule] [--refresh] [--json]` | Wait for a session reset time or schedule an OS wake-up notification when due |
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# CDX Manager 0.9.1
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- `cdx view` now forwards all `logics-manager view` flags instead of rejecting them.
|
|
6
|
+
- Fixes a silent PATH resolution bug that could prevent `logics-manager` from being found.
|
|
7
|
+
|
|
8
|
+
## Changes
|
|
9
|
+
|
|
10
|
+
### cdx view: full flag passthrough
|
|
11
|
+
|
|
12
|
+
`cdx view` previously accepted only `--json` and raised a usage error for every other argument. All `logics-manager view` flags are now supported and forwarded transparently:
|
|
13
|
+
|
|
14
|
+
- `--lan` / `--lan-rw` — expose the viewer on the local network
|
|
15
|
+
- `--focus <ref>` / `--read` — open the viewer centered on a workflow document
|
|
16
|
+
- `--port <port>` / `--host <host>` — bind to a custom address
|
|
17
|
+
- `--refresh-interval <s>` — override the auto-refresh interval
|
|
18
|
+
- `--tls` / `--tls-cert <path>` / `--tls-key <path>` — serve over HTTPS
|
|
19
|
+
- `--open` / `--no-open` — control browser auto-open
|
|
20
|
+
|
|
21
|
+
### Bug fixes
|
|
22
|
+
|
|
23
|
+
- `resolve_logics_manager`: `shutil.which` was called with `path=""` when `env` had no `PATH` key, causing it to search nothing and always return `None`. Now falls back to the process PATH.
|
|
24
|
+
- `run_logics_viewer`: the viewer subprocess now inherits the full OS environment (`os.environ` merged with any `env` overrides) instead of receiving a partial dict that stripped all inherited variables.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
|
|
28
|
+
- README command table and feature description updated to list all forwarded viewer flags.
|
|
29
|
+
- `LOGICS_PROMPT` updated to reference `cdx view` instead of `logics-manager view` directly.
|
|
30
|
+
|
|
31
|
+
## Validation
|
|
32
|
+
|
|
33
|
+
- `npm run prepublishOnly`
|
|
34
|
+
- `npm pack --dry-run`
|
|
35
|
+
- `logics-manager lint --require-status`
|
|
36
|
+
- `logics-manager audit --legacy-cutoff-version 1.1.0 --group-by-doc`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CDX Manager 0.9.2
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- `cdx import` gains a `--merge` flag to fill in missing data without overwriting what already exists locally.
|
|
6
|
+
- npm and PyPI publication workflows now verify that the release tag's commit has a successful `CI` run before any registry upload step.
|
|
7
|
+
|
|
8
|
+
## Changes
|
|
9
|
+
|
|
10
|
+
### cdx import: `--merge` mode
|
|
11
|
+
|
|
12
|
+
`cdx import` previously offered two stances on existing sessions: reject the conflict (default) or erase and replace (`--force`). A third mode is now available:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
cdx import backup.cdx --merge
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With `--merge`, for each session that already exists locally:
|
|
19
|
+
|
|
20
|
+
- **Session fields** — existing values are kept; fields absent locally are pulled in from the bundle.
|
|
21
|
+
- **Session state** — same merge rule: local data wins, bundle fills the gaps.
|
|
22
|
+
- **Profile files** (auth.json, credentials, etc.) — files that already exist on disk are left untouched; files missing locally are restored from the bundle.
|
|
23
|
+
- **New sessions** in the bundle that have no local counterpart are imported normally.
|
|
24
|
+
|
|
25
|
+
`--force` and `--merge` are mutually exclusive and raise a clear error if combined.
|
|
26
|
+
|
|
27
|
+
## Validation
|
|
28
|
+
|
|
29
|
+
- `npm run prepublishOnly`
|
|
30
|
+
- `npm pack --dry-run`
|
|
31
|
+
- `python -m unittest discover -s test -p 'test_release_ci_py.py'`
|
|
32
|
+
- `logics-manager lint --require-status`
|
|
33
|
+
- `logics-manager audit --legacy-cutoff-version 1.1.0 --group-by-doc`
|
|
@@ -64,6 +64,14 @@
|
|
|
64
64
|
"v0.8.0": {
|
|
65
65
|
"github_tarball_sha256": "62cd461f9e22636bb85d801e21209b1762859b6662623e74c01fd40c107ea29a",
|
|
66
66
|
"github_zip_sha256": "4674c8b61110f52b99c76312e9962202a2e8fc6928327f8927781f45eeaa980f"
|
|
67
|
+
},
|
|
68
|
+
"v0.9.0": {
|
|
69
|
+
"github_tarball_sha256": "809af5746e1287f4c5dc7a2fb7583e67e212aa250c45be905c32374e5dee26d6",
|
|
70
|
+
"github_zip_sha256": "9118f81645c00a4a660923a1ae290f48bd458af7ebfcf9f6580a3b91023b7c76"
|
|
71
|
+
},
|
|
72
|
+
"v0.9.1": {
|
|
73
|
+
"github_tarball_sha256": "8cbab620c823aeaf56e72c1a4d20e251a1c7142bfeeb3d516321d59c2c0a621d",
|
|
74
|
+
"github_zip_sha256": "253909ede6dca61937835bd4ee29145ddacaa08fbdfba1b39810da73ae2ccc84"
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -64,7 +64,7 @@ from .status_view import (
|
|
|
64
64
|
)
|
|
65
65
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
66
66
|
|
|
67
|
-
VERSION = "0.9.
|
|
67
|
+
VERSION = "0.9.2"
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
# ---------------------------------------------------------------------------
|
|
@@ -108,7 +108,7 @@ def _print_help(use_color=False):
|
|
|
108
108
|
f" {_style('cdx rmv <name> [--force] [--json]', '36', use_color)}",
|
|
109
109
|
f" {_style('cdx clean [name] [--json]', '36', use_color)}",
|
|
110
110
|
f" {_style('cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR] [--force] [--json]', '36', use_color)}",
|
|
111
|
-
f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]', '36', use_color)}",
|
|
111
|
+
f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force|--merge] [--json]', '36', use_color)}",
|
|
112
112
|
f" {_style('cdx doctor [--json]', '36', use_color)}",
|
|
113
113
|
f" {_style('cdx repair [--dry-run] [--force] [--json]', '36', use_color)}",
|
|
114
114
|
f" {_style('cdx view [--json]', '36', use_color)}",
|
package/src/cli_commands.py
CHANGED
|
@@ -55,7 +55,7 @@ DOCTOR_USAGE = "Usage: cdx doctor [--json]"
|
|
|
55
55
|
REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
|
|
56
56
|
UPDATE_USAGE = "Usage: cdx update [--check] [--yes] [--json] [--version TAG]"
|
|
57
57
|
EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
58
|
-
IMPORT_USAGE = "Usage: cdx import <file> [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
58
|
+
IMPORT_USAGE = "Usage: cdx import <file> [--force|--merge] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
59
59
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
60
60
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
61
61
|
SET_USAGE = "Usage: cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power low|medium|high|xhigh|max] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--priority 0..100] [--json]"
|
|
@@ -1000,6 +1000,7 @@ def _parse_export_args(args):
|
|
|
1000
1000
|
def _parse_import_args(args):
|
|
1001
1001
|
parsed = _parse_flag_args(args, {
|
|
1002
1002
|
"--force": {"key": "force", "type": "bool", "default": False},
|
|
1003
|
+
"--merge": {"key": "merge", "type": "bool", "default": False},
|
|
1003
1004
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
1004
1005
|
"--sessions": {
|
|
1005
1006
|
"key": "session_names",
|
|
@@ -1012,6 +1013,8 @@ def _parse_import_args(args):
|
|
|
1012
1013
|
parsed["file_path"] = parsed.pop("positionals")[0] if parsed["positionals"] else None
|
|
1013
1014
|
if not parsed["file_path"]:
|
|
1014
1015
|
raise CdxError(IMPORT_USAGE)
|
|
1016
|
+
if parsed["force"] and parsed["merge"]:
|
|
1017
|
+
raise CdxError("--force and --merge are mutually exclusive.")
|
|
1015
1018
|
return parsed
|
|
1016
1019
|
|
|
1017
1020
|
|
|
@@ -2643,6 +2646,7 @@ def handle_import(rest, ctx):
|
|
|
2643
2646
|
passphrase=passphrase,
|
|
2644
2647
|
session_names=parsed["session_names"],
|
|
2645
2648
|
force=parsed["force"],
|
|
2649
|
+
merge=parsed["merge"],
|
|
2646
2650
|
)
|
|
2647
2651
|
session_count = len(result["session_names"])
|
|
2648
2652
|
auth_suffix = " with auth" if result["include_auth"] else ""
|
package/src/cli_view.py
CHANGED
|
@@ -12,7 +12,7 @@ from .logics_view import (
|
|
|
12
12
|
from .update_check import check_logics_manager_for_update
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
VIEW_USAGE = "Usage: cdx view [--json]"
|
|
15
|
+
VIEW_USAGE = "Usage: cdx view [--json] [--lan] [--lan-rw] [--focus <ref>] [--read] [--port <port>] [--host <host>] [--refresh-interval <s>] [--tls] [--open] [--no-open]"
|
|
16
16
|
API_SCHEMA_VERSION = 1
|
|
17
17
|
|
|
18
18
|
|
|
@@ -67,18 +67,44 @@ def _logics_manager_update_notice(ctx, env):
|
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
_VIEWER_FLAGS = {
|
|
71
|
+
"--lan", "--lan-rw", "--tls", "--read", "--open", "--no-open",
|
|
72
|
+
}
|
|
73
|
+
_VIEWER_VALUE_FLAGS = {
|
|
74
|
+
"--host", "--port", "--tls-cert", "--tls-key", "--refresh-interval", "--focus",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _parse_view_args(rest):
|
|
79
|
+
json_flag = False
|
|
80
|
+
viewer_args = []
|
|
81
|
+
i = 0
|
|
82
|
+
while i < len(rest):
|
|
83
|
+
arg = rest[i]
|
|
84
|
+
if arg == "--json":
|
|
85
|
+
json_flag = True
|
|
86
|
+
elif arg in _VIEWER_FLAGS:
|
|
87
|
+
viewer_args.append(arg)
|
|
88
|
+
elif arg in _VIEWER_VALUE_FLAGS:
|
|
89
|
+
if i + 1 >= len(rest):
|
|
90
|
+
raise CdxError(f"{arg} requires a value. {VIEW_USAGE}")
|
|
91
|
+
viewer_args.extend([arg, rest[i + 1]])
|
|
92
|
+
i += 1
|
|
93
|
+
else:
|
|
94
|
+
raise CdxError(f"Unknown option: {arg}\n{VIEW_USAGE}")
|
|
95
|
+
i += 1
|
|
96
|
+
return json_flag, viewer_args
|
|
97
|
+
|
|
98
|
+
|
|
70
99
|
def handle_view(rest, ctx):
|
|
71
|
-
json_flag =
|
|
72
|
-
unknown = [arg for arg in rest if arg != "--json"]
|
|
73
|
-
if unknown:
|
|
74
|
-
raise CdxError(VIEW_USAGE)
|
|
100
|
+
json_flag, viewer_args = _parse_view_args(rest)
|
|
75
101
|
|
|
76
102
|
env = ctx.get("env")
|
|
77
103
|
cwd = ctx.get("cwd")
|
|
78
104
|
executable = resolve_logics_manager(env=env)
|
|
79
105
|
update_notice = _logics_manager_update_notice(ctx, env) if executable else None
|
|
80
106
|
failure = None if executable else missing_logics_manager_failure()
|
|
81
|
-
diagnostics = build_viewer_diagnostics(executable, cwd, update_notice=update_notice, failure=failure)
|
|
107
|
+
diagnostics = build_viewer_diagnostics(executable, cwd, update_notice=update_notice, failure=failure, extra_args=viewer_args)
|
|
82
108
|
warnings = _update_notice_warnings([update_notice])
|
|
83
109
|
|
|
84
110
|
if json_flag:
|
|
@@ -92,7 +118,7 @@ def handle_view(rest, ctx):
|
|
|
92
118
|
ctx["out"](f"{_warn(warning['message'], ctx['use_color'])}\n")
|
|
93
119
|
|
|
94
120
|
try:
|
|
95
|
-
result = run_logics_viewer(executable, cwd, env=env, runner=ctx.get("spawn_sync"))
|
|
121
|
+
result = run_logics_viewer(executable, cwd, env=env, extra_args=viewer_args, runner=ctx.get("spawn_sync"))
|
|
96
122
|
except FileNotFoundError as error:
|
|
97
123
|
raise CdxError(f"logics-manager is required for cdx view. {LOGICS_MANAGER_INSTALL_HINT}") from error
|
|
98
124
|
except KeyboardInterrupt:
|
package/src/logics_view.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import shutil
|
|
2
3
|
import subprocess
|
|
3
4
|
|
|
@@ -7,11 +8,11 @@ LOGICS_MANAGER_INSTALL_HINT = "Install or update it with: npm install -g @grifhi
|
|
|
7
8
|
|
|
8
9
|
def resolve_logics_manager(env=None):
|
|
9
10
|
env = env or {}
|
|
10
|
-
return shutil.which("logics-manager", path=env.get("PATH"
|
|
11
|
+
return shutil.which("logics-manager", path=env.get("PATH") or None)
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
def build_viewer_diagnostics(executable, cwd, update_notice=None, failure=None):
|
|
14
|
-
command = [executable or "logics-manager", "view"]
|
|
14
|
+
def build_viewer_diagnostics(executable, cwd, update_notice=None, failure=None, extra_args=None):
|
|
15
|
+
command = [executable or "logics-manager", "view"] + (extra_args or [])
|
|
15
16
|
return {
|
|
16
17
|
"available": bool(executable),
|
|
17
18
|
"executable": executable,
|
|
@@ -29,9 +30,10 @@ def missing_logics_manager_failure():
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def run_logics_viewer(executable, cwd, env=None, runner=None):
|
|
33
|
+
def run_logics_viewer(executable, cwd, env=None, extra_args=None, runner=None):
|
|
33
34
|
runner = runner or subprocess.run
|
|
34
|
-
argv = [executable, "view"]
|
|
35
|
+
argv = [executable, "view"] + (extra_args or [])
|
|
36
|
+
merged_env = {**os.environ, **(env or {})}
|
|
35
37
|
if runner is subprocess.run:
|
|
36
|
-
return subprocess.run(argv, cwd=cwd, env=
|
|
37
|
-
return runner(argv, cwd=cwd, env=
|
|
38
|
+
return subprocess.run(argv, cwd=cwd, env=merged_env)
|
|
39
|
+
return runner(argv, cwd=cwd, env=merged_env)
|
package/src/provider_runtime.py
CHANGED
|
@@ -22,7 +22,7 @@ RTK_PROMPT = (
|
|
|
22
22
|
LOGICS_PROMPT = (
|
|
23
23
|
"When `logics-manager` is available, prefer it for Logics workflow operations: use "
|
|
24
24
|
"`logics-manager status`, `health`, `audit`, and `lint` for workflow state and validation; "
|
|
25
|
-
"`
|
|
25
|
+
"`cdx view` for the browser viewer and focus workflows (supports `--lan`, `--focus <ref>`, `--port`, and all other viewer flags); "
|
|
26
26
|
"`logics-manager sync read-doc|list-docs|search-docs|context-pack` for bounded document context; "
|
|
27
27
|
"`logics-manager flow ...` for request/backlog/task lifecycle changes; and `logics-manager mcp ...` "
|
|
28
28
|
"when an MCP surface is the right fit."
|
package/src/session_service.py
CHANGED
|
@@ -1173,7 +1173,7 @@ def create_session_service(options=None):
|
|
|
1173
1173
|
"bundle_size_bytes": len(bundle_bytes),
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
-
def import_bundle(file_path, passphrase=None, session_names=None, force=False):
|
|
1176
|
+
def import_bundle(file_path, passphrase=None, session_names=None, force=False, merge=False):
|
|
1177
1177
|
if not file_path or not os.path.isfile(file_path):
|
|
1178
1178
|
raise CdxError(f"Bundle file not found: {file_path}")
|
|
1179
1179
|
with open(file_path, "rb") as handle:
|
|
@@ -1193,15 +1193,18 @@ def create_session_service(options=None):
|
|
|
1193
1193
|
|
|
1194
1194
|
existing = {session["name"] for session in list_sessions()}
|
|
1195
1195
|
conflicts = [name for name in names if name in existing]
|
|
1196
|
-
if conflicts and not force:
|
|
1196
|
+
if conflicts and not force and not merge:
|
|
1197
1197
|
raise CdxError(f"Import would overwrite existing sessions: {', '.join(conflicts)}")
|
|
1198
1198
|
|
|
1199
1199
|
for session_payload in imported_sessions:
|
|
1200
1200
|
name = session_payload["name"]
|
|
1201
1201
|
_validate_new_session_name(name)
|
|
1202
1202
|
provider = _normalize_provider(session_payload["provider"])
|
|
1203
|
-
|
|
1203
|
+
is_existing = name in existing
|
|
1204
|
+
|
|
1205
|
+
if is_existing and force:
|
|
1204
1206
|
remove_session(name)
|
|
1207
|
+
is_existing = False
|
|
1205
1208
|
|
|
1206
1209
|
session_root = _get_session_root(name)
|
|
1207
1210
|
auth_home = _get_session_auth_home(name, provider)
|
|
@@ -1210,18 +1213,38 @@ def create_session_service(options=None):
|
|
|
1210
1213
|
_ensure_private_dir(session_root)
|
|
1211
1214
|
_ensure_private_dir(auth_home)
|
|
1212
1215
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1216
|
+
if is_existing and merge:
|
|
1217
|
+
existing_record = store["get_session"](name) or {}
|
|
1218
|
+
bundle_record = {
|
|
1219
|
+
**session_payload,
|
|
1220
|
+
"provider": provider,
|
|
1221
|
+
"enabled": session_payload.get("enabled", True) is not False,
|
|
1222
|
+
"sessionRoot": session_root,
|
|
1223
|
+
"authHome": auth_home,
|
|
1224
|
+
}
|
|
1225
|
+
# Existing values take precedence; bundle fills in missing keys only.
|
|
1226
|
+
merged_record = {**bundle_record, **{k: v for k, v in existing_record.items() if v is not None}}
|
|
1227
|
+
merged_record["sessionRoot"] = session_root
|
|
1228
|
+
merged_record["authHome"] = auth_home
|
|
1229
|
+
store["replace_session"](name, merged_record)
|
|
1230
|
+
else:
|
|
1231
|
+
session_record = {
|
|
1232
|
+
**session_payload,
|
|
1233
|
+
"provider": provider,
|
|
1234
|
+
"enabled": session_payload.get("enabled", True) is not False,
|
|
1235
|
+
"sessionRoot": session_root,
|
|
1236
|
+
"authHome": auth_home,
|
|
1237
|
+
}
|
|
1238
|
+
store["replace_session"](name, session_record)
|
|
1221
1239
|
|
|
1222
1240
|
state = (payload.get("states") or {}).get(name)
|
|
1223
1241
|
if state is not None:
|
|
1224
|
-
|
|
1242
|
+
if is_existing and merge:
|
|
1243
|
+
existing_state = store["read_session_state"](name) or {}
|
|
1244
|
+
merged_state = {**state, **{k: v for k, v in existing_state.items() if v is not None}}
|
|
1245
|
+
store["write_session_state"](name, merged_state)
|
|
1246
|
+
else:
|
|
1247
|
+
store["write_session_state"](name, state)
|
|
1225
1248
|
|
|
1226
1249
|
for item in (payload.get("profiles") or {}).get(name, []):
|
|
1227
1250
|
rel_path = _safe_relpath(item.get("path"))
|
|
@@ -1230,6 +1253,9 @@ def create_session_service(options=None):
|
|
|
1230
1253
|
except (AttributeError, ValueError, UnicodeEncodeError) as error:
|
|
1231
1254
|
raise CdxError(f"Bundle contains invalid file data for session {name}: {rel_path}") from error
|
|
1232
1255
|
dest_path = os.path.join(session_root, rel_path)
|
|
1256
|
+
# In merge mode, skip files that already exist locally.
|
|
1257
|
+
if is_existing and merge and os.path.exists(dest_path):
|
|
1258
|
+
continue
|
|
1233
1259
|
_ensure_private_dir(os.path.dirname(dest_path))
|
|
1234
1260
|
with open(dest_path, "wb") as handle:
|
|
1235
1261
|
handle.write(content)
|