cdx-manager 0.9.15 → 0.10.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 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.9.15-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.10.0-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,10 +41,12 @@ 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
+ - **Disk usage and cleanup.** `cdx disk` reports `CDX_HOME` usage, `cdx disk profiles --candidates` identifies reclaimable profile caches/logs with evidence, and `cdx clean profiles ...` applies explicit cleanup actions.
44
45
  - **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
46
  - **Logics viewer shortcut.** `cdx view` opens the Logics browser/focus viewer through `logics-manager view` when the companion CLI is installed. All viewer flags are forwarded: `--lan`, `--lan-rw`, `--focus <ref>`, `--read`, `--port`, `--host`, `--refresh-interval`, `--tls`, `--tls-cert`, `--tls-key`, `--open`, `--no-open`. `cdx view --json` reports availability and update diagnostics without opening the viewer.
46
47
  - **Shared handoff context.** Keep a per-workspace Markdown context, or build one from a source session transcript, and install it into another assistant session before switching providers or accounts.
47
48
  - **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.
49
+ - **Banked reset visibility.** Eligible Codex accounts show the number of manually redeemable bonus resets in `cdx status` and expose reset details in JSON output.
48
50
  - **Session transcript capture.** Every launch is recorded to a local log file via `script`, giving you a full terminal transcript for each session.
49
51
  - **Clean removal.** `cdx rmv` wipes a session and its entire auth directory. No orphaned files, no stale credentials.
50
52
 
@@ -265,6 +267,10 @@ cdx next
265
267
 
266
268
  # Notify when the next cooling-down assistant is ready
267
269
  cdx ready
270
+
271
+ # Inspect CDX_HOME and profile disk usage
272
+ cdx disk
273
+ cdx disk profiles --candidates
268
274
  ```
269
275
 
270
276
  ### Next-Ready Notifications
@@ -358,7 +364,10 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
358
364
  | `cdx handoff <name> [--json]` | Install the current workspace context into a target session and launch it unless `--json` is used |
359
365
  | `cdx handoff <source> <target> [--json]` | Build shared context from the source session's latest launch transcript, install it into the target session, and launch the target unless `--json` is used; supports cross-provider handoff |
360
366
  | `cdx rmv <name> [--force] [--json]` | Remove a session and its auth data (prompts for confirmation unless `--force`) |
361
- | `cdx clean [name] [--json]` | Clear launch transcript logs for one session or all sessions |
367
+ | `cdx clean [name] [--yes] [--json]` | Clear launch transcript logs for one session or all sessions after confirmation |
368
+ | `cdx clean profiles (--tmp\|--old-logs DAYS) [--yes] [--json]` | Remove explicit profile cleanup candidates after confirmation: temporary marketplace/plugin staging caches or old `.log` files |
369
+ | `cdx disk [profiles] [--candidates] [--json]` | Measure `CDX_HOME`; `profiles` includes per-profile breakdown, and `--candidates` reports reclaimable temporary caches and old logs with evidence |
370
+ | `cdx reset <name> [--yes] [--json]` | Explicitly consume one available banked Codex rate-limit reset; confirmation is required unless `--yes` is supplied |
362
371
  | `cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR\|--passphrase-stdin] [--force] [--json]` | Export sessions to a portable bundle; `--include-auth` encrypts auth data with a passphrase |
363
372
  | `cdx import <file> [--sessions a,b] [--passphrase-env VAR\|--passphrase-stdin] [--force] [--json]` | Import sessions from a bundle into the current `CDX_HOME` |
364
373
  | `cdx doctor [--json]` | Inspect CLI dependencies, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines |
@@ -394,6 +403,8 @@ Commands with machine-readable output:
394
403
  - `cdx ren ... --json`
395
404
  - `cdx rmv ... --json`
396
405
  - `cdx clean ... --json`
406
+ - `cdx disk ... --json`
407
+ - `cdx reset ... --json`
397
408
  - `cdx export ... --json`
398
409
  - `cdx import ... --json`
399
410
  - `cdx login ... --json`
@@ -602,7 +613,68 @@ All session data lives under `CDX_HOME` (default: `~/.cdx/`):
602
613
  cdx-session.log
603
614
  ```
604
615
 
