cdx-manager 0.7.5 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CDX Manager
2
2
 
3
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.7.5-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
3
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.7.8-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
4
4
 
5
5
  **Run multiple Codex, Claude, Antigravity, and Ollama sessions from one terminal. Switch between accounts instantly.**
6
6
 
@@ -41,7 +41,7 @@ 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
45
  - **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
46
  - **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
47
  - **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 +136,7 @@ For a specific version:
136
136
 
137
137
  ```bash
138
138
  curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
139
- CDX_VERSION=v0.7.5 sh install.sh
139
+ CDX_VERSION=v0.7.8 sh install.sh
140
140
  ```
141
141
 
142
142
  From source:
@@ -271,6 +271,7 @@ cdx set --sessions all --permission auto
271
271
  cdx set --provider ollama --model llama3.2
272
272
  cdx set work --priority 80
273
273
  cdx set work --rtk on
274
+ cdx set work --logics off
274
275
  cdx power all low
275
276
  cdx perm provider:claude review
276
277
  cdx model provider:ollama llama3.2
@@ -286,12 +287,13 @@ cdx unset --sessions work,personal --fast
286
287
  cdx unset --provider claude --permission
287
288
  cdx unset work --priority
288
289
  cdx unset work --rtk
290
+ cdx unset work --logics
289
291
  cdx unset work --all
290
292
  cdx power all default
291
293
  cdx model provider:ollama default
292
294
  ```
293
295
 
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.
296
+ `--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
297
 
296
298
  ### Launch History
297
299
 
@@ -329,8 +331,8 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
329
331
  | `cdx config <name> [--json]` | Show persistent launch settings for a session |
330
332
  | `cdx configs [--json]` | Show persistent launch settings for all sessions in one table |
331
333
  | `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 |
334
+ | `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 |
335
+ | `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
336
  | `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
337
  | `cdx last [--json]` | Launch the most recent existing session from launch history |
336
338
  | `cdx context show\|path\|init\|edit\|clear\|set [text...] [--json]` | Manage the shared Markdown context for the current workspace |
