cdx-manager 0.10.0 → 0.11.1
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 +10 -8
- package/bin/cdx +2 -11
- package/changelogs/CHANGELOGS_0_11_0.md +51 -0
- package/changelogs/CHANGELOGS_0_11_1.md +34 -0
- package/checksums/release-archives.json +8 -0
- package/install.ps1 +12 -19
- package/install.sh +6 -29
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/claude_refresh.py +17 -33
- package/src/cli.py +9 -36
- package/src/cli_args.py +30 -12
- package/src/cli_commands.py +46 -17
- package/src/cli_helpers.py +26 -38
- package/src/cli_render.py +5 -0
- package/src/cli_view.py +5 -27
- package/src/codex_usage.py +14 -8
- package/src/notify.py +6 -10
- package/src/provider_runtime.py +58 -14
- package/src/session_service.py +174 -57
- package/src/session_store.py +2 -2
- package/src/status_source.py +47 -25
- package/src/status_view.py +11 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
-
[](LICENSE) ](LICENSE)  
|
|
4
4
|
|
|
5
5
|
**Run multiple Codex, Claude, Antigravity, and Ollama sessions from one terminal. Switch between accounts instantly.**
|
|
6
6
|
|
|
@@ -36,6 +36,7 @@ One command to launch any session. Zero auth juggling.
|
|
|
36
36
|
- **Instant launch.** `cdx work` opens your "work" session. `cdx personal` opens another. No config files to edit mid-flow.
|
|
37
37
|
- **Quick relaunch.** `cdx last` reopens the most recently launched assistant profile.
|
|
38
38
|
- **Auth guardrails.** `cdx` checks authentication before launching. If a session is not logged in, it tells you exactly what to run — no silent failures.
|
|
39
|
+
- **Optional session labels.** `cdx label <name> <label>` adds a short human label; list and full status tables show a `LABEL` column only when at least one session has one.
|
|
39
40
|
- **Usage at a glance.** `cdx status` shows token usage, 5-hour window quota, weekly quota, last-updated timestamps, priority guidance, and the last launched session in one aligned table.
|
|
40
41
|
- **Next-ready notification.** `cdx ready` schedules a native system notification for the next assistant that comes back from cooldown, then returns immediately.
|
|
41
42
|
- **Session control.** Disable a session without deleting it when an account is temporarily out of credits; disabled sessions remain visible and sort last.
|
|
@@ -79,8 +80,6 @@ One command to launch any session. Zero auth juggling.
|
|
|
79
80
|
- Auth probe: synchronous subprocess call to `codex login status` or `claude auth status` before any interactive launch.
|
|
80
81
|
- Signal forwarding: `SIGINT`, `SIGTERM`, and `SIGHUP` are forwarded to the child process and produce clean exit codes.
|
|
81
82
|
- Test stack: `pytest`, `pytest-cov`, and `ruff` via the documented `dev` dependency extra.
|
|
82
|
-
- Python floor decision: `cdx-manager` keeps Python 3.9 support for the 0.9.x line to avoid dropping existing users before a planned compatibility break; classifiers remain aligned with `requires-python = ">=3.9"`.
|
|
83
|
-
|
|
84
83
|
---
|
|
85
84
|
|
|
86
85
|
## Getting Started
|
|
@@ -194,18 +193,18 @@ npm install -g .
|
|
|
194
193
|
|
|
195
194
|
Security note:
|
|
196
195
|
|
|
197
|
-
- The standalone installers
|
|
196
|
+
- The standalone installers resolve official release checksums from the tagged GitHub Release asset `release-archives.json`.
|
|
198
197
|
- You can still override verification explicitly through `CDX_SHA256`.
|
|
199
|
-
- If no checksum is available, standalone installers fail closed unless `CDX_ALLOW_UNVERIFIED=1` is set.
|
|
198
|
+
- If no checksum is available, standalone installers fail closed unless `CDX_ALLOW_UNVERIFIED=1` is set; that override prints a prominent warning before continuing.
|
|
200
199
|
- Prefer `npm`, `pipx`, or `uv` when you want registry-backed install flows.
|
|
201
|
-
- If you use the standalone script, download it first, inspect it, and prefer a release with
|
|
200
|
+
- If you use the standalone script, download it first, inspect it, and prefer a release with the official checksum asset attached.
|
|
202
201
|
|
|
203
202
|
Release maintainer note:
|
|
204
203
|
|
|
205
204
|
- Before publishing npm or PyPI packages, run `npm run release:validate`.
|
|
206
205
|
- The release tag must match `package.json`, `pyproject.toml`, `src/cli.py`, and `VERSION`.
|
|
207
206
|
- `checksums/release-archives.json` must include the matching `vX.Y.Z` entry with both `github_tarball_sha256` and `github_zip_sha256`.
|
|
208
|
-
- Use `python3 scripts/update_release_checksums.py --tag vX.Y.Z` after the GitHub tag archives exist, commit the checksum update to `
|
|
207
|
+
- Use `python3 scripts/update_release_checksums.py --tag vX.Y.Z` after the GitHub tag archives exist, commit the checksum update, attach that file to the tagged GitHub Release as `release-archives.json`, then publish packages only after `npm run release:validate`, `npm run lint`, and `npm test` pass.
|
|
209
208
|
|
|
210
209
|
### Environment
|
|
211
210
|
|
|
@@ -347,6 +346,8 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
347
346
|
| `cdx add [provider] <name> [--model MODEL] [--json]` | Register a new session (`provider`: `codex`, `claude`, `antigravity`, or `ollama`; Ollama requires `--model`) |
|
|
348
347
|
| `cdx cp <source> <dest> [--json]` | Copy a session into another session name, overwriting the destination if it exists |
|
|
349
348
|
| `cdx ren <source> <dest> [--json]` | Rename a session and move its auth data |
|
|
349
|
+
| `cdx label <name> <label> [--json]` | Attach one short label to a session; tables show `LABEL` only when at least one label exists |
|
|
350
|
+
| `cdx label <name> --clear [--json]` | Clear a session label |
|
|
350
351
|
| `cdx login <name> [--json]` | Re-authenticate a session (logout + login) |
|
|
351
352
|
| `cdx logout <name> [--json]` | Log out of a session |
|
|
352
353
|
| `cdx disable <name> [--json]` | Disable a session without deleting it; disabled sessions stay visible and cannot launch |
|
|
@@ -401,6 +402,7 @@ Commands with machine-readable output:
|
|
|
401
402
|
- `cdx add ... --json`
|
|
402
403
|
- `cdx cp ... --json`
|
|
403
404
|
- `cdx ren ... --json`
|
|
405
|
+
- `cdx label ... --json`
|
|
404
406
|
- `cdx rmv ... --json`
|
|
405
407
|
- `cdx clean ... --json`
|
|
406
408
|
- `cdx disk ... --json`
|
|
@@ -523,7 +525,7 @@ Notes:
|
|
|
523
525
|
- `--include-auth` is encrypted, requires a passphrase, and exports only provider credential files rather than full profile caches or logs.
|
|
524
526
|
- Without `--passphrase-env`, `cdx` prompts in an interactive terminal. Non-interactive callers can pass `--passphrase-stdin` to read the passphrase from stdin (one line) instead of exposing it in the child environment.
|
|
525
527
|
- `--sessions work,perso` exports or imports only a subset.
|
|
526
|
-
- `--force` allows overwriting existing destination sessions during import or replacing an existing bundle file during export.
|
|
528
|
+
- `--force` allows overwriting existing destination sessions during import or replacing an existing bundle file during export. Import refuses to overwrite existing sessions from a bundle without auth payloads unless `--allow-authless-force` is also passed.
|
|
527
529
|
- Auth bundles contain credentials. Treat them like secrets and delete them after transfer.
|
|
528
530
|
|
|
529
531
|
---
|
package/bin/cdx
CHANGED
|
@@ -3,21 +3,12 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
ROOT = Path(__file__).resolve().parent.parent
|
|
8
7
|
if str(ROOT) not in sys.path:
|
|
9
8
|
sys.path.insert(0, str(ROOT))
|
|
10
9
|
|
|
11
|
-
from src.cli import
|
|
12
|
-
from src.errors import CdxError # noqa: E402
|
|
10
|
+
from src.cli import cli_entry # noqa: E402
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
if __name__ == "__main__":
|
|
16
|
-
|
|
17
|
-
raise SystemExit(main(sys.argv[1:]))
|
|
18
|
-
except CdxError as error:
|
|
19
|
-
if wants_json(sys.argv[1:]):
|
|
20
|
-
sys.stderr.write(f"{format_json_error(error)}\n")
|
|
21
|
-
else:
|
|
22
|
-
sys.stderr.write(f"{format_error(error)}\n")
|
|
23
|
-
raise SystemExit(error.exit_code)
|
|
14
|
+
cli_entry()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# CDX Manager 0.11.0
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Fixed Codex structured status parsing so five-hour and weekly reset windows stay mapped to the correct columns.
|
|
6
|
+
- Hardened backup import, provider auth probing, release verification, and Windows launcher generation.
|
|
7
|
+
- Closed the July 2026 review remediation corpus and follow-up Logics workflow with release-ready validation.
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
### Codex status reset windows
|
|
12
|
+
|
|
13
|
+
Structured Codex status payloads now preserve the distinction between the five-hour and weekly reset timestamps. `cdx status` no longer mirrors the weekly reset into the five-hour reset column, or the reverse, when provider output includes both windows.
|
|
14
|
+
|
|
15
|
+
### Safer session import and credentials handling
|
|
16
|
+
|
|
17
|
+
`cdx import --force` refuses to overwrite existing sessions from bundles without auth payloads unless `--allow-authless-force` is explicitly supplied.
|
|
18
|
+
|
|
19
|
+
Bundle import now validates selected profile paths, base64 payloads, malformed profile collections, and non-object profile entries before touching existing session profiles. Existing profiles are renamed aside during force import and restored if a per-session import fails.
|
|
20
|
+
|
|
21
|
+
### Provider auth reliability
|
|
22
|
+
|
|
23
|
+
Provider auth probes are bounded by a 15 second timeout. Timeouts now surface as a degraded authentication state instead of being persisted as `logged_out`.
|
|
24
|
+
|
|
25
|
+
Codex interactive launches wait on the per-auth-home lock with a bounded retry instead of running unlocked while another process may be rotating OAuth credentials.
|
|
26
|
+
|
|
27
|
+
Stored `reasoning_effort` and `power` launch settings now clear each other correctly, so setting one cannot leave the other silently shadowing it.
|
|
28
|
+
|
|
29
|
+
### CLI robustness and release hardening
|
|
30
|
+
|
|
31
|
+
`cdx history` skips corrupt JSONL lines, `cdx status` tolerates sessions removed during a concurrent scan, `cdx clean profiles` routes profile cleanup flags consistently, `cdx disk --candidates` validates arguments before scanning, and `cdx view --json` now reports a proper failure when the Logics viewer is unavailable.
|
|
32
|
+
|
|
33
|
+
The npm launcher routes through the same `cli_entry()` path as the Python entrypoint. Standalone installers and publish workflows now use tagged GitHub Release checksum assets rather than main-branch checksum metadata, and `CDX_ALLOW_UNVERIFIED=1` prints a prominent warning before continuing.
|
|
34
|
+
|
|
35
|
+
The standalone Windows installer now generates `cdx.cmd` with the installed version directory in the launcher path.
|
|
36
|
+
|
|
37
|
+
### Logics documentation
|
|
38
|
+
|
|
39
|
+
The July 2026 review remediation corpus and post-review follow-up corpus are closed with implementation reports, validation evidence, and a clean Logics audit.
|
|
40
|
+
|
|
41
|
+
## Validation
|
|
42
|
+
|
|
43
|
+
- `npm run release:validate`
|
|
44
|
+
- `npm run lint`
|
|
45
|
+
- `npm test`
|
|
46
|
+
- `logics-manager lint --require-status`
|
|
47
|
+
- `logics-manager audit`
|
|
48
|
+
- `git diff --check`
|
|
49
|
+
- `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
|
|
50
|
+
- `python -m build`
|
|
51
|
+
- `python -m twine check dist/*`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# CDX Manager 0.11.1
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Added optional labels for saved sessions.
|
|
6
|
+
- Kept existing list and compact status output clean when no labels are present.
|
|
7
|
+
- Closed the session-label Logics corpus with full validation evidence.
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
### Optional session labels
|
|
12
|
+
|
|
13
|
+
`cdx label <name> <label>` now attaches one short human-readable label to a saved session. Labels are stored as session metadata outside the `launch` dictionary, so they do not affect provider commands, auth checks, runtime state, ready/next selection, or launch history behavior.
|
|
14
|
+
|
|
15
|
+
`cdx label <name> --clear` removes the label and returns the session to the same shape as legacy unlabeled records.
|
|
16
|
+
|
|
17
|
+
The default `cdx` list and full `cdx status` table add a `LABEL` column only when at least one row has a label. Unlabeled rows render as `-`, and `cdx status --small` remains compact.
|
|
18
|
+
|
|
19
|
+
JSON outputs for `cdx --json`, `cdx status --json`, `cdx status <name> --json`, and `cdx label ... --json` expose label values for automation consumers.
|
|
20
|
+
|
|
21
|
+
Session copy, rename, export, and import preserve labels.
|
|
22
|
+
|
|
23
|
+
### Documentation and workflow
|
|
24
|
+
|
|
25
|
+
README and CLI help now document `cdx label <name> <label>` and `cdx label <name> --clear`.
|
|
26
|
+
|
|
27
|
+
The optional session-label Logics request, backlog item, product brief, and orchestration task are closed with validation evidence.
|
|
28
|
+
|
|
29
|
+
## Validation
|
|
30
|
+
|
|
31
|
+
- `python3 -m unittest discover -s test`
|
|
32
|
+
- `logics-manager flow validate req_012_add_optional_labels_to_cdx_sessions`
|
|
33
|
+
- `logics-manager status`
|
|
34
|
+
- `logics-manager health`
|
|
@@ -132,6 +132,14 @@
|
|
|
132
132
|
"v0.9.16": {
|
|
133
133
|
"github_tarball_sha256": "0876509c201485824f6e5fe7b4a96ceb41cc68279fbe6dda4891864cf5be93ec",
|
|
134
134
|
"github_zip_sha256": "08072957a0421d257cecfa8bed73cceee80d1c8857c59ac45378ac8ce3e0362d"
|
|
135
|
+
},
|
|
136
|
+
"v0.10.0": {
|
|
137
|
+
"github_tarball_sha256": "20c473f4b17806d565c454ad4aa28758433665d8431251ebb937ab6a909e4adb",
|
|
138
|
+
"github_zip_sha256": "b6a7be359d8dc2a11d8c77749878875419fabd7cd5a6951502dec09d7791e211"
|
|
139
|
+
},
|
|
140
|
+
"v0.11.0": {
|
|
141
|
+
"github_tarball_sha256": "bc42f83f20ba1c6ff81f824c382801457f93cf8cb166acbe952c12d009560a70",
|
|
142
|
+
"github_zip_sha256": "1c03b4f03f53c46d0a5ba580fc8ade7b26fa3cb1bb5f029f703681f136b9ac1f"
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
}
|
package/install.ps1
CHANGED
|
@@ -9,13 +9,7 @@ $ErrorActionPreference = "Stop"
|
|
|
9
9
|
|
|
10
10
|
$repo = "AlexAgo83/cdx-manager"
|
|
11
11
|
if (-not $ChecksumsUrl) {
|
|
12
|
-
$ChecksumsUrl =
|
|
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"
|
|
12
|
+
$ChecksumsUrl = $null
|
|
19
13
|
}
|
|
20
14
|
|
|
21
15
|
function Has-Command {
|
|
@@ -45,10 +39,16 @@ if ($Version.StartsWith("v")) {
|
|
|
45
39
|
$tag = "v$Version"
|
|
46
40
|
}
|
|
47
41
|
|
|
42
|
+
$defaultChecksumsUrl = "https://github.com/$repo/releases/download/$tag/release-archives.json"
|
|
43
|
+
if (-not $ChecksumsUrl) {
|
|
44
|
+
$ChecksumsUrl = $defaultChecksumsUrl
|
|
45
|
+
}
|
|
46
|
+
|
|
48
47
|
$tmpRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("cdx-install-" + [guid]::NewGuid().ToString("N"))
|
|
49
48
|
$archivePath = Join-Path $tmpRoot "cdx-manager.zip"
|
|
50
49
|
$extractRoot = Join-Path $tmpRoot "extract"
|
|
51
|
-
$
|
|
50
|
+
$versionDir = $tag.TrimStart("v")
|
|
51
|
+
$targetDir = Join-Path $installRoot $versionDir
|
|
52
52
|
$archiveUrl = "https://github.com/$repo/archive/refs/tags/$tag.zip"
|
|
53
53
|
|
|
54
54
|
New-Item -ItemType Directory -Force -Path $tmpRoot, $extractRoot, $binDir, $installRoot | Out-Null
|
|
@@ -62,15 +62,6 @@ try {
|
|
|
62
62
|
} catch {
|
|
63
63
|
}
|
|
64
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
|
-
}
|
|
74
65
|
if ($Sha256) {
|
|
75
66
|
$actualSha256 = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash.ToLowerInvariant()
|
|
76
67
|
if ($actualSha256 -ne $Sha256.ToLowerInvariant()) {
|
|
@@ -78,7 +69,9 @@ try {
|
|
|
78
69
|
}
|
|
79
70
|
} else {
|
|
80
71
|
if ($env:CDX_ALLOW_UNVERIFIED -eq "1") {
|
|
81
|
-
Write-Warning "
|
|
72
|
+
Write-Warning "Installing $tag without checksum verification."
|
|
73
|
+
Write-Warning "CDX_ALLOW_UNVERIFIED=1 disables archive integrity checks."
|
|
74
|
+
Write-Warning "Continue only if you trust the download source."
|
|
82
75
|
} else {
|
|
83
76
|
throw "cdx install: no official checksum available for $tag. Set CDX_ALLOW_UNVERIFIED=1 to install without checksum verification."
|
|
84
77
|
}
|
|
@@ -99,7 +92,7 @@ try {
|
|
|
99
92
|
$launcherPath = Join-Path $binDir "cdx.cmd"
|
|
100
93
|
$launcher = @"
|
|
101
94
|
@echo off
|
|
102
|
-
set SCRIPT=%~dp0..\versions\$
|
|
95
|
+
set SCRIPT=%~dp0..\versions\$versionDir\bin\cdx
|
|
103
96
|
where py >nul 2>nul
|
|
104
97
|
if %ERRORLEVEL%==0 (
|
|
105
98
|
py -3 "%SCRIPT%" %*
|
package/install.sh
CHANGED
|
@@ -6,9 +6,6 @@ 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
|
-
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}"
|
|
12
9
|
|
|
13
10
|
need() {
|
|
14
11
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
@@ -53,27 +50,6 @@ if value:
|
|
|
53
50
|
' "$1"
|
|
54
51
|
}
|
|
55
52
|
|
|
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
|
-
|
|
77
53
|
if [ -z "$VERSION" ]; then
|
|
78
54
|
VERSION="$(
|
|
79
55
|
curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" |
|
|
@@ -86,6 +62,9 @@ case "$VERSION" in
|
|
|
86
62
|
*) TAG="v$VERSION" ;;
|
|
87
63
|
esac
|
|
88
64
|
|
|
65
|
+
DEFAULT_CHECKSUMS_URL="https://github.com/$REPO/releases/download/$TAG/release-archives.json"
|
|
66
|
+
CHECKSUMS_URL="${CDX_CHECKSUMS_URL:-$DEFAULT_CHECKSUMS_URL}"
|
|
67
|
+
|
|
89
68
|
TMP_DIR="$(mktemp -d)"
|
|
90
69
|
cleanup() {
|
|
91
70
|
rm -rf "$TMP_DIR"
|
|
@@ -99,10 +78,6 @@ EXPECTED_SHA256="${CDX_SHA256:-}"
|
|
|
99
78
|
if [ -z "$EXPECTED_SHA256" ]; then
|
|
100
79
|
EXPECTED_SHA256="$(resolve_expected_sha256 "$TAG" 2>/dev/null || true)"
|
|
101
80
|
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
|
|
105
|
-
|
|
106
81
|
if [ -n "$EXPECTED_SHA256" ]; then
|
|
107
82
|
ACTUAL_SHA256="$(sha256_file "$TMP_DIR/cdx-manager.tar.gz")"
|
|
108
83
|
if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
|
|
@@ -113,7 +88,9 @@ if [ -n "$EXPECTED_SHA256" ]; then
|
|
|
113
88
|
fi
|
|
114
89
|
else
|
|
115
90
|
if [ "${CDX_ALLOW_UNVERIFIED:-}" = "1" ]; then
|
|
116
|
-
echo "cdx install:
|
|
91
|
+
echo "cdx install: WARNING: installing $TAG without checksum verification." >&2
|
|
92
|
+
echo "cdx install: WARNING: CDX_ALLOW_UNVERIFIED=1 disables archive integrity checks." >&2
|
|
93
|
+
echo "cdx install: WARNING: continue only if you trust the download source." >&2
|
|
117
94
|
else
|
|
118
95
|
echo "cdx install: no official checksum available for $TAG" >&2
|
|
119
96
|
echo "Set CDX_ALLOW_UNVERIFIED=1 to install without checksum verification." >&2
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/claude_refresh.py
CHANGED
|
@@ -48,40 +48,24 @@ def _refresh_claude_sessions(service, refresh_fn=None, target_names=None, force=
|
|
|
48
48
|
errors = []
|
|
49
49
|
results = {}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
import asyncio
|
|
51
|
+
threads = []
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
usage = refresh_fn(s)
|
|
72
|
-
if inspect.isawaitable(usage):
|
|
73
|
-
raise CdxError("Claude refresh function returned an awaitable from a sync callable.")
|
|
74
|
-
if usage:
|
|
75
|
-
results[s["name"]] = usage
|
|
76
|
-
except Exception as e:
|
|
77
|
-
errors.append({"session": s["name"], "error": e})
|
|
78
|
-
|
|
79
|
-
for s in claude_sessions:
|
|
80
|
-
t = threading.Thread(target=fetch, args=(s,), daemon=True)
|
|
81
|
-
threads.append(t)
|
|
82
|
-
t.start()
|
|
83
|
-
for t in threads:
|
|
84
|
-
t.join(timeout=10)
|
|
53
|
+
def fetch(s):
|
|
54
|
+
try:
|
|
55
|
+
usage = refresh_fn(s)
|
|
56
|
+
if inspect.isawaitable(usage):
|
|
57
|
+
raise CdxError("Claude refresh function returned an awaitable from a sync callable.")
|
|
58
|
+
if usage:
|
|
59
|
+
results[s["name"]] = usage
|
|
60
|
+
except Exception as e:
|
|
61
|
+
errors.append({"session": s["name"], "error": e})
|
|
62
|
+
|
|
63
|
+
for s in claude_sessions:
|
|
64
|
+
t = threading.Thread(target=fetch, args=(s,), daemon=True)
|
|
65
|
+
threads.append(t)
|
|
66
|
+
t.start()
|
|
67
|
+
for t in threads:
|
|
68
|
+
t.join(timeout=10)
|
|
85
69
|
|
|
86
70
|
# A worker that outlived its join can still insert into results/errors;
|
|
87
71
|
# snapshot both so late writes can't mutate what we iterate.
|
package/src/cli.py
CHANGED
|
@@ -26,6 +26,7 @@ from .cli_commands import (
|
|
|
26
26
|
handle_handoff,
|
|
27
27
|
handle_history,
|
|
28
28
|
handle_import,
|
|
29
|
+
handle_label,
|
|
29
30
|
handle_last,
|
|
30
31
|
handle_launch,
|
|
31
32
|
handle_launch_setting_alias,
|
|
@@ -53,6 +54,7 @@ from .cli_commands import (
|
|
|
53
54
|
from .cli_commands import (
|
|
54
55
|
_format_bytes as _format_disk_bytes,
|
|
55
56
|
)
|
|
57
|
+
from .cli_helpers import _update_notice_warnings
|
|
56
58
|
from .cli_render import (
|
|
57
59
|
_format_sessions,
|
|
58
60
|
_pad_table,
|
|
@@ -75,7 +77,7 @@ from .status_view import (
|
|
|
75
77
|
)
|
|
76
78
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
77
79
|
|
|
78
|
-
VERSION = "0.
|
|
80
|
+
VERSION = "0.11.1"
|
|
79
81
|
|
|
80
82
|
# Public surface: this module is a facade. Names below are imported above
|
|
81
83
|
# purely to be re-exported (consumed by tests and external callers); listing
|
|
@@ -111,6 +113,7 @@ _COMMAND_HANDLERS = {
|
|
|
111
113
|
"handoff": handle_handoff,
|
|
112
114
|
"history": handle_history,
|
|
113
115
|
"import": handle_import,
|
|
116
|
+
"label": handle_label,
|
|
114
117
|
"last": handle_last,
|
|
115
118
|
"login": handle_login,
|
|
116
119
|
"logout": handle_logout,
|
|
@@ -178,7 +181,7 @@ def _print_help(use_color=False):
|
|
|
178
181
|
f" {_style('cdx next [--json] [--refresh]', '36', use_color)}",
|
|
179
182
|
f" {_style('cdx reset <name> [--yes] [--json]', '36', use_color)}",
|
|
180
183
|
f" {_style('cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json', '36', use_color)}",
|
|
181
|
-
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json', '36', use_color)}",
|
|
184
|
+
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json', '36', use_color)}",
|
|
182
185
|
f" {_style('cdx runs [--limit N] --json', '36', use_color)}",
|
|
183
186
|
f" {_style('cdx run-status <run_id> --json', '36', use_color)}",
|
|
184
187
|
f" {_style('cdx run-report <run_id> --json', '36', use_color)}",
|
|
@@ -199,6 +202,8 @@ def _print_help(use_color=False):
|
|
|
199
202
|
f" {_style('cdx add [provider] <name> [--model MODEL] [--json]', '36', use_color)}",
|
|
200
203
|
f" {_style('cdx cp <source> <dest> [--json]', '36', use_color)}",
|
|
201
204
|
f" {_style('cdx ren <source> <dest> [--json]', '36', use_color)}",
|
|
205
|
+
f" {_style('cdx label <name> <label> [--json]', '36', use_color)}",
|
|
206
|
+
f" {_style('cdx label <name> --clear [--json]', '36', use_color)}",
|
|
202
207
|
f" {_style('cdx login <name> [--json]', '36', use_color)}",
|
|
203
208
|
f" {_style('cdx logout <name> [--json]', '36', use_color)}",
|
|
204
209
|
f" {_style('cdx disable <name> [--json]', '36', use_color)}",
|
|
@@ -426,40 +431,8 @@ def _get_disk_cleanup_notice(service, options):
|
|
|
426
431
|
}
|
|
427
432
|
|
|
428
433
|
|
|
429
|
-
def _update_warning_payload(notices):
|
|
430
|
-
if isinstance(notices, dict):
|
|
431
|
-
notices = [notices]
|
|
432
|
-
if not notices:
|
|
433
|
-
return []
|
|
434
|
-
warnings = []
|
|
435
|
-
for notice in notices:
|
|
436
|
-
if notice.get("code") == "disk_cleanup_available":
|
|
437
|
-
warnings.append({
|
|
438
|
-
"code": "disk_cleanup_available",
|
|
439
|
-
"message": notice["message"],
|
|
440
|
-
**{key: value for key, value in notice.items() if key not in ("code", "message")},
|
|
441
|
-
})
|
|
442
|
-
continue
|
|
443
|
-
tool = notice.get("tool") or "cdx-manager"
|
|
444
|
-
current = notice.get("current_version") or VERSION
|
|
445
|
-
command = notice.get("update_command") or ("cdx update" if tool == "cdx-manager" else None)
|
|
446
|
-
message = f"Update available: {tool} {notice['latest_version']} (current {current})"
|
|
447
|
-
if command:
|
|
448
|
-
message = f"{message}. Run: {command}"
|
|
449
|
-
warnings.append({
|
|
450
|
-
"code": "update_available" if tool == "cdx-manager" else f"{tool.replace('-', '_')}_update_available",
|
|
451
|
-
"message": message,
|
|
452
|
-
"tool": tool,
|
|
453
|
-
"latest_version": notice["latest_version"],
|
|
454
|
-
"current_version": current,
|
|
455
|
-
"update_command": command,
|
|
456
|
-
"url": notice.get("url"),
|
|
457
|
-
})
|
|
458
|
-
return warnings
|
|
459
|
-
|
|
460
|
-
|
|
461
434
|
def _update_warning_text(notices):
|
|
462
|
-
payloads =
|
|
435
|
+
payloads = _update_notice_warnings({"update_notices": notices or [], "version": VERSION})
|
|
463
436
|
if not payloads:
|
|
464
437
|
return None
|
|
465
438
|
return "\n".join(payload["message"] for payload in payloads)
|
|
@@ -571,7 +544,7 @@ def _list_json_payload(rows, notices=None):
|
|
|
571
544
|
"ok": True,
|
|
572
545
|
"action": "list",
|
|
573
546
|
"message": "Listed known sessions",
|
|
574
|
-
"warnings":
|
|
547
|
+
"warnings": _update_notice_warnings({"update_notices": notices or [], "version": VERSION}),
|
|
575
548
|
"sessions": rows,
|
|
576
549
|
}
|
|
577
550
|
|
package/src/cli_args.py
CHANGED
|
@@ -17,11 +17,12 @@ DISK_USAGE = "Usage: cdx disk [profiles] [--candidates] [--json]"
|
|
|
17
17
|
REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
|
|
18
18
|
UPDATE_USAGE = "Usage: cdx update [--check] [--yes] [--json] [--version TAG]"
|
|
19
19
|
EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR|--passphrase-stdin]"
|
|
20
|
-
IMPORT_USAGE = "Usage: cdx import <file> [--force|--merge] [--json] [--sessions name1,name2] [--passphrase-env VAR|--passphrase-stdin]"
|
|
20
|
+
IMPORT_USAGE = "Usage: cdx import <file> [--force|--merge] [--allow-authless-force] [--json] [--sessions name1,name2] [--passphrase-env VAR|--passphrase-stdin]"
|
|
21
21
|
CONTEXT_USAGE = "Usage: cdx context show|path|init|edit|clear|set [text...] [--json]"
|
|
22
22
|
HANDOFF_USAGE = "Usage: cdx handoff <name> [--json] | cdx handoff <source> <target> [--json]"
|
|
23
|
+
LABEL_USAGE = "Usage: cdx label <name> <label> [--json] | cdx label <name> --clear [--json]"
|
|
23
24
|
SET_USAGE = "Usage: cdx set <name>|--sessions all|a,b|--provider PROVIDER [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full] [--fast on|off] [--rtk on|off] [--logics on|off] [--model MODEL] [--priority 0..100] [--json]"
|
|
24
|
-
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]"
|
|
25
|
+
UNSET_USAGE = "Usage: cdx unset <name>|--sessions all|a,b|--provider PROVIDER (--power|--reasoning-effort|--permission|--fast|--rtk|--logics|--model|--priority|--all) [--json]"
|
|
25
26
|
SETTING_ALIAS_USAGE = "Usage: cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]"
|
|
26
27
|
CONFIG_USAGE = "Usage: cdx config <name> [--json]"
|
|
27
28
|
CONFIGS_USAGE = "Usage: cdx configs [--json]"
|
|
@@ -107,6 +108,21 @@ def _parse_rename_args(args):
|
|
|
107
108
|
return {"source": args[0], "dest": args[1]}
|
|
108
109
|
|
|
109
110
|
|
|
111
|
+
def _parse_label_args(args):
|
|
112
|
+
parsed = _parse_flag_args(args, {
|
|
113
|
+
"--clear": {"key": "clear", "type": "bool", "default": False},
|
|
114
|
+
"--json": {"key": "json", "type": "bool", "default": False},
|
|
115
|
+
}, LABEL_USAGE, positionals_key="values", max_positionals=2)
|
|
116
|
+
values = parsed["values"]
|
|
117
|
+
if parsed["clear"]:
|
|
118
|
+
if len(values) != 1:
|
|
119
|
+
raise CdxError(LABEL_USAGE)
|
|
120
|
+
return {"name": values[0], "label": None, "clear": True, "json": parsed["json"]}
|
|
121
|
+
if len(values) != 2:
|
|
122
|
+
raise CdxError(LABEL_USAGE)
|
|
123
|
+
return {"name": values[0], "label": values[1], "clear": False, "json": parsed["json"]}
|
|
124
|
+
|
|
125
|
+
|
|
110
126
|
def _parse_remove_args(args):
|
|
111
127
|
parsed = _parse_flag_args(args, {
|
|
112
128
|
"--force": {"key": "force", "type": "bool", "default": False},
|
|
@@ -186,6 +202,7 @@ def _parse_set_args(args):
|
|
|
186
202
|
def _parse_unset_args(args):
|
|
187
203
|
parsed = _parse_flag_args(args, {
|
|
188
204
|
"--power": {"key": "power", "type": "bool", "default": False},
|
|
205
|
+
"--reasoning-effort": {"key": "reasoning_effort", "type": "bool", "default": False},
|
|
189
206
|
"--permission": {"key": "permission", "type": "bool", "default": False},
|
|
190
207
|
"--fast": {"key": "fast", "type": "bool", "default": False},
|
|
191
208
|
"--rtk": {"key": "rtk", "type": "bool", "default": False},
|
|
@@ -207,8 +224,8 @@ def _parse_unset_args(args):
|
|
|
207
224
|
raise CdxError(UNSET_USAGE)
|
|
208
225
|
if not parsed["names"] and not parsed["sessions"] and not parsed["provider"]:
|
|
209
226
|
raise CdxError(UNSET_USAGE)
|
|
210
|
-
keys = ["power", "permission", "fast", "rtk", "logics", "model", "priority"] if parsed["all"] else [
|
|
211
|
-
key for key in ("power", "permission", "fast", "rtk", "logics", "model", "priority") if parsed[key]
|
|
227
|
+
keys = ["power", "reasoning_effort", "permission", "fast", "rtk", "logics", "model", "priority"] if parsed["all"] else [
|
|
228
|
+
key for key in ("power", "reasoning_effort", "permission", "fast", "rtk", "logics", "model", "priority") if parsed[key]
|
|
212
229
|
]
|
|
213
230
|
if not keys:
|
|
214
231
|
raise CdxError(UNSET_USAGE)
|
|
@@ -405,18 +422,18 @@ def _format_period_datetime(value):
|
|
|
405
422
|
return value.isoformat(timespec="seconds")
|
|
406
423
|
|
|
407
424
|
|
|
408
|
-
def _parse_history_period(parsed, now):
|
|
425
|
+
def _parse_history_period(parsed, now, usage=HISTORY_USAGE):
|
|
409
426
|
since = parsed.get("since")
|
|
410
427
|
from_value = parsed.get("from")
|
|
411
428
|
to_value = parsed.get("to")
|
|
412
429
|
if since and from_value:
|
|
413
|
-
raise CdxError("
|
|
414
|
-
start = _parse_since_value(since, now,
|
|
430
|
+
raise CdxError(f"{usage} cannot combine --since and --from.")
|
|
431
|
+
start = _parse_since_value(since, now, usage) if since else None
|
|
415
432
|
if from_value:
|
|
416
|
-
start = _parse_datetime_value(from_value,
|
|
417
|
-
end = _parse_datetime_value(to_value,
|
|
433
|
+
start = _parse_datetime_value(from_value, usage, end_of_day=False)
|
|
434
|
+
end = _parse_datetime_value(to_value, usage, end_of_day=True) if to_value else None
|
|
418
435
|
if start and end and start.timestamp() > end.timestamp():
|
|
419
|
-
raise CdxError("
|
|
436
|
+
raise CdxError(f"{usage} period start must be before period end.")
|
|
420
437
|
return {
|
|
421
438
|
"from": _format_period_datetime(start),
|
|
422
439
|
"to": _format_period_datetime(end),
|
|
@@ -480,7 +497,7 @@ def _parse_history_args(args, now=None):
|
|
|
480
497
|
"--to": {"key": "to", "type": "str", "default": None},
|
|
481
498
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
482
499
|
}, HISTORY_USAGE, positionals_key="names", max_positionals=1)
|
|
483
|
-
period = _parse_history_period(parsed, now)
|
|
500
|
+
period = _parse_history_period(parsed, now, HISTORY_USAGE)
|
|
484
501
|
return {
|
|
485
502
|
"name": parsed["names"][0] if parsed["names"] else None,
|
|
486
503
|
"limit": parsed["limit"],
|
|
@@ -498,7 +515,7 @@ def _parse_stats_args(args, now=None):
|
|
|
498
515
|
"--to": {"key": "to", "type": "str", "default": None},
|
|
499
516
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
500
517
|
}, STATS_USAGE, positionals_key="names", max_positionals=1)
|
|
501
|
-
period = _parse_history_period(parsed, now)
|
|
518
|
+
period = _parse_history_period(parsed, now, STATS_USAGE)
|
|
502
519
|
return {
|
|
503
520
|
"name": parsed["names"][0] if parsed["names"] else None,
|
|
504
521
|
"period": period,
|
|
@@ -612,6 +629,7 @@ def _parse_import_args(args):
|
|
|
612
629
|
parsed = _parse_flag_args(args, {
|
|
613
630
|
"--force": {"key": "force", "type": "bool", "default": False},
|
|
614
631
|
"--merge": {"key": "merge", "type": "bool", "default": False},
|
|
632
|
+
"--allow-authless-force": {"key": "allow_authless_force", "type": "bool", "default": False},
|
|
615
633
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
616
634
|
"--sessions": {
|
|
617
635
|
"key": "session_names",
|