605
- Session names are URL-encoded when used as directory or file names. CLI command names such as `add`, `status`, and `login` are reserved and cannot be used as session names.
616
+ Session names are URL-encoded when used as directory or file names. CLI command names such as `add`, `disk`, `status`, and `login` are reserved and cannot be used as new session names.
617
+
618
+ ### Disk maintenance
619
+
620
+ Use `cdx disk` for a total `CDX_HOME` measurement, and `cdx disk profiles` for the `profiles/` total plus a per-profile breakdown.
621
+
622
+ In an interactive terminal, long scans report their current stage and per-profile progress on stderr. JSON and redirected output remain free of progress messages.
623
+
624
+ Text output uses aligned summary tables with profile size, share of total space, and reclaimable space. Candidate details are grouped by profile with separate size, type, risk, and evidence columns.
625
+
626
+ ```bash
627
+ cdx disk
628
+ cdx disk profiles
629
+ ```
630
+
631
+ To find cleanup candidates without deleting anything:
632
+
633
+ ```bash
634
+ cdx disk profiles --candidates
635
+ cdx disk profiles --candidates --json
636
+ ```
637
+
638
+ Candidate evidence includes profile name, path, size, risk, reason, and metadata such as modified time or old-log file counts. The current cleanup candidates are intentionally limited to:
639
+
640
+ - temporary marketplace/plugin staging caches under `profiles/*/.tmp/`
641
+ - `.log` files older than the requested age inside profile `log/` directories
642
+
643
+ `cdx` also runs a passive disk cleanup check at most once per day on advisory commands such as `cdx` and `cdx status`. It writes the check timestamp and latest measurements to `~/.cdx/state/disk-cleanup-check.json`. A warning is shown only when at least one threshold is met:
644
+
645
+ - `CDX_HOME` uses 10 GB or more
646
+ - cleanup candidates can reclaim 1 GB or more
647
+ - temporary cache candidates can reclaim 500 MB or more
648
+ - old log candidates can reclaim 500 MB or more
649
+ - one profile is 2 GB or more, or one profile is at least 25% of `profiles/` when `profiles/` is already 1 GB or more
650
+
651
+ The passive warning is read-only and points to `cdx disk profiles --candidates`; it never deletes files.
652
+
653
+ Apply cleanup only with explicit actions:
654
+
655
+ ```bash
656
+ cdx clean profiles --tmp
657
+ cdx clean profiles --old-logs 30d
658
+ ```
659
+
660
+ `--tmp` removes temporary marketplace/plugin clone/backup staging directories. `--old-logs 30d` removes only `.log` files older than 30 days. The commands do not remove `auth.json`, `config.toml`, `sessions/`, SQLite state, installed `plugins/`, `skills/`, or credentials.
661
+
662
+ All `cdx clean` actions require interactive confirmation before any deletion or log truncation begins. Non-interactive scripts must pass `--yes` explicitly.
663
+
664
+ The cleanup action is selected only when `--tmp` or `--old-logs` is present. This keeps `cdx clean profiles` available for an existing session named `profiles`. If any candidate or old log cannot be removed, cleanup stops with a non-zero error that identifies the affected path; it does not report the operation as successful.
665
+
666
+ ### Banked Codex resets
667
+
668
+ Eligible Codex accounts expose manually redeemable rate-limit resets through the Codex app-server. `cdx status` shows their available count in the `RESETS` column; `cdx status <name>` also shows the nearest known expiration, and JSON status includes the available reset details supplied by Codex.
669
+
670
+ Activation is always explicit:
671
+
672
+ ```bash
673
+ cdx reset <name>
674
+ cdx reset <name> --yes --json
675
+ ```
676
+
677
+ Without `--yes`, an interactive confirmation is required. `cdx` sends an idempotency key, consumes at most one available reset, and refreshes the session status after a successful activation. It never activates a reset from `cdx status` or any background check.
606
678
 
607
679
  ---
608
680
 
