cdx-manager 0.7.6 → 0.8.0
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 +20 -7
- package/changelogs/CHANGELOGS_0_7_8.md +42 -0
- package/changelogs/CHANGELOGS_0_8_0.md +39 -0
- package/checksums/release-archives.json +12 -0
- package/install.ps1 +15 -0
- package/install.sh +27 -1
- package/package.json +4 -3
- package/pyproject.toml +1 -1
- package/src/cli.py +62 -26
- package/src/cli_commands.py +48 -27
- package/src/cli_render.py +4 -0
- package/src/cli_view.py +101 -0
- package/src/health.py +12 -0
- package/src/logics_view.py +37 -0
- package/src/provider_runtime.py +35 -6
- package/src/session_service.py +14 -1
- package/src/update_check.py +103 -0
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
|
|
|
@@ -41,7 +41,8 @@ One command to launch any session. Zero auth juggling.
|
|
|
41
41
|
- **Session control.** Disable a session without deleting it when an account is temporarily out of credits; disabled sessions remain visible and sort last.
|
|
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
|
-
- **Update prompts.** Periodic update checks surface `cdx update` directly in the `cdx`, `cdx status`, and launch output when a newer release is available.
|
|
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; `cdx view --json` reports availability and update diagnostics without opening the viewer.
|
|
45
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.
|
|
46
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.
|
|
47
48
|
- **Session transcript capture.** Every launch is recorded to a local log file via `script`, giving you a full terminal transcript for each session.
|
|
@@ -136,7 +137,7 @@ For a specific version:
|
|
|
136
137
|
|
|
137
138
|
```bash
|
|
138
139
|
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
|
|
139
|
-
CDX_VERSION=v0.
|
|
140
|
+
CDX_VERSION=v0.8.0 sh install.sh
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
From source:
|
|
@@ -189,6 +190,13 @@ Security note:
|
|
|
189
190
|
- Prefer `npm`, `pipx`, or `uv` when you want registry-backed install flows.
|
|
190
191
|
- If you use the standalone script, download it first, inspect it, and prefer a release with an official checksum entry.
|
|
191
192
|
|
|
193
|
+
Release maintainer note:
|
|
194
|
+
|
|
195
|
+
- Before publishing npm or PyPI packages, run `npm run release:validate`.
|
|
196
|
+
- The release tag must match `package.json`, `pyproject.toml`, `src/cli.py`, and `VERSION`.
|
|
197
|
+
- `checksums/release-archives.json` must include the matching `vX.Y.Z` entry with both `github_tarball_sha256` and `github_zip_sha256`.
|
|
198
|
+
- Use `python3 scripts/update_release_checksums.py --tag vX.Y.Z` after the GitHub tag archives exist, commit the checksum update to `main`, then publish the GitHub release only after `npm run release:validate`, `npm run lint`, and `npm test` pass.
|
|
199
|
+
|
|
192
200
|
### Environment
|
|
193
201
|
|
|
194
202
|
By default, `cdx` stores all data under `~/.cdx/`. Override with:
|
|
@@ -271,6 +279,7 @@ cdx set --sessions all --permission auto
|
|
|
271
279
|
cdx set --provider ollama --model llama3.2
|
|
272
280
|
cdx set work --priority 80
|
|
273
281
|
cdx set work --rtk on
|
|
282
|
+
cdx set work --logics off
|
|
274
283
|
cdx power all low
|
|
275
284
|
cdx perm provider:claude review
|
|
276
285
|
cdx model provider:ollama llama3.2
|
|
@@ -286,12 +295,13 @@ cdx unset --sessions work,personal --fast
|
|
|
286
295
|
cdx unset --provider claude --permission
|
|
287
296
|
cdx unset work --priority
|
|
288
297
|
cdx unset work --rtk
|
|
298
|
+
cdx unset work --logics
|
|
289
299
|
cdx unset work --all
|
|
290
300
|
cdx power all default
|
|
291
301
|
cdx model provider:ollama default
|
|
292
302
|
```
|
|
293
303
|
|
|
294
|
-
`--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`. `--permission` maps to provider-native permission flags. `--fast on` clears the stored power setting and uses low effort; setting `--power` again disables fast mode. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on` injects a launch instruction that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output.
|
|
304
|
+
`--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`. `--permission` maps to provider-native permission flags. `--fast on` clears the stored power setting and uses low effort; setting `--power` again disables fast mode. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on` injects a launch instruction that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly.
|
|
295
305
|
|
|
296
306
|
### Launch History
|
|
297
307
|
|
|
@@ -329,8 +339,8 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
329
339
|
| `cdx config <name> [--json]` | Show persistent launch settings for a session |
|
|
330
340
|
| `cdx configs [--json]` | Show persistent launch settings for all sessions in one table |
|
|
331
341
|
| `cdx power\|perm\|fast\|model <name\|all\|provider:PROVIDER\|a,b> <value\|default> [--json]` | Shortcut commands for setting or clearing one launch setting |
|
|
332
|
-
| `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] [--model MODEL] [--priority 0..100] [--json]` | Persist launch settings for one or more sessions |
|
|
333
|
-
| `cdx unset <name>\|--sessions all\|a,b\|--provider PROVIDER (--power\|--permission\|--fast\|--rtk\|--model\|--priority\|--all) [--json]` | Remove persisted launch settings and fall back to provider defaults |
|
|
342
|
+
| `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]` | Persist launch settings for one or more sessions |
|
|
343
|
+
| `cdx unset <name>\|--sessions all\|a,b\|--provider PROVIDER (--power\|--permission\|--fast\|--rtk\|--logics\|--model\|--priority\|--all) [--json]` | Remove persisted launch settings and fall back to provider defaults |
|
|
334
344
|
| `cdx history [name] [--limit N] [--summary] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--json]` | Show recent launch history or aggregate total launch time per assistant, optionally filtered by period |
|
|
335
345
|
| `cdx last [--json]` | Launch the most recent existing session from launch history |
|
|
336
346
|
| `cdx context show\|path\|init\|edit\|clear\|set [text...] [--json]` | Manage the shared Markdown context for the current workspace |
|
|
@@ -342,6 +352,7 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
342
352
|
| `cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
|
|
343
353
|
| `cdx doctor [--json]` | Inspect CLI dependencies, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines |
|
|
344
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 |
|
|
345
356
|
| `cdx update [--check] [--yes] [--json] [--version TAG]` | Update cdx-manager using the installer that matches how it was installed |
|
|
346
357
|
| `cdx ready [--refresh] [--json]` | Schedule an OS notification for the next cooling-down assistant that becomes ready, then return immediately |
|
|
347
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 |
|
|
@@ -385,6 +396,7 @@ Commands with machine-readable output:
|
|
|
385
396
|
- `cdx last --json`
|
|
386
397
|
- `cdx doctor --json`
|
|
387
398
|
- `cdx repair --json`
|
|
399
|
+
- `cdx view --json`
|
|
388
400
|
- `cdx update --json`
|
|
389
401
|
- `cdx ready --json`
|
|
390
402
|
- `cdx notify ... --json`
|
|
@@ -498,7 +510,8 @@ Notes:
|
|
|
498
510
|
|
|
499
511
|
- `npm test`: run the Python test suite
|
|
500
512
|
- `npm run test:py`: run the Python unit tests through the portable launcher
|
|
501
|
-
- `npm run lint`: check the Node launcher and byte-compile the Python sources and tests
|
|
513
|
+
- `npm run lint`: check project guidance, the Node launcher, and byte-compile the Python sources, scripts, and tests
|
|
514
|
+
- `npm run release:validate`: verify version alignment and required GitHub release archive checksum metadata before publication
|
|
502
515
|
- `npm run link`: link `cdx` globally for local development (`npm link`)
|
|
503
516
|
- `npm run unlink`: remove the global link
|
|
504
517
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog (`0.7.6 -> 0.7.8`)
|
|
2
|
+
|
|
3
|
+
Release date: 2026-06-08
|
|
4
|
+
|
|
5
|
+
## Launch Guidance
|
|
6
|
+
|
|
7
|
+
- Added update guidance for `logics-manager` so `cdx` can surface a newer companion CLI version alongside the existing `cdx-manager` update notice.
|
|
8
|
+
- Added `cdx view` as a thin shortcut for `logics-manager view`, plus `cdx view --json` diagnostics for companion availability and update suggestions.
|
|
9
|
+
- Added RTK launch preference handling so noisy assistant shell commands can be wrapped with `rtk` when the session setting asks for filtered command output.
|
|
10
|
+
- Extended provider launch metadata and health checks so companion tool hints appear without making `logics-manager` or RTK hard runtime dependencies.
|
|
11
|
+
|
|
12
|
+
## Coverage and Tests
|
|
13
|
+
|
|
14
|
+
- Added regression coverage for multi-tool update warnings, launch notice rendering, provider runtime launch metadata, and RTK preference handling.
|
|
15
|
+
|
|
16
|
+
## Release Metadata
|
|
17
|
+
|
|
18
|
+
- Updated package metadata, CLI version output, README badge, pinned installer example, and release changelog to `v0.7.8`.
|
|
19
|
+
|
|
20
|
+
## Release Governance
|
|
21
|
+
|
|
22
|
+
- Added `npm run release:validate` as the required pre-publication gate for version alignment and GitHub archive checksum metadata.
|
|
23
|
+
- The npm and PyPI publication workflows now run the checksum/version gate before registry upload.
|
|
24
|
+
- Release prep now requires generating `checksums/release-archives.json` entries for the matching `vX.Y.Z` tag before publication.
|
|
25
|
+
|
|
26
|
+
## Maintainability
|
|
27
|
+
|
|
28
|
+
- Extracted the `cdx view` command domain into a dedicated module while keeping the existing CLI routing and JSON diagnostics unchanged.
|
|
29
|
+
|
|
30
|
+
## Validation and Regression Evidence
|
|
31
|
+
|
|
32
|
+
- `npm run lint`
|
|
33
|
+
- `npm run release:validate`
|
|
34
|
+
- `npm test`
|
|
35
|
+
- `python -m unittest discover -s test -p 'test_*_py.py'`
|
|
36
|
+
- `python3 -m logics_manager lint --require-status`
|
|
37
|
+
- `git diff --check`
|
|
38
|
+
- `node bin/cdx.js --version`
|
|
39
|
+
- `python3 bin/cdx --version`
|
|
40
|
+
- `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
|
|
41
|
+
- `python -m build`
|
|
42
|
+
- `python -m twine check dist/*`
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog (`0.7.8 -> 0.8.0`)
|
|
2
|
+
|
|
3
|
+
Release date: 2026-06-08
|
|
4
|
+
|
|
5
|
+
## Release Governance
|
|
6
|
+
|
|
7
|
+
- Added a pre-publication release checksum gate that validates version alignment across `package.json`, `pyproject.toml`, `src/cli.py`, and `VERSION`.
|
|
8
|
+
- Publication workflows now require matching GitHub archive checksum metadata before npm or PyPI upload.
|
|
9
|
+
- Documented the release ordering for version bump, tag/archive checksum generation, checksum commit, and publication.
|
|
10
|
+
|
|
11
|
+
## Logics Workflow
|
|
12
|
+
|
|
13
|
+
- Versioned `LOGICS.md` as normal project guidance instead of treating it as an ignored local artifact.
|
|
14
|
+
- Added project documentation validation to ensure core Logics command families stay documented.
|
|
15
|
+
- Added `cdx view` as a thin shortcut for `logics-manager view`.
|
|
16
|
+
- Added `cdx view --json` diagnostics for companion availability, delegated command details, failure reason, and update suggestions.
|
|
17
|
+
|
|
18
|
+
## Maintainability
|
|
19
|
+
|
|
20
|
+
- Extracted the `cdx view` command domain into `src/cli_view.py` while keeping `src/cli_commands.py` as a compatibility facade for handler routing.
|
|
21
|
+
|
|
22
|
+
## Release Metadata
|
|
23
|
+
|
|
24
|
+
- Updated package metadata, CLI version output, README badge, pinned installer example, and release changelog to `v0.8.0`.
|
|
25
|
+
|
|
26
|
+
## Validation and Regression Evidence
|
|
27
|
+
|
|
28
|
+
- `python -m py_compile bin/cdx src/*.py test/test_*_py.py`
|
|
29
|
+
- `python -m unittest discover -s test -p 'test_*_py.py'`
|
|
30
|
+
- `npm pack --dry-run`
|
|
31
|
+
- `npm run lint`
|
|
32
|
+
- `npm test`
|
|
33
|
+
- `logics-manager lint --require-status`
|
|
34
|
+
- `logics-manager audit`
|
|
35
|
+
- `git diff --check`
|
|
36
|
+
- `node bin/cdx.js --version`
|
|
37
|
+
- `python3 bin/cdx --version`
|
|
38
|
+
- `python -m build`
|
|
39
|
+
- `python -m twine check dist/*`
|
|
@@ -52,6 +52,18 @@
|
|
|
52
52
|
"v0.7.5": {
|
|
53
53
|
"github_tarball_sha256": "18f5f6230bb5704913a90cfe2bfc5f55599c6ca0813bfd9f4bc97fc9f19e074a",
|
|
54
54
|
"github_zip_sha256": "cafd400c65c255aa9569853ec9d1cc80cf691ff849660496779334ee7abf71b4"
|
|
55
|
+
},
|
|
56
|
+
"v0.7.6": {
|
|
57
|
+
"github_tarball_sha256": "cf6390c2071c710edd7c55d89fd75914e1d94ae5a62330d6fe15029484f88c13",
|
|
58
|
+
"github_zip_sha256": "f58be3078daaa9523053dab7b59d681dfb359d5f2a94276aa46000dd7f1360ed"
|
|
59
|
+
},
|
|
60
|
+
"v0.7.8": {
|
|
61
|
+
"github_tarball_sha256": "8afa0c1f0293a973973158d249aaa627d3aede785cd5a36941be067633580e2a",
|
|
62
|
+
"github_zip_sha256": "207e000dc1192d12324616cc4671f42cf07c22e2eb895556117b681ab5a6ab93"
|
|
63
|
+
},
|
|
64
|
+
"v0.8.0": {
|
|
65
|
+
"github_tarball_sha256": "62cd461f9e22636bb85d801e21209b1762859b6662623e74c01fd40c107ea29a",
|
|
66
|
+
"github_zip_sha256": "4674c8b61110f52b99c76312e9962202a2e8fc6928327f8927781f45eeaa980f"
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
}
|
package/install.ps1
CHANGED
|
@@ -11,6 +11,12 @@ $repo = "AlexAgo83/cdx-manager"
|
|
|
11
11
|
if (-not $ChecksumsUrl) {
|
|
12
12
|
$ChecksumsUrl = "https://raw.githubusercontent.com/$repo/main/checksums/release-archives.json"
|
|
13
13
|
}
|
|
14
|
+
$defaultChecksumsUrl = "https://raw.githubusercontent.com/$repo/main/checksums/release-archives.json"
|
|
15
|
+
$checksumsApiUrl = if ($env:CDX_CHECKSUMS_API_URL) {
|
|
16
|
+
$env:CDX_CHECKSUMS_API_URL
|
|
17
|
+
} else {
|
|
18
|
+
"https://api.github.com/repos/$repo/contents/checksums/release-archives.json?ref=main"
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
function Has-Command {
|
|
16
22
|
param([string]$Name)
|
|
@@ -56,6 +62,15 @@ try {
|
|
|
56
62
|
} catch {
|
|
57
63
|
}
|
|
58
64
|
}
|
|
65
|
+
if ((-not $Sha256) -and ($ChecksumsUrl -eq $defaultChecksumsUrl)) {
|
|
66
|
+
try {
|
|
67
|
+
$checksumsResponse = Invoke-RestMethod -Uri $checksumsApiUrl
|
|
68
|
+
$checksumsJson = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($checksumsResponse.content))
|
|
69
|
+
$checksums = $checksumsJson | ConvertFrom-Json
|
|
70
|
+
$Sha256 = $checksums.releases.$tag.github_zip_sha256
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
59
74
|
if ($Sha256) {
|
|
60
75
|
$actualSha256 = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash.ToLowerInvariant()
|
|
61
76
|
if ($actualSha256 -ne $Sha256.ToLowerInvariant()) {
|
package/install.sh
CHANGED
|
@@ -6,7 +6,9 @@ VERSION="${CDX_VERSION:-}"
|
|
|
6
6
|
PREFIX="${PREFIX:-$HOME/.local}"
|
|
7
7
|
BIN_DIR="${BIN_DIR:-$PREFIX/bin}"
|
|
8
8
|
INSTALL_ROOT="${CDX_INSTALL_ROOT:-$PREFIX/share/cdx-manager}"
|
|
9
|
-
|
|
9
|
+
DEFAULT_CHECKSUMS_URL="https://raw.githubusercontent.com/$REPO/main/checksums/release-archives.json"
|
|
10
|
+
CHECKSUMS_URL="${CDX_CHECKSUMS_URL:-$DEFAULT_CHECKSUMS_URL}"
|
|
11
|
+
CHECKSUMS_API_URL="${CDX_CHECKSUMS_API_URL:-https://api.github.com/repos/$REPO/contents/checksums/release-archives.json?ref=main}"
|
|
10
12
|
|
|
11
13
|
need() {
|
|
12
14
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
@@ -51,6 +53,27 @@ if value:
|
|
|
51
53
|
' "$1"
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
resolve_expected_sha256_from_api() {
|
|
57
|
+
curl -fsSL "$CHECKSUMS_API_URL" |
|
|
58
|
+
python3 -c '
|
|
59
|
+
import base64
|
|
60
|
+
import json
|
|
61
|
+
import sys
|
|
62
|
+
|
|
63
|
+
tag = sys.argv[1]
|
|
64
|
+
try:
|
|
65
|
+
response = json.load(sys.stdin)
|
|
66
|
+
payload = json.loads(base64.b64decode(response.get("content") or b"").decode("utf-8"))
|
|
67
|
+
except Exception:
|
|
68
|
+
raise SystemExit(1)
|
|
69
|
+
|
|
70
|
+
release = (payload.get("releases") or {}).get(tag) or {}
|
|
71
|
+
value = release.get("github_tarball_sha256")
|
|
72
|
+
if value:
|
|
73
|
+
print(value)
|
|
74
|
+
' "$1"
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
if [ -z "$VERSION" ]; then
|
|
55
78
|
VERSION="$(
|
|
56
79
|
curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" |
|
|
@@ -76,6 +99,9 @@ EXPECTED_SHA256="${CDX_SHA256:-}"
|
|
|
76
99
|
if [ -z "$EXPECTED_SHA256" ]; then
|
|
77
100
|
EXPECTED_SHA256="$(resolve_expected_sha256 "$TAG" 2>/dev/null || true)"
|
|
78
101
|
fi
|
|
102
|
+
if [ -z "$EXPECTED_SHA256" ] && [ "$CHECKSUMS_URL" = "$DEFAULT_CHECKSUMS_URL" ]; then
|
|
103
|
+
EXPECTED_SHA256="$(resolve_expected_sha256_from_api "$TAG" 2>/dev/null || true)"
|
|
104
|
+
fi
|
|
79
105
|
|
|
80
106
|
if [ -n "$EXPECTED_SHA256" ]; then
|
|
81
107
|
ACTUAL_SHA256="$(sha256_file "$TMP_DIR/cdx-manager.tar.gz")"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdx-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Terminal session manager for Codex and Claude accounts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexandre Agostini",
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
"scripts": {
|
|
45
45
|
"test": "npm run test:py",
|
|
46
46
|
"test:py": "node bin/python-runner.js -m unittest discover -s test -p test_*_py.py",
|
|
47
|
-
"lint": "node --check bin/cdx.js && node --check bin/python-runner.js && node bin/python-runner.js -m py_compile bin/cdx src/*.py test/test_*_py.py",
|
|
48
|
-
"
|
|
47
|
+
"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",
|
|
48
|
+
"release:validate": "node bin/python-runner.js scripts/verify_release_checksums.py",
|
|
49
|
+
"prepublishOnly": "npm run release:validate && npm run lint && npm test",
|
|
49
50
|
"link": "npm link",
|
|
50
51
|
"unlink": "npm unlink -g cdx-manager"
|
|
51
52
|
}
|
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -37,6 +37,7 @@ from .cli_commands import (
|
|
|
37
37
|
handle_set,
|
|
38
38
|
handle_unset,
|
|
39
39
|
handle_update,
|
|
40
|
+
handle_view,
|
|
40
41
|
)
|
|
41
42
|
from .cli_render import (
|
|
42
43
|
_format_sessions,
|
|
@@ -58,9 +59,9 @@ from .status_view import (
|
|
|
58
59
|
_format_status_detail,
|
|
59
60
|
_format_status_rows,
|
|
60
61
|
)
|
|
61
|
-
from .update_check import check_for_update
|
|
62
|
+
from .update_check import check_for_update, check_logics_manager_for_update
|
|
62
63
|
|
|
63
|
-
VERSION = "0.
|
|
64
|
+
VERSION = "0.8.0"
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
# ---------------------------------------------------------------------------
|
|
@@ -84,8 +85,8 @@ def _print_help(use_color=False):
|
|
|
84
85
|
f" {_style('cdx config <name> [--json]', '36', use_color)}",
|
|
85
86
|
f" {_style('cdx configs [--json]', '36', use_color)}",
|
|
86
87
|
f" {_style('cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]', '36', use_color)}",
|
|
87
|
-
f" {_style('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] [--model MODEL] [--priority 0..100] [--json]', '36', use_color)}",
|
|
88
|
-
f" {_style('cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--model|--priority|--all) [--json]', '36', use_color)}",
|
|
88
|
+
f" {_style('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]', '36', use_color)}",
|
|
89
|
+
f" {_style('cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]', '36', use_color)}",
|
|
89
90
|
f" {_style('cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
|
90
91
|
f" {_style('cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
|
|
91
92
|
f" {_style('cdx last [--json]', '36', use_color)}",
|
|
@@ -104,6 +105,7 @@ def _print_help(use_color=False):
|
|
|
104
105
|
f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR] [--force] [--json]', '36', use_color)}",
|
|
105
106
|
f" {_style('cdx doctor [--json]', '36', use_color)}",
|
|
106
107
|
f" {_style('cdx repair [--dry-run] [--force] [--json]', '36', use_color)}",
|
|
108
|
+
f" {_style('cdx view [--json]', '36', use_color)}",
|
|
107
109
|
f" {_style('cdx update [--check] [--yes] [--json] [--version TAG]', '36', use_color)}",
|
|
108
110
|
f" {_style('cdx ready [--refresh] [--json]', '36', use_color)}",
|
|
109
111
|
f" {_style('cdx notify <name> --at-reset [--schedule] [--refresh] [--json]', '36', use_color)}",
|
|
@@ -158,22 +160,52 @@ def _get_update_notice(service, env, options):
|
|
|
158
160
|
)
|
|
159
161
|
|
|
160
162
|
|
|
161
|
-
def
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
def _get_update_notices(service, env, options):
|
|
164
|
+
notices = []
|
|
165
|
+
cdx_notice = _get_update_notice(service, env, options)
|
|
166
|
+
if cdx_notice:
|
|
167
|
+
notices.append({"tool": "cdx-manager", **cdx_notice})
|
|
168
|
+
checker = options.get("checkLogicsManagerForUpdate") or check_logics_manager_for_update
|
|
169
|
+
logics_notice = checker(
|
|
170
|
+
service["base_dir"],
|
|
171
|
+
env=env,
|
|
172
|
+
now_fn=options.get("now"),
|
|
173
|
+
)
|
|
174
|
+
if logics_notice:
|
|
175
|
+
notices.append(logics_notice)
|
|
176
|
+
return notices
|
|
171
177
|
|
|
172
178
|
|
|
173
|
-
def
|
|
174
|
-
if
|
|
179
|
+
def _update_warning_payload(notices):
|
|
180
|
+
if isinstance(notices, dict):
|
|
181
|
+
notices = [notices]
|
|
182
|
+
if not notices:
|
|
183
|
+
return []
|
|
184
|
+
warnings = []
|
|
185
|
+
for notice in notices:
|
|
186
|
+
tool = notice.get("tool") or "cdx-manager"
|
|
187
|
+
current = notice.get("current_version") or VERSION
|
|
188
|
+
command = notice.get("update_command") or ("cdx update" if tool == "cdx-manager" else None)
|
|
189
|
+
message = f"Update available: {tool} {notice['latest_version']} (current {current})"
|
|
190
|
+
if command:
|
|
191
|
+
message = f"{message}. Run: {command}"
|
|
192
|
+
warnings.append({
|
|
193
|
+
"code": "update_available" if tool == "cdx-manager" else f"{tool.replace('-', '_')}_update_available",
|
|
194
|
+
"message": message,
|
|
195
|
+
"tool": tool,
|
|
196
|
+
"latest_version": notice["latest_version"],
|
|
197
|
+
"current_version": current,
|
|
198
|
+
"update_command": command,
|
|
199
|
+
"url": notice.get("url"),
|
|
200
|
+
})
|
|
201
|
+
return warnings
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _update_warning_text(notices):
|
|
205
|
+
payloads = _update_warning_payload(notices)
|
|
206
|
+
if not payloads:
|
|
175
207
|
return None
|
|
176
|
-
return
|
|
208
|
+
return "\n".join(payload["message"] for payload in payloads)
|
|
177
209
|
|
|
178
210
|
|
|
179
211
|
# ---------------------------------------------------------------------------
|
|
@@ -216,15 +248,16 @@ def main(argv, options=None):
|
|
|
216
248
|
|
|
217
249
|
if argv == ["--json"]:
|
|
218
250
|
rows = service["format_list_rows"]()
|
|
219
|
-
|
|
220
|
-
out(f"{json.dumps(_list_json_payload(rows,
|
|
251
|
+
notices = _get_update_notices(service, env, options)
|
|
252
|
+
out(f"{json.dumps(_list_json_payload(rows, notices=notices), indent=2)}\n")
|
|
221
253
|
return 0
|
|
222
254
|
|
|
223
255
|
if not argv:
|
|
224
|
-
|
|
256
|
+
notices = _get_update_notices(service, env, options)
|
|
225
257
|
out(f"{_format_sessions(service, use_color=use_color)}\n")
|
|
226
|
-
|
|
227
|
-
|
|
258
|
+
warning_text = _update_warning_text(notices)
|
|
259
|
+
if warning_text:
|
|
260
|
+
out(f"{_style(warning_text, '33', use_color)}\n")
|
|
228
261
|
return 0
|
|
229
262
|
|
|
230
263
|
command, *rest = argv
|
|
@@ -243,8 +276,8 @@ def main(argv, options=None):
|
|
|
243
276
|
"stdin_is_tty": stdin_is_tty,
|
|
244
277
|
"version": VERSION,
|
|
245
278
|
"cwd": options.get("cwd") or os.getcwd(),
|
|
246
|
-
"
|
|
247
|
-
"add", "cp", "ren", "rename", "mv", "rmv", "clean", "doctor", "repair", "update", "ready", "notify", "next", "context", "config", "configs", "set", "unset", "power", "perm", "fast", "model", "history", "stats", "handoff", "login", "logout", "disable", "enable", "export", "import", "select", "run", "help", "version"
|
|
279
|
+
"update_notices": _get_update_notices(service, env, options) if command not in (
|
|
280
|
+
"add", "cp", "ren", "rename", "mv", "rmv", "clean", "doctor", "repair", "view", "update", "ready", "notify", "next", "context", "config", "configs", "set", "unset", "power", "perm", "fast", "model", "history", "stats", "handoff", "login", "logout", "disable", "enable", "export", "import", "select", "run", "help", "version"
|
|
248
281
|
) else None,
|
|
249
282
|
"use_color": use_color,
|
|
250
283
|
}
|
|
@@ -282,6 +315,9 @@ def main(argv, options=None):
|
|
|
282
315
|
if command == "repair":
|
|
283
316
|
return handle_repair(rest, ctx)
|
|
284
317
|
|
|
318
|
+
if command == "view":
|
|
319
|
+
return handle_view(rest, ctx)
|
|
320
|
+
|
|
285
321
|
if command == "update":
|
|
286
322
|
return handle_update(rest, ctx)
|
|
287
323
|
|
|
@@ -355,13 +391,13 @@ def main(argv, options=None):
|
|
|
355
391
|
raise CdxError(f"Unknown command: {command}. Use cdx --help.")
|
|
356
392
|
|
|
357
393
|
|
|
358
|
-
def _list_json_payload(rows,
|
|
394
|
+
def _list_json_payload(rows, notices=None):
|
|
359
395
|
return {
|
|
360
396
|
"schema_version": API_SCHEMA_VERSION,
|
|
361
397
|
"ok": True,
|
|
362
398
|
"action": "list",
|
|
363
399
|
"message": "Listed known sessions",
|
|
364
|
-
"warnings": _update_warning_payload(
|
|
400
|
+
"warnings": _update_warning_payload(notices),
|
|
365
401
|
"sessions": rows,
|
|
366
402
|
}
|
|
367
403
|
|
package/src/cli_commands.py
CHANGED
|
@@ -21,6 +21,7 @@ from .context_store import (
|
|
|
21
21
|
)
|
|
22
22
|
from .errors import CdxError
|
|
23
23
|
from .health import collect_health_report, format_health_report
|
|
24
|
+
from .cli_view import handle_view
|
|
24
25
|
from .notify import (
|
|
25
26
|
format_notify_event,
|
|
26
27
|
format_scheduled_notification,
|
|
@@ -55,8 +56,8 @@ EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--
|
|
|
55
56
|
IMPORT_USAGE = "Usage: cdx import <file> [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
|
|
56
57
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
57
58
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
58
|
-
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] [--model MODEL] [--priority 0..100] [--json]"
|
|
59
|
-
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--model|--priority|--all) [--json]"
|
|
59
|
+
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]"
|
|
60
|
+
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]"
|
|
60
61
|
SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
|
|
61
62
|
CONFIG_USAGE = "Usage: cdx config <name> [--json]"
|
|
62
63
|
CONFIGS_USAGE = "Usage: cdx configs [--json]"
|
|
@@ -109,23 +110,42 @@ def _write_json(ctx, payload):
|
|
|
109
110
|
|
|
110
111
|
|
|
111
112
|
def _update_notice_warning(ctx):
|
|
112
|
-
|
|
113
|
-
if not
|
|
113
|
+
notices = ctx.get("update_notices") or []
|
|
114
|
+
if not notices:
|
|
114
115
|
return None
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
warnings = _update_notice_warnings(ctx)
|
|
117
|
+
return warnings[0] if warnings else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _update_notice_warnings(ctx):
|
|
121
|
+
warnings = []
|
|
122
|
+
for notice in ctx.get("update_notices") or []:
|
|
123
|
+
tool = notice.get("tool") or "cdx-manager"
|
|
124
|
+
current = notice.get("current_version") or ctx.get("version")
|
|
125
|
+
command = notice.get("update_command") or ("cdx update" if tool == "cdx-manager" else None)
|
|
126
|
+
message = f"Update available: {tool} {notice['latest_version']}"
|
|
127
|
+
if current:
|
|
128
|
+
message = f"{message} (current {current})"
|
|
129
|
+
if command:
|
|
130
|
+
message = f"{message}. Run: {command}"
|
|
131
|
+
warnings.append({
|
|
132
|
+
"code": "update_available" if tool == "cdx-manager" else f"{tool.replace('-', '_')}_update_available",
|
|
133
|
+
"message": message,
|
|
134
|
+
"tool": tool,
|
|
135
|
+
"latest_version": notice["latest_version"],
|
|
136
|
+
"current_version": current,
|
|
137
|
+
"update_command": command,
|
|
138
|
+
"url": notice.get("url"),
|
|
139
|
+
})
|
|
140
|
+
return warnings
|
|
121
141
|
|
|
122
142
|
|
|
123
143
|
def _write_update_notice(ctx):
|
|
124
|
-
|
|
125
|
-
if not
|
|
144
|
+
warnings = _update_notice_warnings(ctx)
|
|
145
|
+
if not warnings:
|
|
126
146
|
return
|
|
127
|
-
|
|
128
|
-
|
|
147
|
+
for warning in warnings:
|
|
148
|
+
ctx["out"](f"{_warn(warning['message'], ctx['use_color'])}\n")
|
|
129
149
|
|
|
130
150
|
|
|
131
151
|
def _format_bytes(value):
|
|
@@ -527,6 +547,7 @@ def _parse_set_args(args):
|
|
|
527
547
|
"--permission": {"key": "permission", "type": "str", "default": None},
|
|
528
548
|
"--fast": {"key": "fast", "type": "str", "default": None, "transform": _parse_fast_value},
|
|
529
549
|
"--rtk": {"key": "rtk", "type": "str", "default": None, "transform": _parse_fast_value},
|
|
550
|
+
"--logics": {"key": "logics", "type": "str", "default": None, "transform": _parse_fast_value},
|
|
530
551
|
"--model": {"key": "model", "type": "str", "default": None},
|
|
531
552
|
"--priority": {"key": "priority", "type": "str", "default": None, "transform": _parse_priority_value},
|
|
532
553
|
"--sessions": {"key": "sessions", "type": "str", "default": None, "transform": _parse_set_unset_sessions},
|
|
@@ -543,7 +564,7 @@ def _parse_set_args(args):
|
|
|
543
564
|
raise CdxError(SET_USAGE)
|
|
544
565
|
settings = {
|
|
545
566
|
key: parsed[key]
|
|
546
|
-
for key in ("power", "permission", "fast", "rtk", "model", "priority")
|
|
567
|
+
for key in ("power", "permission", "fast", "rtk", "logics", "model", "priority")
|
|
547
568
|
if parsed[key] is not None
|
|
548
569
|
}
|
|
549
570
|
if not settings:
|
|
@@ -563,6 +584,7 @@ def _parse_unset_args(args):
|
|
|
563
584
|
"--permission": {"key": "permission", "type": "bool", "default": False},
|
|
564
585
|
"--fast": {"key": "fast", "type": "bool", "default": False},
|
|
565
586
|
"--rtk": {"key": "rtk", "type": "bool", "default": False},
|
|
587
|
+
"--logics": {"key": "logics", "type": "bool", "default": False},
|
|
566
588
|
"--model": {"key": "model", "type": "bool", "default": False},
|
|
567
589
|
"--priority": {"key": "priority", "type": "bool", "default": False},
|
|
568
590
|
"--all": {"key": "all", "type": "bool", "default": False},
|
|
@@ -578,8 +600,8 @@ def _parse_unset_args(args):
|
|
|
578
600
|
raise CdxError(UNSET_USAGE)
|
|
579
601
|
if not parsed["names"] and not parsed["sessions"] and not parsed["provider"]:
|
|
580
602
|
raise CdxError(UNSET_USAGE)
|
|
581
|
-
keys = ["power", "permission", "fast", "rtk", "model", "priority"] if parsed["all"] else [
|
|
582
|
-
key for key in ("power", "permission", "fast", "rtk", "model", "priority") if parsed[key]
|
|
603
|
+
keys = ["power", "permission", "fast", "rtk", "logics", "model", "priority"] if parsed["all"] else [
|
|
604
|
+
key for key in ("power", "permission", "fast", "rtk", "logics", "model", "priority") if parsed[key]
|
|
583
605
|
]
|
|
584
606
|
if not keys:
|
|
585
607
|
raise CdxError(UNSET_USAGE)
|
|
@@ -1440,6 +1462,7 @@ def _format_launch_config(session, use_color=False):
|
|
|
1440
1462
|
("permission", "Permission"),
|
|
1441
1463
|
("fast", "Fast"),
|
|
1442
1464
|
("rtk", "RTK"),
|
|
1465
|
+
("logics", "Logics"),
|
|
1443
1466
|
("model", "Model"),
|
|
1444
1467
|
("priority", "Priority"),
|
|
1445
1468
|
]:
|
|
@@ -1460,16 +1483,18 @@ def _format_launch_config(session, use_color=False):
|
|
|
1460
1483
|
def _format_launch_settings_hint(name="<name>"):
|
|
1461
1484
|
return (
|
|
1462
1485
|
f"Set a value: cdx set {name} --power medium --permission auto "
|
|
1463
|
-
"--fast on --rtk on --model MODEL --priority 80"
|
|
1486
|
+
"--fast on --rtk on --logics on --model MODEL --priority 80"
|
|
1464
1487
|
)
|
|
1465
1488
|
|
|
1466
1489
|
|
|
1467
1490
|
def _format_launch_setting_value(launch, key, use_color=False):
|
|
1468
|
-
if key
|
|
1491
|
+
if key in ("fast", "rtk", "logics"):
|
|
1469
1492
|
if launch.get(key) is True:
|
|
1470
1493
|
return _style("on", "32", use_color)
|
|
1471
1494
|
if launch.get(key) is False:
|
|
1472
1495
|
return _style("off", "2", use_color)
|
|
1496
|
+
if key == "logics":
|
|
1497
|
+
return _dim("auto", use_color)
|
|
1473
1498
|
return _dim("default", use_color)
|
|
1474
1499
|
value = launch.get(key)
|
|
1475
1500
|
if value is None or value == "":
|
|
@@ -1494,7 +1519,7 @@ def _format_launch_configs(sessions, use_color=False):
|
|
|
1494
1519
|
_dim(_format_launch_settings_hint(), use_color),
|
|
1495
1520
|
])
|
|
1496
1521
|
rows = [[_style(value, "1", use_color) for value in [
|
|
1497
|
-
"SESSION", "PROVIDER", "POWER", "PERMISSION", "FAST", "RTK", "MODEL", "PRIORITY"
|
|
1522
|
+
"SESSION", "PROVIDER", "POWER", "PERMISSION", "FAST", "RTK", "LOGICS", "MODEL", "PRIORITY"
|
|
1498
1523
|
]]]
|
|
1499
1524
|
for session in sessions:
|
|
1500
1525
|
launch = session.get("launch") or {}
|
|
@@ -1505,6 +1530,7 @@ def _format_launch_configs(sessions, use_color=False):
|
|
|
1505
1530
|
_format_launch_setting_value(launch, "permission", use_color),
|
|
1506
1531
|
_format_launch_setting_value(launch, "fast", use_color),
|
|
1507
1532
|
_format_launch_setting_value(launch, "rtk", use_color),
|
|
1533
|
+
_format_launch_setting_value(launch, "logics", use_color),
|
|
1508
1534
|
_format_launch_setting_value(launch, "model", use_color),
|
|
1509
1535
|
_format_launch_setting_value(launch, "priority", use_color),
|
|
1510
1536
|
])
|
|
@@ -2384,9 +2410,7 @@ def handle_status(rest, ctx):
|
|
|
2384
2410
|
}
|
|
2385
2411
|
for item in refresh_errors
|
|
2386
2412
|
])
|
|
2387
|
-
|
|
2388
|
-
if update_warning:
|
|
2389
|
-
warnings.append(update_warning)
|
|
2413
|
+
warnings.extend(_update_notice_warnings(ctx))
|
|
2390
2414
|
|
|
2391
2415
|
status_progress = None if parsed["json"] else _make_status_progress(ctx)
|
|
2392
2416
|
rows = ctx["service"]["get_status_rows"](
|
|
@@ -2709,10 +2733,7 @@ def handle_update(rest, ctx):
|
|
|
2709
2733
|
|
|
2710
2734
|
def handle_launch(command, ctx, initial_prompt=None):
|
|
2711
2735
|
json_flag = "--json" in ctx.get("raw_args", ctx["options"].get("raw_args", []))
|
|
2712
|
-
|
|
2713
|
-
warnings = []
|
|
2714
|
-
if update_notice:
|
|
2715
|
-
warnings.append(_update_notice_warning(ctx))
|
|
2736
|
+
warnings = _update_notice_warnings(ctx)
|
|
2716
2737
|
session = ctx["service"]["launch_session"](command)
|
|
2717
2738
|
_ensure_session_authentication(
|
|
2718
2739
|
session,
|
package/src/cli_render.py
CHANGED
|
@@ -110,6 +110,10 @@ def _format_launch_text(launch):
|
|
|
110
110
|
parts.append(launch["permission"])
|
|
111
111
|
if launch.get("fast") is True:
|
|
112
112
|
parts.append("fast-on")
|
|
113
|
+
if launch.get("logics") is True:
|
|
114
|
+
parts.append("logics-on")
|
|
115
|
+
elif launch.get("logics") is False:
|
|
116
|
+
parts.append("logics-off")
|
|
113
117
|
return "/".join(parts) or "default"
|
|
114
118
|
|
|
115
119
|
|
package/src/cli_view.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from .cli_render import _warn
|
|
4
|
+
from .errors import CdxError
|
|
5
|
+
from .logics_view import (
|
|
6
|
+
LOGICS_MANAGER_INSTALL_HINT,
|
|
7
|
+
build_viewer_diagnostics,
|
|
8
|
+
missing_logics_manager_failure,
|
|
9
|
+
resolve_logics_manager,
|
|
10
|
+
run_logics_viewer,
|
|
11
|
+
)
|
|
12
|
+
from .update_check import check_logics_manager_for_update
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
VIEW_USAGE = "Usage: cdx view [--json]"
|
|
16
|
+
API_SCHEMA_VERSION = 1
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _json_success(action, message, warnings=None, **extra):
|
|
20
|
+
payload = {
|
|
21
|
+
"schema_version": API_SCHEMA_VERSION,
|
|
22
|
+
"ok": True,
|
|
23
|
+
"action": action,
|
|
24
|
+
"message": message,
|
|
25
|
+
"warnings": warnings or [],
|
|
26
|
+
}
|
|
27
|
+
payload.update(extra)
|
|
28
|
+
return payload
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _write_json(ctx, payload):
|
|
32
|
+
ctx["out"](f"{json.dumps(payload, indent=2)}\n")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _update_notice_warnings(notices):
|
|
36
|
+
warnings = []
|
|
37
|
+
for notice in notices or []:
|
|
38
|
+
if not notice:
|
|
39
|
+
continue
|
|
40
|
+
tool = notice.get("tool") or "logics-manager"
|
|
41
|
+
current = notice.get("current_version")
|
|
42
|
+
command = notice.get("update_command")
|
|
43
|
+
message = f"Update available: {tool} {notice['latest_version']}"
|
|
44
|
+
if current:
|
|
45
|
+
message = f"{message} (current {current})"
|
|
46
|
+
if command:
|
|
47
|
+
message = f"{message}. Run: {command}"
|
|
48
|
+
warnings.append({
|
|
49
|
+
"code": f"{tool.replace('-', '_')}_update_available",
|
|
50
|
+
"message": message,
|
|
51
|
+
"tool": tool,
|
|
52
|
+
"latest_version": notice["latest_version"],
|
|
53
|
+
"current_version": current,
|
|
54
|
+
"update_command": command,
|
|
55
|
+
"url": notice.get("url"),
|
|
56
|
+
})
|
|
57
|
+
return warnings
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _logics_manager_update_notice(ctx, env):
|
|
61
|
+
checker = ctx["options"].get("checkLogicsManagerForUpdate") or check_logics_manager_for_update
|
|
62
|
+
return checker(
|
|
63
|
+
ctx["service"]["base_dir"],
|
|
64
|
+
env=env,
|
|
65
|
+
now_fn=ctx["options"].get("now"),
|
|
66
|
+
runner=ctx["options"].get("runLogicsVersionCheck"),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def handle_view(rest, ctx):
|
|
71
|
+
json_flag = "--json" in rest
|
|
72
|
+
unknown = [arg for arg in rest if arg != "--json"]
|
|
73
|
+
if unknown:
|
|
74
|
+
raise CdxError(VIEW_USAGE)
|
|
75
|
+
|
|
76
|
+
env = ctx.get("env")
|
|
77
|
+
cwd = ctx.get("cwd")
|
|
78
|
+
executable = resolve_logics_manager(env=env)
|
|
79
|
+
update_notice = _logics_manager_update_notice(ctx, env) if executable else None
|
|
80
|
+
failure = None if executable else missing_logics_manager_failure()
|
|
81
|
+
diagnostics = build_viewer_diagnostics(executable, cwd, update_notice=update_notice, failure=failure)
|
|
82
|
+
warnings = _update_notice_warnings([update_notice])
|
|
83
|
+
|
|
84
|
+
if json_flag:
|
|
85
|
+
_write_json(ctx, _json_success("view", "Collected Logics viewer diagnostics", warnings=warnings, viewer=diagnostics))
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
if not executable:
|
|
89
|
+
raise CdxError(f"logics-manager is required for cdx view. {LOGICS_MANAGER_INSTALL_HINT}")
|
|
90
|
+
|
|
91
|
+
for warning in warnings:
|
|
92
|
+
ctx["out"](f"{_warn(warning['message'], ctx['use_color'])}\n")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
result = run_logics_viewer(executable, cwd, env=env, runner=ctx.get("spawn_sync"))
|
|
96
|
+
except FileNotFoundError as error:
|
|
97
|
+
raise CdxError(f"logics-manager is required for cdx view. {LOGICS_MANAGER_INSTALL_HINT}") from error
|
|
98
|
+
returncode = getattr(result, "returncode", 0)
|
|
99
|
+
if returncode not in (0, None):
|
|
100
|
+
raise CdxError("logics-manager view failed.", exit_code=returncode)
|
|
101
|
+
return 0
|
package/src/health.py
CHANGED
|
@@ -47,6 +47,18 @@ def collect_health_report(service, base_dir, env=None):
|
|
|
47
47
|
rtk_path,
|
|
48
48
|
))
|
|
49
49
|
|
|
50
|
+
logics_path = shutil.which("logics-manager", path=env.get("PATH"))
|
|
51
|
+
issues.append(_issue(
|
|
52
|
+
"OK" if logics_path else "WARN",
|
|
53
|
+
"logics_manager_cli",
|
|
54
|
+
(
|
|
55
|
+
"logics-manager CLI found"
|
|
56
|
+
if logics_path
|
|
57
|
+
else "logics-manager CLI not found; Logics workflow guidance will not be auto-enabled"
|
|
58
|
+
),
|
|
59
|
+
logics_path,
|
|
60
|
+
))
|
|
61
|
+
|
|
50
62
|
script_bin = env.get("CDX_SCRIPT_BIN", "script")
|
|
51
63
|
script_path = shutil.which(script_bin, path=env.get("PATH"))
|
|
52
64
|
issues.append(_issue(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
LOGICS_MANAGER_INSTALL_HINT = "Install or update it with: npm install -g @grifhinz/logics-manager"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def resolve_logics_manager(env=None):
|
|
9
|
+
env = env or {}
|
|
10
|
+
return shutil.which("logics-manager", path=env.get("PATH", ""))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_viewer_diagnostics(executable, cwd, update_notice=None, failure=None):
|
|
14
|
+
command = [executable or "logics-manager", "view"]
|
|
15
|
+
return {
|
|
16
|
+
"available": bool(executable),
|
|
17
|
+
"executable": executable,
|
|
18
|
+
"command": command,
|
|
19
|
+
"cwd": cwd,
|
|
20
|
+
"update": update_notice,
|
|
21
|
+
"failure": failure,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def missing_logics_manager_failure():
|
|
26
|
+
return {
|
|
27
|
+
"code": "logics_manager_missing",
|
|
28
|
+
"message": f"logics-manager is required for cdx view. {LOGICS_MANAGER_INSTALL_HINT}",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_logics_viewer(executable, cwd, env=None, runner=None):
|
|
33
|
+
runner = runner or subprocess.run
|
|
34
|
+
argv = [executable, "view"]
|
|
35
|
+
if runner is subprocess.run:
|
|
36
|
+
return subprocess.run(argv, cwd=cwd, env=env)
|
|
37
|
+
return runner(argv, cwd=cwd, env=env)
|
package/src/provider_runtime.py
CHANGED
|
@@ -19,6 +19,14 @@ RTK_PROMPT = (
|
|
|
19
19
|
"When running noisy shell commands, prefer RTK wrappers (`rtk <command>`) if `rtk` is available. "
|
|
20
20
|
"Use raw commands when exact, unfiltered output is required."
|
|
21
21
|
)
|
|
22
|
+
LOGICS_PROMPT = (
|
|
23
|
+
"When `logics-manager` is available, prefer it for Logics workflow operations: use "
|
|
24
|
+
"`logics-manager status`, `health`, `audit`, and `lint` for workflow state and validation; "
|
|
25
|
+
"`logics-manager view` for the browser viewer and focus workflows; "
|
|
26
|
+
"`logics-manager sync read-doc|list-docs|search-docs|context-pack` for bounded document context; "
|
|
27
|
+
"`logics-manager flow ...` for request/backlog/task lifecycle changes; and `logics-manager mcp ...` "
|
|
28
|
+
"when an MCP surface is the right fit."
|
|
29
|
+
)
|
|
22
30
|
LAUNCH_PERMISSION_ARGS = {
|
|
23
31
|
PROVIDER_CLAUDE: {
|
|
24
32
|
"review": ["--permission-mode", "plan"],
|
|
@@ -327,20 +335,40 @@ def _rtk_enabled(session):
|
|
|
327
335
|
return (session.get("launch") or {}).get("rtk") is True
|
|
328
336
|
|
|
329
337
|
|
|
330
|
-
def
|
|
331
|
-
|
|
338
|
+
def _logics_manager_available(path=None):
|
|
339
|
+
return shutil.which("logics-manager", path=path) is not None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _logics_enabled(session, env=None):
|
|
343
|
+
launch = session.get("launch") or {}
|
|
344
|
+
if launch.get("logics") is False:
|
|
345
|
+
return False
|
|
346
|
+
if launch.get("logics") is True:
|
|
347
|
+
return True
|
|
348
|
+
env = env or os.environ
|
|
349
|
+
return _logics_manager_available(path=env.get("PATH"))
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _with_launch_preferences(session, initial_prompt=None, env=None):
|
|
353
|
+
prompts = []
|
|
354
|
+
if _rtk_enabled(session):
|
|
355
|
+
prompts.append(RTK_PROMPT)
|
|
356
|
+
if _logics_enabled(session, env=env):
|
|
357
|
+
prompts.append(LOGICS_PROMPT)
|
|
358
|
+
if not prompts:
|
|
332
359
|
return initial_prompt
|
|
333
360
|
if initial_prompt:
|
|
334
|
-
|
|
335
|
-
return
|
|
361
|
+
prompts.append(initial_prompt)
|
|
362
|
+
return "\n\n".join(prompts)
|
|
336
363
|
|
|
337
364
|
|
|
338
365
|
def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None, capture_transcript=True):
|
|
339
|
-
initial_prompt = _with_launch_preferences(session, initial_prompt)
|
|
340
366
|
_validate_initial_prompt(initial_prompt)
|
|
341
367
|
cwd = cwd or os.getcwd()
|
|
342
368
|
env_override = env_override or {}
|
|
343
369
|
env = {**os.environ, **env_override}
|
|
370
|
+
initial_prompt = _with_launch_preferences(session, initial_prompt, env=env)
|
|
371
|
+
_validate_initial_prompt(initial_prompt)
|
|
344
372
|
if session["provider"] == PROVIDER_CLAUDE:
|
|
345
373
|
launch = session.get("launch") or {}
|
|
346
374
|
args = ["--name", session["name"]]
|
|
@@ -418,10 +446,11 @@ def _validate_initial_prompt(initial_prompt):
|
|
|
418
446
|
|
|
419
447
|
|
|
420
448
|
def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_prompt=None):
|
|
421
|
-
initial_prompt = _with_launch_preferences(session, initial_prompt)
|
|
422
449
|
_validate_initial_prompt(initial_prompt)
|
|
423
450
|
cwd = cwd or os.getcwd()
|
|
424
451
|
env = {**os.environ, **(env_override or {})}
|
|
452
|
+
initial_prompt = _with_launch_preferences(session, initial_prompt, env=env)
|
|
453
|
+
_validate_initial_prompt(initial_prompt)
|
|
425
454
|
launch = session.get("launch") or {}
|
|
426
455
|
power = _launch_power(session)
|
|
427
456
|
permission = launch.get("permission")
|
package/src/session_service.py
CHANGED
|
@@ -56,6 +56,7 @@ RESERVED_SESSION_NAMES = {
|
|
|
56
56
|
"status",
|
|
57
57
|
"unset",
|
|
58
58
|
"update",
|
|
59
|
+
"view",
|
|
59
60
|
"version",
|
|
60
61
|
"--help",
|
|
61
62
|
"-h",
|
|
@@ -149,6 +150,18 @@ def _normalize_launch_settings(settings):
|
|
|
149
150
|
normalized["rtk"] = False
|
|
150
151
|
else:
|
|
151
152
|
raise CdxError(f"Unsupported rtk value: {settings['rtk']}")
|
|
153
|
+
if "logics" in settings and settings["logics"] is not None:
|
|
154
|
+
value = settings["logics"]
|
|
155
|
+
if isinstance(value, bool):
|
|
156
|
+
normalized["logics"] = value
|
|
157
|
+
else:
|
|
158
|
+
text = str(value).strip().lower()
|
|
159
|
+
if text in ("on", "true", "1", "yes"):
|
|
160
|
+
normalized["logics"] = True
|
|
161
|
+
elif text in ("off", "false", "0", "no"):
|
|
162
|
+
normalized["logics"] = False
|
|
163
|
+
else:
|
|
164
|
+
raise CdxError(f"Unsupported logics value: {settings['logics']}")
|
|
152
165
|
if "model" in settings and settings["model"] is not None:
|
|
153
166
|
model = str(settings["model"]).strip()
|
|
154
167
|
if not model:
|
|
@@ -839,7 +852,7 @@ def create_session_service(options=None):
|
|
|
839
852
|
raise CdxError(f"Unknown session: {name}")
|
|
840
853
|
if not keys:
|
|
841
854
|
raise CdxError("At least one launch setting is required.")
|
|
842
|
-
allowed = {"power", "permission", "fast", "rtk", "model", "priority"}
|
|
855
|
+
allowed = {"power", "permission", "fast", "rtk", "logics", "model", "priority"}
|
|
843
856
|
unknown = [key for key in keys if key not in allowed]
|
|
844
857
|
if unknown:
|
|
845
858
|
raise CdxError(f"Unsupported launch setting: {', '.join(unknown)}")
|
package/src/update_check.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
3
5
|
import urllib.error
|
|
4
6
|
import urllib.request
|
|
5
7
|
from datetime import datetime, timezone
|
|
@@ -7,6 +9,7 @@ from datetime import datetime, timezone
|
|
|
7
9
|
|
|
8
10
|
UPDATE_CHECK_TTL_SECONDS = 12 * 60 * 60
|
|
9
11
|
LATEST_RELEASE_URL = "https://api.github.com/repos/AlexAgo83/cdx-manager/releases/latest"
|
|
12
|
+
LOGICS_MANAGER_LATEST_URL = "https://registry.npmjs.org/@grifhinz%2Flogics-manager/latest"
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class LatestReleaseCheckError(Exception):
|
|
@@ -40,6 +43,10 @@ def _cache_path(base_dir):
|
|
|
40
43
|
return os.path.join(base_dir, "state", "update-check.json")
|
|
41
44
|
|
|
42
45
|
|
|
46
|
+
def _tool_cache_path(base_dir, tool):
|
|
47
|
+
return os.path.join(base_dir, "state", f"{tool}-update-check.json")
|
|
48
|
+
|
|
49
|
+
|
|
43
50
|
def _read_cache(path):
|
|
44
51
|
try:
|
|
45
52
|
with open(path, "r", encoding="utf-8") as handle:
|
|
@@ -106,6 +113,50 @@ def fetch_latest_release(env=None):
|
|
|
106
113
|
return None
|
|
107
114
|
|
|
108
115
|
|
|
116
|
+
def _fetch_latest_npm_package_version(url=LOGICS_MANAGER_LATEST_URL):
|
|
117
|
+
request = urllib.request.Request(
|
|
118
|
+
url,
|
|
119
|
+
headers={
|
|
120
|
+
"Accept": "application/json",
|
|
121
|
+
"User-Agent": "cdx-manager-update-check",
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
with urllib.request.urlopen(request, timeout=1) as response:
|
|
125
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
126
|
+
version = payload.get("version") if isinstance(payload, dict) else None
|
|
127
|
+
return str(version).strip() if version else None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def fetch_latest_logics_manager_version(env=None):
|
|
131
|
+
try:
|
|
132
|
+
return _fetch_latest_npm_package_version()
|
|
133
|
+
except (urllib.error.URLError, TimeoutError, ValueError, OSError):
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _installed_logics_manager_version(env=None, runner=None):
|
|
138
|
+
env = env or os.environ
|
|
139
|
+
executable = shutil.which("logics-manager", path=env.get("PATH", ""))
|
|
140
|
+
if not executable:
|
|
141
|
+
return None
|
|
142
|
+
runner = runner or subprocess.run
|
|
143
|
+
try:
|
|
144
|
+
result = runner(
|
|
145
|
+
[executable, "--version"],
|
|
146
|
+
capture_output=True,
|
|
147
|
+
text=True,
|
|
148
|
+
timeout=2,
|
|
149
|
+
env=env,
|
|
150
|
+
)
|
|
151
|
+
except (OSError, subprocess.SubprocessError):
|
|
152
|
+
return None
|
|
153
|
+
if getattr(result, "returncode", 0) not in (0, None):
|
|
154
|
+
return None
|
|
155
|
+
output = (getattr(result, "stdout", "") or getattr(result, "stderr", "") or "").strip()
|
|
156
|
+
parts = output.split()
|
|
157
|
+
return parts[-1].lstrip("v") if parts else None
|
|
158
|
+
|
|
159
|
+
|
|
109
160
|
def fetch_latest_release_or_raise(env=None):
|
|
110
161
|
try:
|
|
111
162
|
return _fetch_latest_release(env=env)
|
|
@@ -154,3 +205,55 @@ def check_for_update(base_dir, current_version, env=None, now_fn=None):
|
|
|
154
205
|
"cached": False,
|
|
155
206
|
}
|
|
156
207
|
return None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def check_logics_manager_for_update(base_dir, env=None, now_fn=None, runner=None):
|
|
211
|
+
env = env or os.environ
|
|
212
|
+
now_fn = now_fn or (lambda: datetime.now(timezone.utc).timestamp())
|
|
213
|
+
if env.get("CDX_DISABLE_UPDATE_CHECK") in {"1", "true", "TRUE", "yes", "YES"}:
|
|
214
|
+
return None
|
|
215
|
+
if env.get("LOGICS_MANAGER_NO_UPDATE_CHECK") in {"1", "true", "TRUE", "yes", "YES"}:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
current_version = _installed_logics_manager_version(env=env, runner=runner)
|
|
219
|
+
if not current_version:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
path = _tool_cache_path(base_dir, "logics-manager")
|
|
223
|
+
now_ts = float(now_fn())
|
|
224
|
+
cached = _read_cache(path) or {}
|
|
225
|
+
checked_at = cached.get("checked_at")
|
|
226
|
+
if isinstance(checked_at, (int, float)) and (now_ts - checked_at) < UPDATE_CHECK_TTL_SECONDS:
|
|
227
|
+
latest_version = cached.get("latest_version")
|
|
228
|
+
if _is_newer_version(current_version, latest_version):
|
|
229
|
+
return {
|
|
230
|
+
"tool": "logics-manager",
|
|
231
|
+
"latest_version": latest_version,
|
|
232
|
+
"current_version": current_version,
|
|
233
|
+
"update_command": "logics-manager self-update",
|
|
234
|
+
"url": cached.get("url"),
|
|
235
|
+
"cached": True,
|
|
236
|
+
}
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
latest_version = fetch_latest_logics_manager_version(env=env)
|
|
240
|
+
payload = {
|
|
241
|
+
"checked_at": now_ts,
|
|
242
|
+
"latest_version": latest_version,
|
|
243
|
+
"url": "https://www.npmjs.com/package/@grifhinz/logics-manager",
|
|
244
|
+
}
|
|
245
|
+
try:
|
|
246
|
+
_write_cache(path, payload)
|
|
247
|
+
except OSError:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
if _is_newer_version(current_version, latest_version):
|
|
251
|
+
return {
|
|
252
|
+
"tool": "logics-manager",
|
|
253
|
+
"latest_version": latest_version,
|
|
254
|
+
"current_version": current_version,
|
|
255
|
+
"update_command": "logics-manager self-update",
|
|
256
|
+
"url": payload["url"],
|
|
257
|
+
"cached": False,
|
|
258
|
+
}
|
|
259
|
+
return None
|