@@ -0,0 +1,37 @@
1
+ # Changelog (`0.7.5 -> 0.7.6`)
2
+
3
+ Release date: 2026-06-05
4
+
5
+ ## Claude Launches
6
+
7
+ - Normalized Claude model names before invoking Claude Code so persisted API-style IDs such as `claude-sonnet-4-5-20250929` launch as the Claude Code CLI form `claude-sonnet-4-5`.
8
+ - Added compatibility for marketing-style Claude model values such as `sonnet-4.5` while preserving unknown custom model values unchanged.
9
+ - Applied the same Claude model normalization to both interactive launches and headless `cdx run` launches.
10
+
11
+ ## Release Integrity
12
+
13
+ - Added official standalone release archive checksums for `v0.7.5` so verified installer upgrades can resolve the previous release.
14
+ - Added regression coverage for the `v0.7.5` release checksum metadata.
15
+
16
+ ## CI Hardening
17
+
18
+ - Restricted GitHub Actions workflow token permissions for the CI workflow.
19
+
20
+ ## Coverage and Tests
21
+
22
+ - Added regression coverage for Claude CLI model normalization in runtime launch specs and interactive CLI launch behavior.
23
+ - Increased the Python test suite to 285 tests.
24
+
25
+ ## Release Metadata
26
+
27
+ - Updated package metadata, CLI version output, README badge, pinned installer example, and release changelog to `v0.7.6`.
28
+
29
+ ## Validation and Regression Evidence
30
+
31
+ - `npm run lint`
32
+ - `npm test`
33
+ - `node bin/cdx.js --version`
34
+ - `npm audit --omit=dev --json`
35
+ - `sh -n install.sh`
36
+ - PowerShell parser check for `install.ps1`
37
+ - `python3 -m logics_manager lint --require-status`
@@ -0,0 +1,30 @@
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 RTK launch preference handling so noisy assistant shell commands can be wrapped with `rtk` when the session setting asks for filtered command output.
9
+ - Extended provider launch metadata and health checks so companion tool hints appear without making `logics-manager` or RTK hard runtime dependencies.
10
+
11
+ ## Coverage and Tests
12
+
13
+ - Added regression coverage for multi-tool update warnings, launch notice rendering, provider runtime launch metadata, and RTK preference handling.
14
+
15
+ ## Release Metadata
16
+
17
+ - Updated package metadata, CLI version output, README badge, pinned installer example, and release changelog to `v0.7.8`.
18
+
19
+ ## Validation and Regression Evidence
20
+
21
+ - `npm run lint`
22
+ - `npm test`
23
+ - `python -m unittest discover -s test -p 'test_*_py.py'`
24
+ - `python3 -m logics_manager lint --require-status`
25
+ - `git diff --check`
26
+ - `node bin/cdx.js --version`
27
+ - `python3 bin/cdx --version`
28
+ - `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
29
+ - `python -m build`
30
+ - `python -m twine check dist/*`
@@ -48,6 +48,14 @@
48
48
  "v0.7.4": {
49
49
  "github_tarball_sha256": "02b399899e611300ddf367bfb8c22ad57b499e94265e50256c8afe3f4bd73851",
50
50
  "github_zip_sha256": "87043d49cdd50dcd7d51349984ea4abfb1a2ff50f2eab69c75a421bedd2f2fe8"
51
+ },
52
+ "v0.7.5": {
53
+ "github_tarball_sha256": "18f5f6230bb5704913a90cfe2bfc5f55599c6ca0813bfd9f4bc97fc9f19e074a",
54
+ "github_zip_sha256": "cafd400c65c255aa9569853ec9d1cc80cf691ff849660496779334ee7abf71b4"
55
+ },
56
+ "v0.7.6": {
57
+ "github_tarball_sha256": "cf6390c2071c710edd7c55d89fd75914e1d94ae5a62330d6fe15029484f88c13",
58
+ "github_zip_sha256": "f58be3078daaa9523053dab7b59d681dfb359d5f2a94276aa46000dd7f1360ed"
51
59
  }
52
60
  }
53
61
  }
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
- CHECKSUMS_URL="${CDX_CHECKSUMS_URL:-https://raw.githubusercontent.com/$REPO/main/checksums/release-archives.json}"
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.7.5",
3
+ "version": "0.7.8",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.7.5"
7
+ version = "0.7.8"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
package/src/cli.py CHANGED
@@ -58,9 +58,9 @@ from .status_view import (
58
58
  _format_status_detail,
59
59
  _format_status_rows,
60
60
  )
61
- from .update_check import check_for_update
61
+ from .update_check import check_for_update, check_logics_manager_for_update
62
62
 
63
- VERSION = "0.7.5"
63
+ VERSION = "0.7.8"
64
64
 
65
65
 
66
66
  # ---------------------------------------------------------------------------
@@ -84,8 +84,8 @@ def _print_help(use_color=False):
84
84
  f" {_style('cdx config <name> [--json]', '36', use_color)}",
85
85
  f" {_style('cdx configs [--json]', '36', use_color)}",
86
86
  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)}",
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] [--logics 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|--logics|--model|--priority|--all) [--json]', '36', use_color)}",
89
89
  f" {_style('cdx history [name] [--limit N] [--summary] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
90
90
  f" {_style('cdx stats [name] [--since 7d|today|DATE] [--from DATE] [--to DATE] [--json]', '36', use_color)}",
91
91
  f" {_style('cdx last [--json]', '36', use_color)}",
@@ -158,22 +158,52 @@ def _get_update_notice(service, env, options):
158
158
  )
159
159
 
160
160
 
161
- def _update_warning_payload(notice):
162
- if not notice:
163
- return []
164
- message = f"Update available: cdx-manager {notice['latest_version']} (current {VERSION})"
165
- return [{
166
- "code": "update_available",
167
- "message": message,
168
- "latest_version": notice["latest_version"],
169
- "url": notice.get("url"),
170
- }]
161
+ def _get_update_notices(service, env, options):
162
+ notices = []
163
+ cdx_notice = _get_update_notice(service, env, options)
164
+ if cdx_notice:
165
+ notices.append({"tool": "cdx-manager", **cdx_notice})
166
+ checker = options.get("checkLogicsManagerForUpdate") or check_logics_manager_for_update
167
+ logics_notice = checker(
168
+ service["base_dir"],
169
+ env=env,
170
+ now_fn=options.get("now"),
171
+ )
172
+ if logics_notice:
173
+ notices.append(logics_notice)
174
+ return notices
171
175
 
172
176
 
173
- def _update_warning_text(notice):
174
- if not notice:
177
+ def _update_warning_payload(notices):
178
+ if isinstance(notices, dict):
179
+ notices = [notices]
180
+ if not notices:
181
+ return []
182
+ warnings = []
183
+ for notice in notices:
184
+ tool = notice.get("tool") or "cdx-manager"
185
+ current = notice.get("current_version") or VERSION
186
+ command = notice.get("update_command") or ("cdx update" if tool == "cdx-manager" else None)
187
+ message = f"Update available: {tool} {notice['latest_version']} (current {current})"
188
+ if command:
189
+ message = f"{message}. Run: {command}"
190
+ warnings.append({
191
+ "code": "update_available" if tool == "cdx-manager" else f"{tool.replace('-', '_')}_update_available",
192
+ "message": message,
193
+ "tool": tool,
194
+ "latest_version": notice["latest_version"],
195
+ "current_version": current,
196
+ "update_command": command,
197
+ "url": notice.get("url"),
198
+ })
199
+ return warnings
200
+
201
+
202
+ def _update_warning_text(notices):
203
+ payloads = _update_warning_payload(notices)
204
+ if not payloads:
175
205
  return None
176
- return f"Update available: cdx-manager {notice['latest_version']} (current {VERSION}). Run: cdx update"
206
+ return "\n".join(payload["message"] for payload in payloads)
177
207
 
178
208
 
179
209
  # ---------------------------------------------------------------------------
@@ -216,15 +246,16 @@ def main(argv, options=None):
216
246
 
217
247
  if argv == ["--json"]:
218
248
  rows = service["format_list_rows"]()
219
- notice = _get_update_notice(service, env, options)
220
- out(f"{json.dumps(_list_json_payload(rows, notice=notice), indent=2)}\n")
249
+ notices = _get_update_notices(service, env, options)
250
+ out(f"{json.dumps(_list_json_payload(rows, notices=notices), indent=2)}\n")
221
251
  return 0
222
252
 
223
253
  if not argv:
224
- notice = _get_update_notice(service, env, options)
254
+ notices = _get_update_notices(service, env, options)
225
255
  out(f"{_format_sessions(service, use_color=use_color)}\n")
226
- if notice:
227
- out(f"{_style(_update_warning_text(notice), '33', use_color)}\n")
256
+ warning_text = _update_warning_text(notices)
257
+ if warning_text:
258
+ out(f"{_style(warning_text, '33', use_color)}\n")
228
259
  return 0
229
260
 
230
261
  command, *rest = argv
@@ -243,7 +274,7 @@ def main(argv, options=None):
243
274
  "stdin_is_tty": stdin_is_tty,
244
275
  "version": VERSION,
245
276
  "cwd": options.get("cwd") or os.getcwd(),
246
- "update_notice": _get_update_notice(service, env, options) if command not in (
277
+ "update_notices": _get_update_notices(service, env, options) if command not in (
247
278
  "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"
248
279
  ) else None,
249
280
  "use_color": use_color,
@@ -355,13 +386,13 @@ def main(argv, options=None):
355
386
  raise CdxError(f"Unknown command: {command}. Use cdx --help.")
356
387
 
357
388
 
358
- def _list_json_payload(rows, notice=None):
389
+ def _list_json_payload(rows, notices=None):
359
390
  return {
360
391
  "schema_version": API_SCHEMA_VERSION,
361
392
  "ok": True,
362
393
  "action": "list",
363
394
  "message": "Listed known sessions",
364
- "warnings": _update_warning_payload(notice),
395
+ "warnings": _update_warning_payload(notices),
365
396
  "sessions": rows,
366
397
  }
367
398
 
@@ -55,8 +55,8 @@ EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--
55
55
  IMPORT_USAGE = "Usage: cdx import <file> [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR]"
56
56
  CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
57
57
  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]"
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] [--logics 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|--logics|--model|--priority|--all) [--json]"
60
60
  SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
61
61
  CONFIG_USAGE = "Usage: cdx config <name> [--json]"
62
62
  CONFIGS_USAGE = "Usage: cdx configs [--json]"
@@ -109,23 +109,42 @@ def _write_json(ctx, payload):
109
109
 
110
110
 
111
111
  def _update_notice_warning(ctx):
112
- notice = ctx.get("update_notice")
113
- if not notice:
112
+ notices = ctx.get("update_notices") or []
113
+ if not notices:
114
114
  return None
115
- return {
116
- "code": "update_available",
117
- "message": f"Update available: cdx-manager {notice['latest_version']}",
118
- "latest_version": notice["latest_version"],
119
- "url": notice.get("url"),
120
- }
115
+ warnings = _update_notice_warnings(ctx)
116
+ return warnings[0] if warnings else None
117
+
118
+
119
+ def _update_notice_warnings(ctx):
120
+ warnings = []
121
+ for notice in ctx.get("update_notices") or []:
122
+ tool = notice.get("tool") or "cdx-manager"
123
+ current = notice.get("current_version") or ctx.get("version")
124
+ command = notice.get("update_command") or ("cdx update" if tool == "cdx-manager" else None)
125
+ message = f"Update available: {tool} {notice['latest_version']}"
126
+ if current:
127
+ message = f"{message} (current {current})"
128
+ if command:
129
+ message = f"{message}. Run: {command}"
130
+ warnings.append({
131
+ "code": "update_available" if tool == "cdx-manager" else f"{tool.replace('-', '_')}_update_available",
132
+ "message": message,
133
+ "tool": tool,
134
+ "latest_version": notice["latest_version"],
135
+ "current_version": current,
136
+ "update_command": command,
137
+ "url": notice.get("url"),
138
+ })
139
+ return warnings
121
140
 
122
141
 
123
142
  def _write_update_notice(ctx):
124
- notice = ctx.get("update_notice")
125
- if not notice:
143
+ warnings = _update_notice_warnings(ctx)
144
+ if not warnings:
126
145
  return
127
- text = f"Update available: cdx-manager {notice['latest_version']} (current version installed may be older). Run: cdx update"
128
- ctx["out"](f"{_warn(text, ctx['use_color'])}\n")
146
+ for warning in warnings:
147
+ ctx["out"](f"{_warn(warning['message'], ctx['use_color'])}\n")
129
148
 
130
149
 
131
150
  def _format_bytes(value):
@@ -527,6 +546,7 @@ def _parse_set_args(args):
527
546
  "--permission": {"key": "permission", "type": "str", "default": None},
528
547
  "--fast": {"key": "fast", "type": "str", "default": None, "transform": _parse_fast_value},
529
548
  "--rtk": {"key": "rtk", "type": "str", "default": None, "transform": _parse_fast_value},
549
+ "--logics": {"key": "logics", "type": "str", "default": None, "transform": _parse_fast_value},
530
550
  "--model": {"key": "model", "type": "str", "default": None},
531
551
  "--priority": {"key": "priority", "type": "str", "default": None, "transform": _parse_priority_value},
532
552
  "--sessions": {"key": "sessions", "type": "str", "default": None, "transform": _parse_set_unset_sessions},
@@ -543,7 +563,7 @@ def _parse_set_args(args):
543
563
  raise CdxError(SET_USAGE)
544
564
  settings = {
545
565
  key: parsed[key]
546
- for key in ("power", "permission", "fast", "rtk", "model", "priority")
566
+ for key in ("power", "permission", "fast", "rtk", "logics", "model", "priority")
547
567
  if parsed[key] is not None
548
568
  }
549
569
  if not settings:
@@ -563,6 +583,7 @@ def _parse_unset_args(args):
563
583
  "--permission": {"key": "permission", "type": "bool", "default": False},
564
584
  "--fast": {"key": "fast", "type": "bool", "default": False},
565
585
  "--rtk": {"key": "rtk", "type": "bool", "default": False},
586
+ "--logics": {"key": "logics", "type": "bool", "default": False},
566
587
  "--model": {"key": "model", "type": "bool", "default": False},
567
588
  "--priority": {"key": "priority", "type": "bool", "default": False},
568
589
  "--all": {"key": "all", "type": "bool", "default": False},
@@ -578,8 +599,8 @@ def _parse_unset_args(args):
578
599
  raise CdxError(UNSET_USAGE)
579
600
  if not parsed["names"] and not parsed["sessions"] and not parsed["provider"]:
580
601
  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]
602
+ keys = ["power", "permission", "fast", "rtk", "logics", "model", "priority"] if parsed["all"] else [
603
+ key for key in ("power", "permission", "fast", "rtk", "logics", "model", "priority") if parsed[key]
583
604
  ]
584
605
  if not keys:
585
606
  raise CdxError(UNSET_USAGE)
@@ -1440,6 +1461,7 @@ def _format_launch_config(session, use_color=False):
1440
1461
  ("permission", "Permission"),
1441
1462
  ("fast", "Fast"),
1442
1463
  ("rtk", "RTK"),
1464
+ ("logics", "Logics"),
1443
1465
  ("model", "Model"),
1444
1466
  ("priority", "Priority"),
1445
1467
  ]:
@@ -1460,16 +1482,18 @@ def _format_launch_config(session, use_color=False):
1460
1482
  def _format_launch_settings_hint(name="<name>"):
1461
1483
  return (
1462
1484
  f"Set a value: cdx set {name} --power medium --permission auto "
1463
- "--fast on --rtk on --model MODEL --priority 80"
1485
+ "--fast on --rtk on --logics on --model MODEL --priority 80"
1464
1486
  )
1465
1487
 
1466
1488
 
1467
1489
  def _format_launch_setting_value(launch, key, use_color=False):
1468
- if key == "fast" or key == "rtk":
1490
+ if key in ("fast", "rtk", "logics"):
1469
1491
  if launch.get(key) is True:
1470
1492
  return _style("on", "32", use_color)
1471
1493
  if launch.get(key) is False:
1472
1494
  return _style("off", "2", use_color)
1495
+ if key == "logics":
1496
+ return _dim("auto", use_color)
1473
1497
  return _dim("default", use_color)
1474
1498
  value = launch.get(key)
1475
1499
  if value is None or value == "":
@@ -1494,7 +1518,7 @@ def _format_launch_configs(sessions, use_color=False):
1494
1518
  _dim(_format_launch_settings_hint(), use_color),
1495
1519
  ])
1496
1520
  rows = [[_style(value, "1", use_color) for value in [
1497
- "SESSION", "PROVIDER", "POWER", "PERMISSION", "FAST", "RTK", "MODEL", "PRIORITY"
1521
+ "SESSION", "PROVIDER", "POWER", "PERMISSION", "FAST", "RTK", "LOGICS", "MODEL", "PRIORITY"
1498
1522
  ]]]
1499
1523
  for session in sessions:
1500
1524
  launch = session.get("launch") or {}
@@ -1505,6 +1529,7 @@ def _format_launch_configs(sessions, use_color=False):
1505
1529
  _format_launch_setting_value(launch, "permission", use_color),
1506
1530
  _format_launch_setting_value(launch, "fast", use_color),
1507
1531
  _format_launch_setting_value(launch, "rtk", use_color),
1532
+ _format_launch_setting_value(launch, "logics", use_color),
1508
1533
  _format_launch_setting_value(launch, "model", use_color),
1509
1534
  _format_launch_setting_value(launch, "priority", use_color),
1510
1535
  ])
@@ -2384,9 +2409,7 @@ def handle_status(rest, ctx):
2384
2409
  }
2385
2410
  for item in refresh_errors
2386
2411
  ])
2387
- update_warning = _update_notice_warning(ctx)
2388
- if update_warning:
2389
- warnings.append(update_warning)
2412
+ warnings.extend(_update_notice_warnings(ctx))
2390
2413
 
2391
2414
  status_progress = None if parsed["json"] else _make_status_progress(ctx)
2392
2415
  rows = ctx["service"]["get_status_rows"](
@@ -2709,10 +2732,7 @@ def handle_update(rest, ctx):
2709
2732
 
2710
2733
  def handle_launch(command, ctx, initial_prompt=None):
2711
2734
  json_flag = "--json" in ctx.get("raw_args", ctx["options"].get("raw_args", []))
2712
- update_notice = ctx.get("update_notice")
2713
- warnings = []
2714
- if update_notice:
2715
- warnings.append(_update_notice_warning(ctx))
2735
+ warnings = _update_notice_warnings(ctx)
2716
2736
  session = ctx["service"]["launch_session"](command)
2717
2737
  _ensure_session_authentication(
2718
2738
  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/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(
@@ -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"],
@@ -40,6 +48,14 @@ HEADLESS_CODEX_PERMISSION_ARGS = {
40
48
  "full": ["--dangerously-bypass-approvals-and-sandbox"],
41
49
  }
42
50
  REDACTED_PROMPT_ARG = "[prompt redacted]"
51
+ CLAUDE_CLI_MODEL_ALIASES = {
52
+ "claude-sonnet": "sonnet",
53
+ "claude-opus": "opus",
54
+ "claude-haiku": "haiku",
55
+ "sonnet-latest": "sonnet",
56
+ "opus-latest": "opus",
57
+ "haiku-latest": "haiku",
58
+ }
43
59
 
44
60
 
45
61
  def _home_env_overrides(auth_home):
@@ -207,6 +223,26 @@ def _normalize_reasoning_effort(reasoning_effort=None, power=None, usage="Unsupp
207
223
  }
208
224
 
209
225
 
226
+ def _claude_cli_model(model):
227
+ if not model:
228
+ return model
229
+ raw = str(model).strip()
230
+ normalized = raw.lower().replace("_", "-")
231
+ if normalized in CLAUDE_CLI_MODEL_ALIASES:
232
+ return CLAUDE_CLI_MODEL_ALIASES[normalized]
233
+
234
+ marketing = re.fullmatch(r"(?:claude-)?(sonnet|opus|haiku)-(\d+)(?:[.-](\d+))?", normalized)
235
+ if marketing:
236
+ family, major, minor = marketing.groups()
237
+ return f"claude-{family}-{major}-{minor}" if minor else family
238
+
239
+ dated = re.fullmatch(r"(claude-(?:sonnet|opus|haiku)-4(?:-\d+)*)-\d{8}", normalized)
240
+ if dated:
241
+ return dated.group(1)
242
+
243
+ return raw
244
+
245
+
210
246
  def _launch_config_args(session):
211
247
  launch = session.get("launch") or {}
212
248
  args = []
@@ -299,25 +335,45 @@ def _rtk_enabled(session):
299
335
  return (session.get("launch") or {}).get("rtk") is True
300
336
 
301
337
 
302
- def _with_launch_preferences(session, initial_prompt=None):
303
- if not _rtk_enabled(session):
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:
304
359
  return initial_prompt
305
360
  if initial_prompt:
306
- return f"{RTK_PROMPT}\n\n{initial_prompt}"
307
- return RTK_PROMPT
361
+ prompts.append(initial_prompt)
362
+ return "\n\n".join(prompts)
308
363
 
309
364
 
310
365
  def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None, capture_transcript=True):
311
- initial_prompt = _with_launch_preferences(session, initial_prompt)
312
366
  _validate_initial_prompt(initial_prompt)
313
367
  cwd = cwd or os.getcwd()
314
368
  env_override = env_override or {}
315
369
  env = {**os.environ, **env_override}
370
+ initial_prompt = _with_launch_preferences(session, initial_prompt, env=env)
371
+ _validate_initial_prompt(initial_prompt)
316
372
  if session["provider"] == PROVIDER_CLAUDE:
317
373
  launch = session.get("launch") or {}
318
374
  args = ["--name", session["name"]]
319
375
  if launch.get("model"):
320
- args += ["--model", launch["model"]]
376
+ args += ["--model", _claude_cli_model(launch["model"])]
321
377
  args += _launch_config_args(session)
322
378
  if initial_prompt:
323
379
  args.append(initial_prompt)
@@ -390,10 +446,11 @@ def _validate_initial_prompt(initial_prompt):
390
446
 
391
447
 
392
448
  def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_prompt=None):
393
- initial_prompt = _with_launch_preferences(session, initial_prompt)
394
449
  _validate_initial_prompt(initial_prompt)
395
450
  cwd = cwd or os.getcwd()
396
451
  env = {**os.environ, **(env_override or {})}
452
+ initial_prompt = _with_launch_preferences(session, initial_prompt, env=env)
453
+ _validate_initial_prompt(initial_prompt)
397
454
  launch = session.get("launch") or {}
398
455
  power = _launch_power(session)
399
456
  permission = launch.get("permission")
@@ -402,7 +459,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
402
459
  if session["provider"] == PROVIDER_CLAUDE:
403
460
  args = ["--print", "--output-format", "json", "--name", session["name"]]
404
461
  if model:
405
- args += ["--model", model]
462
+ args += ["--model", _claude_cli_model(model)]
406
463
  args += _launch_config_args(session)
407
464
  if initial_prompt:
408
465
  args.append(initial_prompt)
@@ -149,6 +149,18 @@ def _normalize_launch_settings(settings):
149
149
  normalized["rtk"] = False
150
150
  else:
151
151
  raise CdxError(f"Unsupported rtk value: {settings['rtk']}")
152
+ if "logics" in settings and settings["logics"] is not None:
153
+ value = settings["logics"]
154
+ if isinstance(value, bool):
155
+ normalized["logics"] = value
156
+ else:
157
+ text = str(value).strip().lower()
158
+ if text in ("on", "true", "1", "yes"):
159
+ normalized["logics"] = True
160
+ elif text in ("off", "false", "0", "no"):
161
+ normalized["logics"] = False
162
+ else:
163
+ raise CdxError(f"Unsupported logics value: {settings['logics']}")
152
164
  if "model" in settings and settings["model"] is not None:
153
165
  model = str(settings["model"]).strip()
154
166
  if not model:
@@ -839,7 +851,7 @@ def create_session_service(options=None):
839
851
  raise CdxError(f"Unknown session: {name}")
840
852
  if not keys:
841
853
  raise CdxError("At least one launch setting is required.")
842
- allowed = {"power", "permission", "fast", "rtk", "model", "priority"}
854
+ allowed = {"power", "permission", "fast", "rtk", "logics", "model", "priority"}
843
855
  unknown = [key for key in keys if key not in allowed]
844
856
  if unknown:
845
857
  raise CdxError(f"Unsupported launch setting: {', '.join(unknown)}")
@@ -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