@@ -0,0 +1,43 @@
1
+ # CDX Manager 0.10.0
2
+
3
+ ## Highlights
4
+
5
+ - Inspect `CDX_HOME` and per-profile disk usage with readable, progress-aware reports.
6
+ - Find evidence-backed cleanup candidates and reclaim space through confirmed actions.
7
+ - Display and explicitly activate banked Codex rate-limit resets.
8
+
9
+ ## Changes
10
+
11
+ ### Disk usage and cleanup
12
+
13
+ `cdx disk` measures total `CDX_HOME` usage. `cdx disk profiles` adds an aligned per-profile table with size and share of total space, while `--candidates` adds reclaimable totals and evidence grouped by profile.
14
+
15
+ Interactive scans report their current stage and profile count on stderr so large profiles no longer appear stalled. JSON and redirected output remain unchanged.
16
+
17
+ Cleanup candidates are intentionally limited to temporary marketplace/plugin staging directories and old `.log` files. `cdx clean profiles --tmp` and `cdx clean profiles --old-logs DAYS` require confirmation before deletion; all other `cdx clean` log-truncation actions now require confirmation as well. Non-interactive automation must opt in with `--yes`.
18
+
19
+ A passive read-only check runs at most once per day and warns when documented total, reclaimable, category, or large-profile thresholds are reached. It never removes files automatically.
20
+
21
+ ### Banked Codex resets
22
+
23
+ Eligible Codex accounts now show banked rate-limit reset counts in `cdx status`. Detailed JSON includes reset identifiers, labels, grant timestamps, and expiration timestamps when Codex provides them.
24
+
25
+ `cdx reset <name>` consumes one reset through Codex's idempotent reset-credit endpoint, requires confirmation unless `--yes` is supplied, and refreshes status after activation. Status checks never consume resets automatically.
26
+
27
+ ### Compatibility and safeguards
28
+
29
+ `disk` and `reset` are reserved for new session names. Existing sessions named `profiles` remain compatible with `cdx clean profiles`; profile-wide cleanup is selected only when `--tmp` or `--old-logs` is present.
30
+
31
+ Cleanup failures now stop with a non-zero error naming the affected path instead of silently reporting zero bytes removed.
32
+
33
+ ## Validation
34
+
35
+ - `npm run release:validate`
36
+ - `npm run lint`
37
+ - `npm test`
38
+ - `logics-manager lint --require-status`
39
+ - `logics-manager audit`
40
+ - `git diff --check`
41
+ - `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
42
+ - `python -m build`
43
+ - `python -m twine check dist/*`
@@ -0,0 +1,19 @@
1
+ # CDX Manager 0.9.16
2
+
3
+ ## Highlights
4
+
5
+ - Clear error message when a provider CLI binary is broken instead of a raw Python traceback.
6
+
7
+ ## Changes
8
+
9
+ ### Friendlier auth probe failures
10
+
11
+ Launching a session whose provider CLI exists but cannot be executed (corrupted install, wrong architecture, or a script missing its shebang — e.g. `Exec format error` on `~/.local/bin/claude`) previously crashed with an unhandled `OSError` traceback. The auth probe now catches all `OSError` failures and reports an actionable message telling the user to reinstall the provider CLI, exiting with code 126.
12
+
13
+ ## Validation
14
+
15
+ - `npm run release:validate`
16
+ - `npm run lint`
17
+ - `npm test`
18
+ - `git diff --check`
19
+ - `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
@@ -124,6 +124,14 @@
124
124
  "v0.9.14": {
125
125
  "github_tarball_sha256": "5469be5448eee56b5f0834d0c42d9ed3ca088158f08052bd8addc552635fe33d",
126
126
  "github_zip_sha256": "7eb5650cb348e3ef2a082cd7fb39d7f4c170d4a095fe88719a7ebbfb7601db48"
127
+ },
128
+ "v0.9.15": {
129
+ "github_tarball_sha256": "08e6c5a3459fdd65d1f6085ca03a3614f07b4bf44c57a995a3ef47446aa70482",
130
+ "github_zip_sha256": "c8ac01f4a65e85b417f5eba5139868ab17b8f0952bf99f17f0a79f73ef0f36d6"
131
+ },
132
+ "v0.9.16": {
133
+ "github_tarball_sha256": "0876509c201485824f6e5fe7b4a96ceb41cc68279fbe6dda4891864cf5be93ec",
134
+ "github_zip_sha256": "08072957a0421d257cecfa8bed73cceee80d1c8857c59ac45378ac8ce3e0362d"
127
135
  }
128
136
  }
129
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.9.15",
3
+ "version": "0.10.0",
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.9.15"
7
+ version = "0.10.0"
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
@@ -3,10 +3,14 @@
3
3
  import json
4
4
  import os
5
5
  import sys
6
+ from datetime import datetime, timezone
6
7
 
7
8
  from .cli_commands import (
8
9
  API_SCHEMA_VERSION,
9
10
  STATUS_USAGE,
11
+ _collect_profile_cleanup_candidates,
12
+ _directory_child_sizes,
13
+ _directory_size_bytes,
10
14
  handle_add,
11
15
  handle_can_resume,
12
16
  handle_clean,
@@ -15,6 +19,7 @@ from .cli_commands import (
15
19
  handle_context,
16
20
  handle_copy,
17
21
  handle_disable,
22
+ handle_disk,
18
23
  handle_doctor,
19
24
  handle_enable,
20
25
  handle_export,
@@ -31,6 +36,7 @@ from .cli_commands import (
31
36
  handle_remove,
32
37
  handle_rename,
33
38
  handle_repair,
39
+ handle_reset,
34
40
  handle_resume,
35
41
  handle_run,
36
42
  handle_run_report,
@@ -44,6 +50,9 @@ from .cli_commands import (
44
50
  handle_update,
45
51
  handle_view,
46
52
  )
53
+ from .cli_commands import (
54
+ _format_bytes as _format_disk_bytes,
55
+ )
47
56
  from .cli_render import (
48
57
  _format_sessions,
49
58
  _pad_table,
@@ -66,7 +75,7 @@ from .status_view import (
66
75
  )
67
76
  from .update_check import check_for_update, check_logics_manager_for_update
68
77
 
69
- VERSION = "0.9.15"
78
+ VERSION = "0.10.0"
70
79
 
71
80
  # Public surface: this module is a facade. Names below are imported above
72
81
  # purely to be re-exported (consumed by tests and external callers); listing
@@ -94,6 +103,7 @@ _COMMAND_HANDLERS = {
94
103
  "config": handle_config,
95
104
  "configs": handle_configs,
96
105
  "context": handle_context,
106
+ "disk": handle_disk,
97
107
  "doctor": handle_doctor,
98
108
  "disable": handle_disable,
99
109
  "enable": handle_enable,
@@ -107,6 +117,7 @@ _COMMAND_HANDLERS = {
107
117
  "next": handle_next,
108
118
  "notify": handle_notify,
109
119
  "repair": handle_repair,
120
+ "reset": handle_reset,
110
121
  "rename": handle_rename,
111
122
  "resume": handle_resume,
112
123
  "can-resume": handle_can_resume,
@@ -165,12 +176,14 @@ def _print_help(use_color=False):
165
176
  f" {_style('cdx status --small|-s [--refresh|--cached] [--timeout SECONDS]', '36', use_color)}",
166
177
  f" {_style('cdx status <name> [--json] [--refresh|--cached] [--timeout SECONDS]', '36', use_color)}",
167
178
  f" {_style('cdx next [--json] [--refresh]', '36', use_color)}",
179
+ f" {_style('cdx reset <name> [--yes] [--json]', '36', use_color)}",
168
180
  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)}",
169
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)}",
170
182
  f" {_style('cdx runs [--limit N] --json', '36', use_color)}",
171
183
  f" {_style('cdx run-status <run_id> --json', '36', use_color)}",
172
184
  f" {_style('cdx run-report <run_id> --json', '36', use_color)}",
173
185
  f" {_style('cdx context show|path|init|edit|clear|set [text...] [--json]', '36', use_color)}",
186
+ f" {_style('cdx disk [profiles] [--candidates] [--json]', '36', use_color)}",
174
187
  f" {_style('cdx config <name> [--json]', '36', use_color)}",
175
188
  f" {_style('cdx configs [--json]', '36', use_color)}",
176
189
  f" {_style('cdx power|perm|fast|model <name|all|provider:PROVIDER|a,b> <value|default> [--json]', '36', use_color)}",
@@ -191,7 +204,8 @@ def _print_help(use_color=False):
191
204
  f" {_style('cdx disable <name> [--json]', '36', use_color)}",
192
205
  f" {_style('cdx enable <name> [--json]', '36', use_color)}",
193
206
  f" {_style('cdx rmv <name> [--force] [--json]', '36', use_color)}",
194
- f" {_style('cdx clean [name] [--json]', '36', use_color)}",
207
+ f" {_style('cdx clean [name] [--yes] [--json]', '36', use_color)}",
208
+ f" {_style('cdx clean profiles (--tmp|--old-logs DAYS) [--yes] [--json]', '36', use_color)}",
195
209
  f" {_style('cdx export <file> [--include-auth] [--sessions a,b] [--passphrase-env VAR|--passphrase-stdin] [--force] [--json]', '36', use_color)}",
196
210
  f" {_style('cdx import <file> [--sessions a,b] [--passphrase-env VAR|--passphrase-stdin] [--force|--merge] [--json]', '36', use_color)}",
197
211
  f" {_style('cdx doctor [--json]', '36', use_color)}",
@@ -264,9 +278,154 @@ def _get_update_notices(service, env, options):
264
278
  )
265
279
  if logics_notice:
266
280
  notices.append(logics_notice)
281
+ disk_notice = _get_disk_cleanup_notice(service, options)
282
+ if disk_notice:
283
+ notices.append(disk_notice)
267
284
  return notices
268
285
 
269
286
 
287
+ _DISK_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
288
+ _DISK_HOME_THRESHOLD_BYTES = 10 * 1024 * 1024 * 1024
289
+ _DISK_RECLAIMABLE_THRESHOLD_BYTES = 1 * 1024 * 1024 * 1024
290
+ _DISK_CATEGORY_THRESHOLD_BYTES = 500 * 1024 * 1024
291
+ _DISK_PROFILE_THRESHOLD_BYTES = 2 * 1024 * 1024 * 1024
292
+
293
+
294
+ def _now_utc(options):
295
+ now_fn = options.get("now")
296
+ value = now_fn() if callable(now_fn) else None
297
+ if isinstance(value, datetime):
298
+ return value.astimezone(timezone.utc)
299
+ return datetime.now(timezone.utc)
300
+
301
+
302
+ def _read_json_file(path):
303
+ try:
304
+ with open(path, encoding="utf-8") as handle:
305
+ return json.load(handle)
306
+ except (OSError, ValueError):
307
+ return {}
308
+
309
+
310
+ def _write_json_file(path, payload):
311
+ os.makedirs(os.path.dirname(path), exist_ok=True)
312
+ tmp_path = f"{path}.tmp"
313
+ with open(tmp_path, "w", encoding="utf-8") as handle:
314
+ json.dump(payload, handle, indent=2, sort_keys=True)
315
+ handle.write("\n")
316
+ os.replace(tmp_path, path)
317
+
318
+
319
+ def _parse_iso_datetime(value):
320
+ if not value:
321
+ return None
322
+ try:
323
+ return datetime.fromisoformat(str(value).replace("Z", "+00:00")).astimezone(timezone.utc)
324
+ except ValueError:
325
+ return None
326
+
327
+
328
+ def _get_disk_cleanup_notice(service, options):
329
+ checker = options.get("checkDiskCleanup")
330
+ if callable(checker):
331
+ return checker(service, options)
332
+
333
+ base_dir = service["base_dir"]
334
+ state_path = os.path.join(base_dir, "state", "disk-cleanup-check.json")
335
+ now = _now_utc(options)
336
+ state = _read_json_file(state_path)
337
+ last_checked = _parse_iso_datetime(state.get("last_checked_at"))
338
+ if last_checked and (now - last_checked).total_seconds() < _DISK_CHECK_INTERVAL_SECONDS:
339
+ return None
340
+
341
+ runner = options.get("diskUsageRunner")
342
+ try:
343
+ home_bytes = _directory_size_bytes(base_dir, runner=runner)
344
+ profiles_dir = os.path.join(base_dir, "profiles")
345
+ profile_rows = _directory_child_sizes(profiles_dir, runner=runner)
346
+ profile_total = sum(row["bytes"] for row in profile_rows)
347
+ candidates = _collect_profile_cleanup_candidates(base_dir, runner=runner, now=now.timestamp())
348
+ except Exception:
349
+ return None
350
+
351
+ reclaimable_bytes = sum(item["bytes"] for item in candidates)
352
+ tmp_bytes = sum(item["bytes"] for item in candidates if item["kind"].startswith("tmp-"))
353
+ old_log_bytes = sum(item["bytes"] for item in candidates if item["kind"].startswith("old-logs-"))
354
+ large_profiles = [
355
+ {
356
+ "name": row["name"],
357
+ "bytes": row["bytes"],
358
+ "size": _format_disk_bytes(row["bytes"]),
359
+ "share_pct": round((row["bytes"] / profile_total) * 100, 1) if profile_total else 0,
360
+ }
361
+ for row in profile_rows
362
+ if row["bytes"] >= _DISK_PROFILE_THRESHOLD_BYTES
363
+ or (
364
+ profile_total >= _DISK_RECLAIMABLE_THRESHOLD_BYTES
365
+ and row["bytes"] / profile_total >= 0.25
366
+ )
367
+ ]
368
+ triggered = (
369
+ home_bytes >= _DISK_HOME_THRESHOLD_BYTES
370
+ or reclaimable_bytes >= _DISK_RECLAIMABLE_THRESHOLD_BYTES
371
+ or tmp_bytes >= _DISK_CATEGORY_THRESHOLD_BYTES
372
+ or old_log_bytes >= _DISK_CATEGORY_THRESHOLD_BYTES
373
+ or bool(large_profiles)
374
+ )
375
+ state_payload = {
376
+ "last_checked_at": now.isoformat(),
377
+ "home_bytes": home_bytes,
378
+ "reclaimable_bytes": reclaimable_bytes,
379
+ "tmp_bytes": tmp_bytes,
380
+ "old_log_bytes": old_log_bytes,
381
+ "large_profiles": large_profiles,
382
+ }
383
+ try:
384
+ _write_json_file(state_path, state_payload)
385
+ except OSError:
386
+ pass
387
+ if not triggered:
388
+ return None
389
+
390
+ if reclaimable_bytes > 0:
391
+ message = (
392
+ f"Disk cleanup available: {_format_disk_bytes(reclaimable_bytes)} reclaimable "
393
+ f"in CDX profiles. Inspect: cdx disk profiles --candidates"
394
+ )
395
+ else:
396
+ message = (
397
+ f"CDX_HOME uses {_format_disk_bytes(home_bytes)}. "
398
+ "Inspect: cdx disk profiles --candidates"
399
+ )
400
+ if tmp_bytes >= _DISK_CATEGORY_THRESHOLD_BYTES:
401
+ message = (
402
+ f"{message}. Safe temp cleanup: {_format_disk_bytes(tmp_bytes)} "
403
+ "with cdx clean profiles --tmp"
404
+ )
405
+ if old_log_bytes >= _DISK_CATEGORY_THRESHOLD_BYTES:
406
+ message = (
407
+ f"{message}. Old logs: {_format_disk_bytes(old_log_bytes)} "
408
+ "with cdx clean profiles --old-logs 30d"
409
+ )
410
+ return {
411
+ "tool": "cdx-disk",
412
+ "code": "disk_cleanup_available",
413
+ "message": message,
414
+ "reclaimable_bytes": reclaimable_bytes,
415
+ "reclaimable_size": _format_disk_bytes(reclaimable_bytes),
416
+ "home_bytes": home_bytes,
417
+ "home_size": _format_disk_bytes(home_bytes),
418
+ "tmp_bytes": tmp_bytes,
419
+ "tmp_size": _format_disk_bytes(tmp_bytes),
420
+ "old_log_bytes": old_log_bytes,
421
+ "old_log_size": _format_disk_bytes(old_log_bytes),
422
+ "large_profiles": large_profiles,
423
+ "inspect_command": "cdx disk profiles --candidates",
424
+ "cleanup_tmp_command": "cdx clean profiles --tmp" if tmp_bytes >= _DISK_CATEGORY_THRESHOLD_BYTES else None,
425
+ "cleanup_old_logs_command": "cdx clean profiles --old-logs 30d" if old_log_bytes >= _DISK_CATEGORY_THRESHOLD_BYTES else None,
426
+ }
427
+
428
+
270
429
  def _update_warning_payload(notices):
271
430
  if isinstance(notices, dict):
272
431
  notices = [notices]
@@ -274,6 +433,13 @@ def _update_warning_payload(notices):
274
433
  return []
275
434
  warnings = []
276
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
277
443
  tool = notice.get("tool") or "cdx-manager"
278
444
  current = notice.get("current_version") or VERSION
279
445
  command = notice.get("update_command") or ("cdx update" if tool == "cdx-manager" else None)
package/src/cli_args.py CHANGED
@@ -13,6 +13,7 @@ from .provider_runtime import _normalize_reasoning_effort
13
13
 
14
14
  STATUS_USAGE = "Usage: cdx status [--json] [--refresh|--cached] [--timeout SECONDS] | cdx status --small|-s [--refresh|--cached] [--timeout SECONDS] | cdx status <name> [--json] [--refresh|--cached] [--timeout SECONDS]"
15
15
  DOCTOR_USAGE = "Usage: cdx doctor [--json]"
16
+ DISK_USAGE = "Usage: cdx disk [profiles] [--candidates] [--json]"
16
17
  REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
17
18
  UPDATE_USAGE = "Usage: cdx update [--check] [--yes] [--json] [--version TAG]"
18
19
  EXPORT_USAGE = "Usage: cdx export <file> [--include-auth] [--force] [--json] [--sessions name1,name2] [--passphrase-env VAR|--passphrase-stdin]"