caffeinated-whale-cli 2.1.0__tar.gz → 2.2.0__tar.gz
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.
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/PKG-INFO +79 -6
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/README.md +78 -5
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/pyproject.toml +1 -1
- caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/axi.py +34 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/console.html +929 -280
- caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/commands/doctor.py +79 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/init.py +1 -1
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/open.py +7 -5
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/restore.py +22 -14
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/serve.py +131 -3
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/docker.py +105 -12
- caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/core/doctor.py +604 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/init.py +5 -3
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/inspect.py +12 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/open.py +15 -14
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/scale.py +1 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/start.py +3 -3
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/version.py +20 -5
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/main.py +22 -6
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/config_utils.py +11 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/docker_utils.py +48 -19
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +2 -0
- caffeinated_whale_cli-2.1.0/src/caffeinated_whale_cli/__init__.py +0 -1
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/LICENSE +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/setup.cfg +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/apps.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/config.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/rm.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/run.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/start.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/stop.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/update.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/apps.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/bench_ops.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/config.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/resolvers.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/rm.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/stop.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/update.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/where.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/bench_labels.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/cache.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/db_utils.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/startup.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-2.1.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/vscode_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: caffeinated-whale-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A CLI tool to help manage Frappe Docker instances.
|
|
5
5
|
Author-email: Christopher McKay <mckay.christopher73@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -40,7 +40,9 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
# Caffeinated Whale CLI
|
|
42
42
|
|
|
43
|
-
A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development.
|
|
43
|
+
A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development.
|
|
44
|
+
The repository also contains the Phase 1 Tauri desktop shell, which runs the existing Console GUI over the same Python core.
|
|
45
|
+
See [`desktop/README.md`](./desktop/README.md) for its current source-build status, supported platforms, and build instructions.
|
|
44
46
|
|
|
45
47
|
## Features
|
|
46
48
|
|
|
@@ -59,15 +61,21 @@ A command-line interface (CLI) for managing Frappe/ERPNext Docker instances duri
|
|
|
59
61
|
- **Private App Repos** - `apps install`/`apps update` authenticate private GitHub/GitLab app fetches through your host's already-signed-in `gh`/`glab`, with no token ever entering the container
|
|
60
62
|
- **Update Management** - App updates with automatic migrations and lock cleanup
|
|
61
63
|
- **Self-Update** - Upgrade cwcli itself to the latest release with `cwcli self-update` (install-method aware)
|
|
64
|
+
- **Environment Preflight** - Read-only checks for Docker, disk space, and tool dependencies with `cwcli doctor` (chainable exit code, `cwcli axi doctor` for agents; the shared cache layer may idempotently initialize cwcli's private cache directory while building the command tree)
|
|
62
65
|
- **Update Notices** - A passive, once/day "a newer cwcli is available" hint on stderr, shown only to a human at a TTY
|
|
63
66
|
- **Auto-Inspection** - Background process to keep project cache fresh automatically
|
|
64
67
|
- **System Integration** - Auto-start on system boot with platform-specific configurations
|
|
65
68
|
- **Contextual Tips** - Helpful tips displayed during long-running operations to help you discover features
|
|
69
|
+
- **Desktop Console** - A source-build Tauri 2 shell for the existing Console GUI on Linux and Windows; installer bundles are deferred to a later phase
|
|
66
70
|
|
|
67
71
|
## Installation
|
|
68
72
|
|
|
69
73
|
`cwcli` requires Python 3.10+.
|
|
70
74
|
|
|
75
|
+
`cwcli init` and `cwcli scale` also require the Docker CLI with the Docker Compose v2 plugin (`docker compose version`).
|
|
76
|
+
Docker Desktop includes the plugin; on a bare Docker Engine host, install the `docker-compose-plugin` package.
|
|
77
|
+
Both commands verify this prerequisite before changing project state and return an actionable precondition error when it is unavailable.
|
|
78
|
+
|
|
71
79
|
### With uv (recommended)
|
|
72
80
|
|
|
73
81
|
[uv](https://docs.astral.sh/uv/) installs `cwcli` into an isolated environment and puts the `cwcli` command on your `PATH`, without touching your system or project Python.
|
|
@@ -1541,7 +1549,9 @@ Backup: 20251112_105638-development_localhost-database.sql.gz
|
|
|
1541
1549
|
```
|
|
1542
1550
|
|
|
1543
1551
|
For scripted (non-interactive) receives, pass `-y`/`--yes` to skip the confirmation and the missing-apps prompt. Without a TTY and without `--yes`, cwcli refuses and exits non-zero rather than silently overwriting the site's data.
|
|
1544
|
-
Before
|
|
1552
|
+
Before transferring, cwcli requires at least 2 GiB free in its temporary [transfer directory](#architecture).
|
|
1553
|
+
It checks before the sender stages a backup and before the receiver downloads one.
|
|
1554
|
+
The receiver also validates the target site before downloading.
|
|
1545
1555
|
The backup may require more space.
|
|
1546
1556
|
Set `TMPDIR`, `TEMP`, or `TMP` to place transfers on a larger filesystem, in that priority order, or relocate all cwcli data with `CWCLI_HOME`.
|
|
1547
1557
|
Copying the downloaded backup into the container streams through a bounded pipe instead of creating another full host-side copy.
|
|
@@ -1777,6 +1787,60 @@ cwcli status -w --interval 5 frappe-one # refresh every 5s
|
|
|
1777
1787
|
|
|
1778
1788
|
---
|
|
1779
1789
|
|
|
1790
|
+
### `doctor` - Preflight Your Environment
|
|
1791
|
+
|
|
1792
|
+
Checks whether cwcli can operate on this machine at all - Docker, cwcli's own
|
|
1793
|
+
on-disk footprint, disk space, sendme, and the `gh`/`glab` credential-bridge
|
|
1794
|
+
dependencies - as a fast, read-only report. `status`/`inspect` answer "is this
|
|
1795
|
+
instance healthy"; `doctor` answers "will the next command even work here".
|
|
1796
|
+
|
|
1797
|
+
```bash
|
|
1798
|
+
cwcli doctor [OPTIONS]
|
|
1799
|
+
```
|
|
1800
|
+
|
|
1801
|
+
**Options:**
|
|
1802
|
+
|
|
1803
|
+
| Option | Description |
|
|
1804
|
+
|--------|-------------|
|
|
1805
|
+
| `-v`, `--verbose` | Show each check's stable id (e.g. `[c2]`) alongside its title |
|
|
1806
|
+
|
|
1807
|
+
**Read-only checks:** doctor never starts a container, installs anything, or
|
|
1808
|
+
writes a config file.
|
|
1809
|
+
There is one accepted shared-infrastructure exception before the checks run:
|
|
1810
|
+
building the command tree for `cwcli doctor` or `cwcli axi doctor` imports the
|
|
1811
|
+
pre-existing cache layer, which may idempotently create cwcli's private cache
|
|
1812
|
+
directory and apply mode `0700`.
|
|
1813
|
+
It does not touch project data.
|
|
1814
|
+
Making that initialization lazy is tracked separately as
|
|
1815
|
+
`cwcli-cache-dir-lazy-init`; this caveat should disappear when that lands.
|
|
1816
|
+
When a direct remedy is available, the finding includes the command or action
|
|
1817
|
+
to apply yourself.
|
|
1818
|
+
|
|
1819
|
+
**Checks always run in full** (no tiers, no selection flags), grouped into
|
|
1820
|
+
Docker, cwcli, Storage, Transfer, and Git hosting, plus a cross-instance
|
|
1821
|
+
port-range collision check under Instances. Each check is `pass`, `warn`, or
|
|
1822
|
+
`fail`; an absent *optional* tool (sendme, `gh`, `glab`) is `warn`, never
|
|
1823
|
+
`fail`.
|
|
1824
|
+
|
|
1825
|
+
**Exit codes:** `0` when every check is pass/warn (a warning never blocks -
|
|
1826
|
+
doctor is a chainable preflight gate), `1` when any check fails.
|
|
1827
|
+
|
|
1828
|
+
**Example:**
|
|
1829
|
+
|
|
1830
|
+
```bash
|
|
1831
|
+
cwcli doctor # grouped glyph checklist
|
|
1832
|
+
cwcli doctor -v # same, with each check's stable id shown
|
|
1833
|
+
```
|
|
1834
|
+
|
|
1835
|
+
For agents, `cwcli axi doctor` emits the same checks as one TOON document with
|
|
1836
|
+
a machine-readable `status` token per row.
|
|
1837
|
+
The version row also carries `version_verified`, so `status: pass` with
|
|
1838
|
+
`version_verified: false` is explicitly unverified rather than a claim that the
|
|
1839
|
+
installed version is current.
|
|
1840
|
+
See [the `axi` surface](#for-agents-the-cwcli-axi-surface).
|
|
1841
|
+
|
|
1842
|
+
---
|
|
1843
|
+
|
|
1780
1844
|
### `config` - Manage Configuration
|
|
1781
1845
|
|
|
1782
1846
|
Manages the CLI configuration and cache.
|
|
@@ -1963,7 +2027,11 @@ cwcli self-update --no-cache
|
|
|
1963
2027
|
|
|
1964
2028
|
**Passive update notices:**
|
|
1965
2029
|
|
|
1966
|
-
Every `cwcli` and `cwcli axi` run also does a passive, cache-only check: if a newer release is already known (from the same ≤1-day cache `self-update`/`--check` share) and you're at an interactive terminal, cwcli prints a one-line "a newer cwcli is available" hint
|
|
2030
|
+
Every `cwcli` and `cwcli axi` run except the strictly read-only doctor commands also does a passive, cache-only check: if a newer release is already known (from the same ≤1-day cache `self-update`/`--check` share) and you're at an interactive terminal, cwcli prints a one-line "a newer cwcli is available" hint with the right upgrade command for how you installed it to stderr.
|
|
2031
|
+
`cwcli doctor` and `cwcli axi doctor` skip this check because a stale or missing version cache would otherwise launch a background refresh that writes it.
|
|
2032
|
+
The notice never makes a blocking network call itself: PyPI is actually re-checked at most once/day, via a detached background refresh kicked off whenever the cache is missing or stale, so this never delays a command; until that refresh lands, the hint keeps showing on each eligible run.
|
|
2033
|
+
It never touches stdout, so it is invisible to pipes, scripts, CI, and `cwcli axi`'s TOON output.
|
|
2034
|
+
Set `CWCLI_NO_UPDATE_CHECK=1` to suppress it entirely.
|
|
1967
2035
|
|
|
1968
2036
|
---
|
|
1969
2037
|
|
|
@@ -2185,9 +2253,14 @@ cwcli axi self-update --check
|
|
|
2185
2253
|
# auto-inspect state (config, live daemon, boot hook - reported separately),
|
|
2186
2254
|
# tips, and the config-file/cache-DB locations.
|
|
2187
2255
|
cwcli axi config
|
|
2256
|
+
|
|
2257
|
+
# System-wide environment preflight as one TOON document: Docker, cwcli's own
|
|
2258
|
+
# footprint, disk space, sendme, gh/glab. Machine-readable status per check;
|
|
2259
|
+
# exits 0 unless a check FAILS (a warning never blocks a chained preflight).
|
|
2260
|
+
cwcli axi doctor
|
|
2188
2261
|
```
|
|
2189
2262
|
|
|
2190
|
-
`cwcli axi ls`, `cwcli axi where`, `cwcli axi backup`, `cwcli axi unlock`, `cwcli axi stop`, `cwcli axi start`, `cwcli axi status`, `cwcli axi restart`, `cwcli axi inspect`, `cwcli axi benches`, and `cwcli axi label` run on the same logic core as their human counterparts; only the output (always TOON, never JSON) and choice-handling differ. `cwcli axi start` never prompts: an ambiguous multi-bench project is a `--bench` usage error (exit 2), and an unresolved port conflict is a `CONFLICT` error naming `--yes` (exit 1). `cwcli axi unlock` follows `cwcli axi backup`'s conventions exactly: an ambiguous multi-bench project is a `--bench` usage error (exit 2) and a stopped instance is a usage error pointing at `cwcli start` (exit 2). `cwcli axi stop` is idempotent for an agent - stopping an already-stopped project is a success, not an error. `cwcli axi where` answers from the cache, which outlives the instances it describes, so every match carries `project_state` (`present`, `absent`, or `unverified`) and the document carries `verified`: an agent must read those before acting on a hit, because an `absent` row describes an instance that no longer exists and an `unverified` row means the liveness check could not run. An unreachable Docker daemon degrades the whole answer to `unverified` rather than vouching for it, and `--no-verify` skips the check (reporting `unverified`) when a caller has already established liveness. `cwcli axi status` always exits 0, leading with the instance `overall` aggregate (`offline`/`online`/`running`/`degraded`). It reports **every** bench, in one document, under `benches[N]` - with no `--bench` it used to refuse a multi-bench project with exit 2 and send you off to `cwcli axi benches` to poll once per bench, reassembling the instance view from documents that never said which bench they described. The shape is uniform, so `supervisor_up`, `web_http_code`, `processes` and `not_cwcli_supervised` live inside `benches[i]` even for a single-bench project (`benches[0].processes`), and there is one parse path rather than a branch on bench count. Each bench also carries `bench_present` (`present`/`absent`/`unverified`) - the bench LIST comes from the cache, and a bench whose directory was deleted has no marker and no supervisord, which is indistinguishable from one that was simply never started, so it used to read as `online`; the token answers that, and `overall` keeps its four tokens. Each bench also carries `index`, `bench_path`, `label`, its own `overall`, `web_port`/`web_port_verified` (the port that bench's `web_http_code` was actually measured on), and `web_site` (the site it was measured FOR - Frappe routes by `Host`, so the code belongs to that site, and a probe naming no site is answered `404` by a healthy bench). When that port cannot be read it is `null`/`false` and **no probe was made** - cwcli never falls back to `:8000`, because on a multi-bench instance that reports a different bench's web server as this one's. A stopped instance carries `benches: []`. `cwcli axi stop` takes `--bench <index|label>` to stop ONE bench's dev processes, leaving sibling benches and every container running - the inverse of `cwcli axi start --bench`; without it, it stops the whole instance's containers, and either form is idempotent. `cwcli axi restart` requires `--process`; an unknown/ambiguous process is a usage error listing the valid labels (exit 2). `cwcli axi inspect` serves whichever freshness tier answers the request (`served_from` names it) and, unlike every other bench-scoped verb, has deliberately NO `--yes` - a stopped project on the refresh path is a usage error (exit 2) naming `cwcli start`, and a drift escalation that can no longer discover the bench serves the cached data with `degraded: true` and a warning (exit 0). Every site also carries `installed_apps_verified`: only a full read actually re-observes an app's installed version and git ref, so a cache or partial read carries the remembered list forward unverified and, when that list is non-empty, adds a warning naming `--update` - an agent must not treat an unverified `installed_apps` entry as the live git state. `cwcli axi benches` is the discovery verb behind every other verb's `--bench`: when a bench-scoped verb reports "multiple benches; pass `--bench <index|label>`", this is what tells you the valid values, and a project that has never been inspected is a structured error naming `cwcli axi inspect` rather than an empty list. Its rows come from the cache, which outlives the benches it describes, so every row carries `state` (`present`, `absent`, or `unverified`) and the document carries `verified` - the same contract `cwcli axi where` uses, cross-checked here against the running container in one exec. An agent must read `state` before acting on a row: `absent` means that bench directory no longer exists, and `unverified` means the check could not run (a stopped project, an unreachable daemon, or `--no-verify`) - never a confirmation. A stale row is reported, never pruned; `cwcli axi inspect <project> --update` is what refreshes it. `cwcli axi label` never starts a stopped project (the label marker lives inside the bench), and listing is deliberately `cwcli axi benches` rather than a mode of the mutation verb. `cwcli axi self-update --check` is read-only and **exits 0 whenever the check succeeds, including when an update is available** - the answer is the `is_outdated` field, not the exit code, because on the agent surface a non-zero exit means an error. This deliberately differs from the human `cwcli self-update --check`, which exits 1 when an update is available so shell scripts can gate on it; the mutating `cwcli axi self-update` is deliberately not offered. `cwcli axi config` is the read-only counterpart of `cwcli config show`: one TOON document carrying the search paths, all three auto-inspect state stores, the tips setting, and the file locations. **No config-mutating axi verbs exist** (no `paths add`/`remove`, no `cache clear`, no `auto-inspect enable`/`disable`): an agent rewriting the user's search paths or wiping the cache is a product decision that deserves its own evidence, so mutations stay on the human `cwcli config` surface (whose reads all have `--json`).
|
|
2263
|
+
`cwcli axi ls`, `cwcli axi where`, `cwcli axi backup`, `cwcli axi unlock`, `cwcli axi stop`, `cwcli axi start`, `cwcli axi status`, `cwcli axi restart`, `cwcli axi inspect`, `cwcli axi benches`, and `cwcli axi label` run on the same logic core as their human counterparts; only the output (always TOON, never JSON) and choice-handling differ. `cwcli axi start` never prompts: an ambiguous multi-bench project is a `--bench` usage error (exit 2), and an unresolved port conflict is a `CONFLICT` error naming `--yes` (exit 1). `cwcli axi unlock` follows `cwcli axi backup`'s conventions exactly: an ambiguous multi-bench project is a `--bench` usage error (exit 2) and a stopped instance is a usage error pointing at `cwcli start` (exit 2). `cwcli axi stop` is idempotent for an agent - stopping an already-stopped project is a success, not an error. `cwcli axi where` answers from the cache, which outlives the instances it describes, so every match carries `project_state` (`present`, `absent`, or `unverified`) and the document carries `verified`: an agent must read those before acting on a hit, because an `absent` row describes an instance that no longer exists and an `unverified` row means the liveness check could not run. An unreachable Docker daemon degrades the whole answer to `unverified` rather than vouching for it, and `--no-verify` skips the check (reporting `unverified`) when a caller has already established liveness. `cwcli axi status` always exits 0, leading with the instance `overall` aggregate (`offline`/`online`/`running`/`degraded`). It reports **every** bench, in one document, under `benches[N]` - with no `--bench` it used to refuse a multi-bench project with exit 2 and send you off to `cwcli axi benches` to poll once per bench, reassembling the instance view from documents that never said which bench they described. The shape is uniform, so `supervisor_up`, `web_http_code`, `processes` and `not_cwcli_supervised` live inside `benches[i]` even for a single-bench project (`benches[0].processes`), and there is one parse path rather than a branch on bench count. Each bench also carries `bench_present` (`present`/`absent`/`unverified`) - the bench LIST comes from the cache, and a bench whose directory was deleted has no marker and no supervisord, which is indistinguishable from one that was simply never started, so it used to read as `online`; the token answers that, and `overall` keeps its four tokens. Each bench also carries `index`, `bench_path`, `label`, its own `overall`, `web_port`/`web_port_verified` (the port that bench's `web_http_code` was actually measured on), and `web_site` (the site it was measured FOR - Frappe routes by `Host`, so the code belongs to that site, and a probe naming no site is answered `404` by a healthy bench). When that port cannot be read it is `null`/`false` and **no probe was made** - cwcli never falls back to `:8000`, because on a multi-bench instance that reports a different bench's web server as this one's. A stopped instance carries `benches: []`. `cwcli axi stop` takes `--bench <index|label>` to stop ONE bench's dev processes, leaving sibling benches and every container running - the inverse of `cwcli axi start --bench`; without it, it stops the whole instance's containers, and either form is idempotent. `cwcli axi restart` requires `--process`; an unknown/ambiguous process is a usage error listing the valid labels (exit 2). `cwcli axi inspect` serves whichever freshness tier answers the request (`served_from` names it) and, unlike every other bench-scoped verb, has deliberately NO `--yes` - a stopped project on the refresh path is a usage error (exit 2) naming `cwcli start`, and a drift escalation that can no longer discover the bench serves the cached data with `degraded: true` and a warning (exit 0). Every site also carries `installed_apps_verified`: only a full read actually re-observes an app's installed version and git ref, so a cache or partial read carries the remembered list forward unverified and, when that list is non-empty, adds a warning naming `--update` - an agent must not treat an unverified `installed_apps` entry as the live git state. `cwcli axi benches` is the discovery verb behind every other verb's `--bench`: when a bench-scoped verb reports "multiple benches; pass `--bench <index|label>`", this is what tells you the valid values, and a project that has never been inspected is a structured error naming `cwcli axi inspect` rather than an empty list. Its rows come from the cache, which outlives the benches it describes, so every row carries `state` (`present`, `absent`, or `unverified`) and the document carries `verified` - the same contract `cwcli axi where` uses, cross-checked here against the running container in one exec. An agent must read `state` before acting on a row: `absent` means that bench directory no longer exists, and `unverified` means the check could not run (a stopped project, an unreachable daemon, or `--no-verify`) - never a confirmation. A stale row is reported, never pruned; `cwcli axi inspect <project> --update` is what refreshes it. `cwcli axi label` never starts a stopped project (the label marker lives inside the bench), and listing is deliberately `cwcli axi benches` rather than a mode of the mutation verb. `cwcli axi self-update --check` is read-only and **exits 0 whenever the check succeeds, including when an update is available** - the answer is the `is_outdated` field, not the exit code, because on the agent surface a non-zero exit means an error. This deliberately differs from the human `cwcli self-update --check`, which exits 1 when an update is available so shell scripts can gate on it; the mutating `cwcli axi self-update` is deliberately not offered. `cwcli axi config` is the read-only counterpart of `cwcli config show`: one TOON document carrying the search paths, all three auto-inspect state stores, the tips setting, and the file locations. **No config-mutating axi verbs exist** (no `paths add`/`remove`, no `cache clear`, no `auto-inspect enable`/`disable`): an agent rewriting the user's search paths or wiping the cache is a product decision that deserves its own evidence, so mutations stay on the human `cwcli config` surface (whose reads all have `--json`). `cwcli axi doctor` is the read-only, system-wide counterpart of `cwcli doctor`: one TOON document listing every check with a machine-readable `status` (`pass`/`warn`/`fail`) an agent gates on directly, never by parsing the `detail` text. It exits 0 whenever every check is pass/warn and non-zero only when a check fails, exactly matching the human command, so a fleet can run it before driving any instance and `&&`-chain on the result.
|
|
2191
2264
|
|
|
2192
2265
|
`cwcli axi url <project>` answers two questions no other `axi` verb does: what HOST URL a bench actually serves on, and whether it is answering HTTP right now.
|
|
2193
2266
|
`cwcli axi status`'s `web_http_code` is measured against the bench's CONTAINER-internal port and never states the host address; the host URL is computed only by `cwcli open`'s success banner and the human `cwcli init` banner, neither reachable from `cwcli axi`, so getting either answer meant dropping to a raw `docker inspect`/`curl` against the container.
|
|
@@ -2362,7 +2435,7 @@ The CLI uses:
|
|
|
2362
2435
|
- **Questionary** - Interactive prompts
|
|
2363
2436
|
- **Peewee ORM** - SQLite-based caching
|
|
2364
2437
|
|
|
2365
|
-
**Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and
|
|
2438
|
+
**Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and the Console GUI are all thin frontends over one implementation.
|
|
2366
2439
|
`backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, and `rm-site` are migrated onto it so far.
|
|
2367
2440
|
|
|
2368
2441
|
**Data Directories:**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Caffeinated Whale CLI
|
|
2
2
|
|
|
3
|
-
A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development.
|
|
3
|
+
A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development.
|
|
4
|
+
The repository also contains the Phase 1 Tauri desktop shell, which runs the existing Console GUI over the same Python core.
|
|
5
|
+
See [`desktop/README.md`](./desktop/README.md) for its current source-build status, supported platforms, and build instructions.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
@@ -19,15 +21,21 @@ A command-line interface (CLI) for managing Frappe/ERPNext Docker instances duri
|
|
|
19
21
|
- **Private App Repos** - `apps install`/`apps update` authenticate private GitHub/GitLab app fetches through your host's already-signed-in `gh`/`glab`, with no token ever entering the container
|
|
20
22
|
- **Update Management** - App updates with automatic migrations and lock cleanup
|
|
21
23
|
- **Self-Update** - Upgrade cwcli itself to the latest release with `cwcli self-update` (install-method aware)
|
|
24
|
+
- **Environment Preflight** - Read-only checks for Docker, disk space, and tool dependencies with `cwcli doctor` (chainable exit code, `cwcli axi doctor` for agents; the shared cache layer may idempotently initialize cwcli's private cache directory while building the command tree)
|
|
22
25
|
- **Update Notices** - A passive, once/day "a newer cwcli is available" hint on stderr, shown only to a human at a TTY
|
|
23
26
|
- **Auto-Inspection** - Background process to keep project cache fresh automatically
|
|
24
27
|
- **System Integration** - Auto-start on system boot with platform-specific configurations
|
|
25
28
|
- **Contextual Tips** - Helpful tips displayed during long-running operations to help you discover features
|
|
29
|
+
- **Desktop Console** - A source-build Tauri 2 shell for the existing Console GUI on Linux and Windows; installer bundles are deferred to a later phase
|
|
26
30
|
|
|
27
31
|
## Installation
|
|
28
32
|
|
|
29
33
|
`cwcli` requires Python 3.10+.
|
|
30
34
|
|
|
35
|
+
`cwcli init` and `cwcli scale` also require the Docker CLI with the Docker Compose v2 plugin (`docker compose version`).
|
|
36
|
+
Docker Desktop includes the plugin; on a bare Docker Engine host, install the `docker-compose-plugin` package.
|
|
37
|
+
Both commands verify this prerequisite before changing project state and return an actionable precondition error when it is unavailable.
|
|
38
|
+
|
|
31
39
|
### With uv (recommended)
|
|
32
40
|
|
|
33
41
|
[uv](https://docs.astral.sh/uv/) installs `cwcli` into an isolated environment and puts the `cwcli` command on your `PATH`, without touching your system or project Python.
|
|
@@ -1501,7 +1509,9 @@ Backup: 20251112_105638-development_localhost-database.sql.gz
|
|
|
1501
1509
|
```
|
|
1502
1510
|
|
|
1503
1511
|
For scripted (non-interactive) receives, pass `-y`/`--yes` to skip the confirmation and the missing-apps prompt. Without a TTY and without `--yes`, cwcli refuses and exits non-zero rather than silently overwriting the site's data.
|
|
1504
|
-
Before
|
|
1512
|
+
Before transferring, cwcli requires at least 2 GiB free in its temporary [transfer directory](#architecture).
|
|
1513
|
+
It checks before the sender stages a backup and before the receiver downloads one.
|
|
1514
|
+
The receiver also validates the target site before downloading.
|
|
1505
1515
|
The backup may require more space.
|
|
1506
1516
|
Set `TMPDIR`, `TEMP`, or `TMP` to place transfers on a larger filesystem, in that priority order, or relocate all cwcli data with `CWCLI_HOME`.
|
|
1507
1517
|
Copying the downloaded backup into the container streams through a bounded pipe instead of creating another full host-side copy.
|
|
@@ -1737,6 +1747,60 @@ cwcli status -w --interval 5 frappe-one # refresh every 5s
|
|
|
1737
1747
|
|
|
1738
1748
|
---
|
|
1739
1749
|
|
|
1750
|
+
### `doctor` - Preflight Your Environment
|
|
1751
|
+
|
|
1752
|
+
Checks whether cwcli can operate on this machine at all - Docker, cwcli's own
|
|
1753
|
+
on-disk footprint, disk space, sendme, and the `gh`/`glab` credential-bridge
|
|
1754
|
+
dependencies - as a fast, read-only report. `status`/`inspect` answer "is this
|
|
1755
|
+
instance healthy"; `doctor` answers "will the next command even work here".
|
|
1756
|
+
|
|
1757
|
+
```bash
|
|
1758
|
+
cwcli doctor [OPTIONS]
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+
**Options:**
|
|
1762
|
+
|
|
1763
|
+
| Option | Description |
|
|
1764
|
+
|--------|-------------|
|
|
1765
|
+
| `-v`, `--verbose` | Show each check's stable id (e.g. `[c2]`) alongside its title |
|
|
1766
|
+
|
|
1767
|
+
**Read-only checks:** doctor never starts a container, installs anything, or
|
|
1768
|
+
writes a config file.
|
|
1769
|
+
There is one accepted shared-infrastructure exception before the checks run:
|
|
1770
|
+
building the command tree for `cwcli doctor` or `cwcli axi doctor` imports the
|
|
1771
|
+
pre-existing cache layer, which may idempotently create cwcli's private cache
|
|
1772
|
+
directory and apply mode `0700`.
|
|
1773
|
+
It does not touch project data.
|
|
1774
|
+
Making that initialization lazy is tracked separately as
|
|
1775
|
+
`cwcli-cache-dir-lazy-init`; this caveat should disappear when that lands.
|
|
1776
|
+
When a direct remedy is available, the finding includes the command or action
|
|
1777
|
+
to apply yourself.
|
|
1778
|
+
|
|
1779
|
+
**Checks always run in full** (no tiers, no selection flags), grouped into
|
|
1780
|
+
Docker, cwcli, Storage, Transfer, and Git hosting, plus a cross-instance
|
|
1781
|
+
port-range collision check under Instances. Each check is `pass`, `warn`, or
|
|
1782
|
+
`fail`; an absent *optional* tool (sendme, `gh`, `glab`) is `warn`, never
|
|
1783
|
+
`fail`.
|
|
1784
|
+
|
|
1785
|
+
**Exit codes:** `0` when every check is pass/warn (a warning never blocks -
|
|
1786
|
+
doctor is a chainable preflight gate), `1` when any check fails.
|
|
1787
|
+
|
|
1788
|
+
**Example:**
|
|
1789
|
+
|
|
1790
|
+
```bash
|
|
1791
|
+
cwcli doctor # grouped glyph checklist
|
|
1792
|
+
cwcli doctor -v # same, with each check's stable id shown
|
|
1793
|
+
```
|
|
1794
|
+
|
|
1795
|
+
For agents, `cwcli axi doctor` emits the same checks as one TOON document with
|
|
1796
|
+
a machine-readable `status` token per row.
|
|
1797
|
+
The version row also carries `version_verified`, so `status: pass` with
|
|
1798
|
+
`version_verified: false` is explicitly unverified rather than a claim that the
|
|
1799
|
+
installed version is current.
|
|
1800
|
+
See [the `axi` surface](#for-agents-the-cwcli-axi-surface).
|
|
1801
|
+
|
|
1802
|
+
---
|
|
1803
|
+
|
|
1740
1804
|
### `config` - Manage Configuration
|
|
1741
1805
|
|
|
1742
1806
|
Manages the CLI configuration and cache.
|
|
@@ -1923,7 +1987,11 @@ cwcli self-update --no-cache
|
|
|
1923
1987
|
|
|
1924
1988
|
**Passive update notices:**
|
|
1925
1989
|
|
|
1926
|
-
Every `cwcli` and `cwcli axi` run also does a passive, cache-only check: if a newer release is already known (from the same ≤1-day cache `self-update`/`--check` share) and you're at an interactive terminal, cwcli prints a one-line "a newer cwcli is available" hint
|
|
1990
|
+
Every `cwcli` and `cwcli axi` run except the strictly read-only doctor commands also does a passive, cache-only check: if a newer release is already known (from the same ≤1-day cache `self-update`/`--check` share) and you're at an interactive terminal, cwcli prints a one-line "a newer cwcli is available" hint with the right upgrade command for how you installed it to stderr.
|
|
1991
|
+
`cwcli doctor` and `cwcli axi doctor` skip this check because a stale or missing version cache would otherwise launch a background refresh that writes it.
|
|
1992
|
+
The notice never makes a blocking network call itself: PyPI is actually re-checked at most once/day, via a detached background refresh kicked off whenever the cache is missing or stale, so this never delays a command; until that refresh lands, the hint keeps showing on each eligible run.
|
|
1993
|
+
It never touches stdout, so it is invisible to pipes, scripts, CI, and `cwcli axi`'s TOON output.
|
|
1994
|
+
Set `CWCLI_NO_UPDATE_CHECK=1` to suppress it entirely.
|
|
1927
1995
|
|
|
1928
1996
|
---
|
|
1929
1997
|
|
|
@@ -2145,9 +2213,14 @@ cwcli axi self-update --check
|
|
|
2145
2213
|
# auto-inspect state (config, live daemon, boot hook - reported separately),
|
|
2146
2214
|
# tips, and the config-file/cache-DB locations.
|
|
2147
2215
|
cwcli axi config
|
|
2216
|
+
|
|
2217
|
+
# System-wide environment preflight as one TOON document: Docker, cwcli's own
|
|
2218
|
+
# footprint, disk space, sendme, gh/glab. Machine-readable status per check;
|
|
2219
|
+
# exits 0 unless a check FAILS (a warning never blocks a chained preflight).
|
|
2220
|
+
cwcli axi doctor
|
|
2148
2221
|
```
|
|
2149
2222
|
|
|
2150
|
-
`cwcli axi ls`, `cwcli axi where`, `cwcli axi backup`, `cwcli axi unlock`, `cwcli axi stop`, `cwcli axi start`, `cwcli axi status`, `cwcli axi restart`, `cwcli axi inspect`, `cwcli axi benches`, and `cwcli axi label` run on the same logic core as their human counterparts; only the output (always TOON, never JSON) and choice-handling differ. `cwcli axi start` never prompts: an ambiguous multi-bench project is a `--bench` usage error (exit 2), and an unresolved port conflict is a `CONFLICT` error naming `--yes` (exit 1). `cwcli axi unlock` follows `cwcli axi backup`'s conventions exactly: an ambiguous multi-bench project is a `--bench` usage error (exit 2) and a stopped instance is a usage error pointing at `cwcli start` (exit 2). `cwcli axi stop` is idempotent for an agent - stopping an already-stopped project is a success, not an error. `cwcli axi where` answers from the cache, which outlives the instances it describes, so every match carries `project_state` (`present`, `absent`, or `unverified`) and the document carries `verified`: an agent must read those before acting on a hit, because an `absent` row describes an instance that no longer exists and an `unverified` row means the liveness check could not run. An unreachable Docker daemon degrades the whole answer to `unverified` rather than vouching for it, and `--no-verify` skips the check (reporting `unverified`) when a caller has already established liveness. `cwcli axi status` always exits 0, leading with the instance `overall` aggregate (`offline`/`online`/`running`/`degraded`). It reports **every** bench, in one document, under `benches[N]` - with no `--bench` it used to refuse a multi-bench project with exit 2 and send you off to `cwcli axi benches` to poll once per bench, reassembling the instance view from documents that never said which bench they described. The shape is uniform, so `supervisor_up`, `web_http_code`, `processes` and `not_cwcli_supervised` live inside `benches[i]` even for a single-bench project (`benches[0].processes`), and there is one parse path rather than a branch on bench count. Each bench also carries `bench_present` (`present`/`absent`/`unverified`) - the bench LIST comes from the cache, and a bench whose directory was deleted has no marker and no supervisord, which is indistinguishable from one that was simply never started, so it used to read as `online`; the token answers that, and `overall` keeps its four tokens. Each bench also carries `index`, `bench_path`, `label`, its own `overall`, `web_port`/`web_port_verified` (the port that bench's `web_http_code` was actually measured on), and `web_site` (the site it was measured FOR - Frappe routes by `Host`, so the code belongs to that site, and a probe naming no site is answered `404` by a healthy bench). When that port cannot be read it is `null`/`false` and **no probe was made** - cwcli never falls back to `:8000`, because on a multi-bench instance that reports a different bench's web server as this one's. A stopped instance carries `benches: []`. `cwcli axi stop` takes `--bench <index|label>` to stop ONE bench's dev processes, leaving sibling benches and every container running - the inverse of `cwcli axi start --bench`; without it, it stops the whole instance's containers, and either form is idempotent. `cwcli axi restart` requires `--process`; an unknown/ambiguous process is a usage error listing the valid labels (exit 2). `cwcli axi inspect` serves whichever freshness tier answers the request (`served_from` names it) and, unlike every other bench-scoped verb, has deliberately NO `--yes` - a stopped project on the refresh path is a usage error (exit 2) naming `cwcli start`, and a drift escalation that can no longer discover the bench serves the cached data with `degraded: true` and a warning (exit 0). Every site also carries `installed_apps_verified`: only a full read actually re-observes an app's installed version and git ref, so a cache or partial read carries the remembered list forward unverified and, when that list is non-empty, adds a warning naming `--update` - an agent must not treat an unverified `installed_apps` entry as the live git state. `cwcli axi benches` is the discovery verb behind every other verb's `--bench`: when a bench-scoped verb reports "multiple benches; pass `--bench <index|label>`", this is what tells you the valid values, and a project that has never been inspected is a structured error naming `cwcli axi inspect` rather than an empty list. Its rows come from the cache, which outlives the benches it describes, so every row carries `state` (`present`, `absent`, or `unverified`) and the document carries `verified` - the same contract `cwcli axi where` uses, cross-checked here against the running container in one exec. An agent must read `state` before acting on a row: `absent` means that bench directory no longer exists, and `unverified` means the check could not run (a stopped project, an unreachable daemon, or `--no-verify`) - never a confirmation. A stale row is reported, never pruned; `cwcli axi inspect <project> --update` is what refreshes it. `cwcli axi label` never starts a stopped project (the label marker lives inside the bench), and listing is deliberately `cwcli axi benches` rather than a mode of the mutation verb. `cwcli axi self-update --check` is read-only and **exits 0 whenever the check succeeds, including when an update is available** - the answer is the `is_outdated` field, not the exit code, because on the agent surface a non-zero exit means an error. This deliberately differs from the human `cwcli self-update --check`, which exits 1 when an update is available so shell scripts can gate on it; the mutating `cwcli axi self-update` is deliberately not offered. `cwcli axi config` is the read-only counterpart of `cwcli config show`: one TOON document carrying the search paths, all three auto-inspect state stores, the tips setting, and the file locations. **No config-mutating axi verbs exist** (no `paths add`/`remove`, no `cache clear`, no `auto-inspect enable`/`disable`): an agent rewriting the user's search paths or wiping the cache is a product decision that deserves its own evidence, so mutations stay on the human `cwcli config` surface (whose reads all have `--json`).
|
|
2223
|
+
`cwcli axi ls`, `cwcli axi where`, `cwcli axi backup`, `cwcli axi unlock`, `cwcli axi stop`, `cwcli axi start`, `cwcli axi status`, `cwcli axi restart`, `cwcli axi inspect`, `cwcli axi benches`, and `cwcli axi label` run on the same logic core as their human counterparts; only the output (always TOON, never JSON) and choice-handling differ. `cwcli axi start` never prompts: an ambiguous multi-bench project is a `--bench` usage error (exit 2), and an unresolved port conflict is a `CONFLICT` error naming `--yes` (exit 1). `cwcli axi unlock` follows `cwcli axi backup`'s conventions exactly: an ambiguous multi-bench project is a `--bench` usage error (exit 2) and a stopped instance is a usage error pointing at `cwcli start` (exit 2). `cwcli axi stop` is idempotent for an agent - stopping an already-stopped project is a success, not an error. `cwcli axi where` answers from the cache, which outlives the instances it describes, so every match carries `project_state` (`present`, `absent`, or `unverified`) and the document carries `verified`: an agent must read those before acting on a hit, because an `absent` row describes an instance that no longer exists and an `unverified` row means the liveness check could not run. An unreachable Docker daemon degrades the whole answer to `unverified` rather than vouching for it, and `--no-verify` skips the check (reporting `unverified`) when a caller has already established liveness. `cwcli axi status` always exits 0, leading with the instance `overall` aggregate (`offline`/`online`/`running`/`degraded`). It reports **every** bench, in one document, under `benches[N]` - with no `--bench` it used to refuse a multi-bench project with exit 2 and send you off to `cwcli axi benches` to poll once per bench, reassembling the instance view from documents that never said which bench they described. The shape is uniform, so `supervisor_up`, `web_http_code`, `processes` and `not_cwcli_supervised` live inside `benches[i]` even for a single-bench project (`benches[0].processes`), and there is one parse path rather than a branch on bench count. Each bench also carries `bench_present` (`present`/`absent`/`unverified`) - the bench LIST comes from the cache, and a bench whose directory was deleted has no marker and no supervisord, which is indistinguishable from one that was simply never started, so it used to read as `online`; the token answers that, and `overall` keeps its four tokens. Each bench also carries `index`, `bench_path`, `label`, its own `overall`, `web_port`/`web_port_verified` (the port that bench's `web_http_code` was actually measured on), and `web_site` (the site it was measured FOR - Frappe routes by `Host`, so the code belongs to that site, and a probe naming no site is answered `404` by a healthy bench). When that port cannot be read it is `null`/`false` and **no probe was made** - cwcli never falls back to `:8000`, because on a multi-bench instance that reports a different bench's web server as this one's. A stopped instance carries `benches: []`. `cwcli axi stop` takes `--bench <index|label>` to stop ONE bench's dev processes, leaving sibling benches and every container running - the inverse of `cwcli axi start --bench`; without it, it stops the whole instance's containers, and either form is idempotent. `cwcli axi restart` requires `--process`; an unknown/ambiguous process is a usage error listing the valid labels (exit 2). `cwcli axi inspect` serves whichever freshness tier answers the request (`served_from` names it) and, unlike every other bench-scoped verb, has deliberately NO `--yes` - a stopped project on the refresh path is a usage error (exit 2) naming `cwcli start`, and a drift escalation that can no longer discover the bench serves the cached data with `degraded: true` and a warning (exit 0). Every site also carries `installed_apps_verified`: only a full read actually re-observes an app's installed version and git ref, so a cache or partial read carries the remembered list forward unverified and, when that list is non-empty, adds a warning naming `--update` - an agent must not treat an unverified `installed_apps` entry as the live git state. `cwcli axi benches` is the discovery verb behind every other verb's `--bench`: when a bench-scoped verb reports "multiple benches; pass `--bench <index|label>`", this is what tells you the valid values, and a project that has never been inspected is a structured error naming `cwcli axi inspect` rather than an empty list. Its rows come from the cache, which outlives the benches it describes, so every row carries `state` (`present`, `absent`, or `unverified`) and the document carries `verified` - the same contract `cwcli axi where` uses, cross-checked here against the running container in one exec. An agent must read `state` before acting on a row: `absent` means that bench directory no longer exists, and `unverified` means the check could not run (a stopped project, an unreachable daemon, or `--no-verify`) - never a confirmation. A stale row is reported, never pruned; `cwcli axi inspect <project> --update` is what refreshes it. `cwcli axi label` never starts a stopped project (the label marker lives inside the bench), and listing is deliberately `cwcli axi benches` rather than a mode of the mutation verb. `cwcli axi self-update --check` is read-only and **exits 0 whenever the check succeeds, including when an update is available** - the answer is the `is_outdated` field, not the exit code, because on the agent surface a non-zero exit means an error. This deliberately differs from the human `cwcli self-update --check`, which exits 1 when an update is available so shell scripts can gate on it; the mutating `cwcli axi self-update` is deliberately not offered. `cwcli axi config` is the read-only counterpart of `cwcli config show`: one TOON document carrying the search paths, all three auto-inspect state stores, the tips setting, and the file locations. **No config-mutating axi verbs exist** (no `paths add`/`remove`, no `cache clear`, no `auto-inspect enable`/`disable`): an agent rewriting the user's search paths or wiping the cache is a product decision that deserves its own evidence, so mutations stay on the human `cwcli config` surface (whose reads all have `--json`). `cwcli axi doctor` is the read-only, system-wide counterpart of `cwcli doctor`: one TOON document listing every check with a machine-readable `status` (`pass`/`warn`/`fail`) an agent gates on directly, never by parsing the `detail` text. It exits 0 whenever every check is pass/warn and non-zero only when a check fails, exactly matching the human command, so a fleet can run it before driving any instance and `&&`-chain on the result.
|
|
2151
2224
|
|
|
2152
2225
|
`cwcli axi url <project>` answers two questions no other `axi` verb does: what HOST URL a bench actually serves on, and whether it is answering HTTP right now.
|
|
2153
2226
|
`cwcli axi status`'s `web_http_code` is measured against the bench's CONTAINER-internal port and never states the host address; the host URL is computed only by `cwcli open`'s success banner and the human `cwcli init` banner, neither reachable from `cwcli axi`, so getting either answer meant dropping to a raw `docker inspect`/`curl` against the container.
|
|
@@ -2322,7 +2395,7 @@ The CLI uses:
|
|
|
2322
2395
|
- **Questionary** - Interactive prompts
|
|
2323
2396
|
- **Peewee ORM** - SQLite-based caching
|
|
2324
2397
|
|
|
2325
|
-
**Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and
|
|
2398
|
+
**Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and the Console GUI are all thin frontends over one implementation.
|
|
2326
2399
|
`backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, and `rm-site` are migrated onto it so far.
|
|
2327
2400
|
|
|
2328
2401
|
**Data Directories:**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0"
|
|
@@ -40,6 +40,7 @@ from ..core import apps as core_apps
|
|
|
40
40
|
from ..core import backup as core_backup
|
|
41
41
|
from ..core import bench_ops as core_bench_ops
|
|
42
42
|
from ..core import config as core_config
|
|
43
|
+
from ..core import doctor as core_doctor
|
|
43
44
|
from ..core import init as core_init
|
|
44
45
|
from ..core import inspect as core_inspect
|
|
45
46
|
from ..core import label as core_label
|
|
@@ -2033,6 +2034,39 @@ def axi_config() -> None:
|
|
|
2033
2034
|
raise typer.Exit(0)
|
|
2034
2035
|
|
|
2035
2036
|
|
|
2037
|
+
# ------------------------------------------------------------------------------ doctor
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
@app.command("doctor")
|
|
2041
|
+
def axi_doctor() -> None:
|
|
2042
|
+
"""Run the system-wide, read-only environment preflight; emit it as one TOON document.
|
|
2043
|
+
|
|
2044
|
+
Answers "can cwcli operate on this machine at all" - Docker, cwcli's own
|
|
2045
|
+
on-disk footprint, storage, sendme, and the gh/glab credential-bridge
|
|
2046
|
+
dependencies - as a single fast, non-interactive read, so a fleet can gate a
|
|
2047
|
+
run on it before driving any instance. Every check's ``status`` token
|
|
2048
|
+
(``pass``/``warn``/``fail``) is machine-readable; an agent gates on the
|
|
2049
|
+
token, never on parsing ``detail`` prose (the ``where``/``inspect``
|
|
2050
|
+
precedent).
|
|
2051
|
+
|
|
2052
|
+
Strictly read-only: no check starts a container, installs anything, writes a
|
|
2053
|
+
config file, or triggers an interactive login flow. Always runs every check
|
|
2054
|
+
(no ``--fast``/selection flags - the surviving check set is already
|
|
2055
|
+
fast-band).
|
|
2056
|
+
|
|
2057
|
+
Exit codes mirror the human ``cwcli doctor`` (the maintainer's ruled
|
|
2058
|
+
contract, identical on both surfaces): 0 when every check is pass/warn, 1
|
|
2059
|
+
when any check fails. A WARN never blocks - doctor is a chainable preflight
|
|
2060
|
+
gate and cosmetic findings must not stop a script.
|
|
2061
|
+
"""
|
|
2062
|
+
result = core_doctor.run_all()
|
|
2063
|
+
report = result.data
|
|
2064
|
+
assert report is not None # run_all always returns a DoctorReport
|
|
2065
|
+
|
|
2066
|
+
emit_result(report)
|
|
2067
|
+
raise typer.Exit(0 if report.ok else 1)
|
|
2068
|
+
|
|
2069
|
+
|
|
2036
2070
|
# ------------------------------------------------------------------------------- init
|
|
2037
2071
|
|
|
2038
2072
|
|