caffeinated-whale-cli 3.0.0__tar.gz → 3.1.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-3.0.0 → caffeinated_whale_cli-3.1.0}/PKG-INFO +54 -3
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/README.md +53 -2
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/pyproject.toml +7 -1
- caffeinated_whale_cli-3.1.0/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/axi.py +38 -27
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/init.py +7 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/inspect.py +17 -5
- caffeinated_whale_cli-3.1.0/src/caffeinated_whale_cli/commands/setup.py +134 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/cred_bridge.py +8 -1
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/credbridge.py +10 -3
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/docker.py +15 -1
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/doctor.py +43 -3
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/inspect.py +41 -7
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/rm.py +6 -1
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/rm_site.py +9 -3
- caffeinated_whale_cli-3.1.0/src/caffeinated_whale_cli/core/setup.py +478 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/main.py +8 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/config_utils.py +21 -4
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/cred_daemon.py +39 -3
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/db_utils.py +12 -5
- caffeinated_whale_cli-3.1.0/src/caffeinated_whale_cli/utils/shared_home.py +206 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/startup.py +64 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +3 -0
- caffeinated_whale_cli-3.0.0/src/caffeinated_whale_cli/__init__.py +0 -1
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/LICENSE +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/setup.cfg +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/__main__.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/apps.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/config.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/console.html +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/open.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/restore.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/rm.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/run.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/serve.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/start.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/stop.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/update.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/apps.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/bench_ops.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/config.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/init.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/open.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/resolvers.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/scale.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/start.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/stop.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/update.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/version.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/core/where.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/bench_labels.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/cache.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/daemon_identity.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-3.0.0 → caffeinated_whale_cli-3.1.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: 3.
|
|
3
|
+
Version: 3.1.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
|
|
@@ -158,6 +158,47 @@ python -m caffeinated_whale_cli ls
|
|
|
158
158
|
|
|
159
159
|
This bypasses the shim entirely and behaves identically to `cwcli`/`caffeinated-whale-cli`.
|
|
160
160
|
|
|
161
|
+
## Shared (multi-user) install
|
|
162
|
+
|
|
163
|
+
By default cwcli's on-disk state is **per-user**: everything lives under `~/.cwcli`, the cache is `0700`, the config `0600`, and the credential-bridge daemon runs as you.
|
|
164
|
+
That is correct for one person per machine, and it is unchanged - if you do not opt in, nothing here applies to you.
|
|
165
|
+
|
|
166
|
+
When several people on **one Linux/UNIX box** need to drive the **same** Frappe instances (they already share the containers through the `docker` group), the per-user modes get in the way: the second person hits `Permission denied` on the cache, cannot edit the shared config, and the credential bridge either locks them out or hands them the first person's token.
|
|
167
|
+
Shared mode fixes this with an opt-in, admin-provisioned layout modelled on Docker's `root:docker` socket and Homebrew-on-Linux's shared tree: a dedicated `cwcli` group, a system state tree with setgid group-writable directories, and one machine-wide credential bridge behind a group-owned socket.
|
|
168
|
+
|
|
169
|
+
**Enable it (once, by an admin):**
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
sudo cwcli setup shared --users alice,bob # create the group + tree, add members
|
|
173
|
+
sudo cwcli setup shared --users alice,bob --cred-bridge # also stand up the machine-wide credential bridge
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This creates the `cwcli` group and a `cwcli` service account, provisions `/var/lib/cwcli` (config, cache, projects, archive) as `root:cwcli` with setgid `2770` directories, and writes the marker `/etc/cwcli/shared.toml` that turns shared mode on.
|
|
177
|
+
It is idempotent - safe to re-run to add users or repair modes.
|
|
178
|
+
Members must log out and back in for the new group to take effect.
|
|
179
|
+
Windows is unaffected (shared mode is POSIX-only; cwcli stays per-user there).
|
|
180
|
+
|
|
181
|
+
**Consolidate existing per-user homes into the shared tree:**
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
sudo cwcli setup migrate --from /home/alice/.cwcli --from /home/bob/.cwcli
|
|
185
|
+
sudo cwcli setup migrate --discover # find every /home/*/.cwcli and /root/.cwcli
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`migrate` copies each user's project state into the shared tree with the correct group ownership and modes.
|
|
189
|
+
It **never deletes a source** and **refuses to overwrite an existing shared project** (a name clash is reported, not clobbered), so it is safe to re-run.
|
|
190
|
+
The per-user SQLite cache is regenerable (`cwcli inspect`) and deliberately not merged.
|
|
191
|
+
|
|
192
|
+
**Opting an invocation back to a private home:** `CWCLI_HOME` always wins over shared mode, so `CWCLI_HOME=~/.cwcli cwcli ...` uses your own home even on a shared box.
|
|
193
|
+
|
|
194
|
+
**Security consequences - state them plainly before enabling:**
|
|
195
|
+
|
|
196
|
+
- **`cwcli` group membership is the trust boundary.** Anyone in the group can read every instance's cache, read the rm/rm-site backups (which contain full database dumps), edit the shared config, and - with `--cred-bridge` - ask the machine-wide bridge for a credential. This is the same "docker group ≈ root-ish capability" trade every shared dev tool makes. Keep membership deliberate.
|
|
197
|
+
- **The credential bridge in shared mode serves one machine-owned, read-only service-account token** to any group member's container git, so its audit log records the host and project but **not which human** triggered a fetch (they all present as the service account). Provision that account with a read-only deploy token; rotate it in one place if it leaks.
|
|
198
|
+
- The group-owned socket (`0660 root:cwcli`) closes the world-connectable `0666` socket that could otherwise leak the first user's token to a second user on the box. A uid outside the `cwcli` group cannot reach it.
|
|
199
|
+
|
|
200
|
+
Run `cwcli doctor` to verify the shared install (group present, tree modes, service account).
|
|
201
|
+
|
|
161
202
|
## Quick Start
|
|
162
203
|
|
|
163
204
|
```bash
|
|
@@ -193,6 +234,8 @@ A cluster containing an unknown short option is rejected as a whole.
|
|
|
193
234
|
|
|
194
235
|
Creates a complete Frappe development environment in a single step. Downloads compose files, starts containers, initializes bench, creates a site, and starts the bench's dev services.
|
|
195
236
|
|
|
237
|
+
Blocks for roughly 10-20 minutes (image pulls, bench init, site creation). Running several `cwcli init` invocations in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance (`cwcli init existing-project --bench another-bench`) never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout.
|
|
238
|
+
|
|
196
239
|
```bash
|
|
197
240
|
cwcli init [OPTIONS] [PROJECT_NAME]
|
|
198
241
|
```
|
|
@@ -934,6 +977,7 @@ cwcli inspect [OPTIONS] PROJECT_NAME
|
|
|
934
977
|
|
|
935
978
|
| Option | Description |
|
|
936
979
|
|--------|-------------|
|
|
980
|
+
| `--bench <index\|label>` | Narrow the report to one bench on a multi-bench project (its numeric index or label). Every bench is still discovered/refreshed/cached regardless; only the output is narrowed |
|
|
937
981
|
| `-v`, `--verbose` | Enable verbose diagnostic output |
|
|
938
982
|
| `-j`, `--json` | Output the result as a JSON object |
|
|
939
983
|
| `-u`, `--update` | Update the cache by re-inspecting the project |
|
|
@@ -988,6 +1032,9 @@ cwcli inspect frappe-one --no-refresh
|
|
|
988
1032
|
# Get JSON output
|
|
989
1033
|
cwcli inspect frappe-one --json
|
|
990
1034
|
|
|
1035
|
+
# Narrow the report to one bench in a multi-bench project
|
|
1036
|
+
cwcli inspect frappe-one --bench 1
|
|
1037
|
+
|
|
991
1038
|
# Interactively label each bench (durable handle for --bench)
|
|
992
1039
|
cwcli inspect frappe-one --interactive
|
|
993
1040
|
```
|
|
@@ -2021,6 +2068,7 @@ This is the same class of exposure as SSH agent forwarding or VS Code's own cred
|
|
|
2021
2068
|
Every request is written to an audit log (`~/.cwcli/run/credbridge-audit.log`, one line per request: timestamp, instance, host, answered - never a credential byte).
|
|
2022
2069
|
For a tighter blast radius, authenticate `gh`/`glab` with a fine-grained, expiring token scoped to only the repos you need; the bridge faithfully forwards whatever the host tools hold, so the token's reach is your auth choice.
|
|
2023
2070
|
On a shared multi-user host, note the per-instance socket lives in the instance's workspace directory under `~/.cwcli/projects/<name>/data/`; `enable` tightens `~/.cwcli/projects` to owner-only (0700) for exactly that reason.
|
|
2071
|
+
This per-user daemon is distinct from the **machine-wide** bridge of an opt-in [shared install](#shared-multi-user-install): there the daemon runs once as the `cwcli` service account (started by a system unit at boot), serves a single machine-owned read-only service-account token, and its socket is `0660 root:cwcli` so a uid outside the `cwcli` group cannot reach it. See that section for the security consequences.
|
|
2024
2072
|
|
|
2025
2073
|
**Host allowlist:** the daemon only answers credential requests for `github.com` and `gitlab.com` by default.
|
|
2026
2074
|
Using a self-hosted GitLab (or GitHub Enterprise)?
|
|
@@ -2297,8 +2345,11 @@ cwcli axi scale frappe-one --to 8 --yes
|
|
|
2297
2345
|
# Every site carries installed_apps_verified: true only on a full read - the
|
|
2298
2346
|
# cache/partial tiers carry a site's apps (and their git refs) forward without
|
|
2299
2347
|
# re-observing them, so a non-full read with a remembered app list also warns.
|
|
2348
|
+
# --bench <index|label> narrows the report to one bench (every tier still
|
|
2349
|
+
# refreshes/caches every bench regardless; only the emitted report is narrowed).
|
|
2300
2350
|
cwcli axi inspect frappe-one
|
|
2301
2351
|
cwcli axi inspect frappe-one --update
|
|
2352
|
+
cwcli axi inspect frappe-one --bench 1
|
|
2302
2353
|
|
|
2303
2354
|
# List a project's benches with their indices and labels. This is what answers
|
|
2304
2355
|
# "pass --bench <index|label>" from any other verb - no other verb can.
|
|
@@ -2364,7 +2415,7 @@ cwcli axi config
|
|
|
2364
2415
|
cwcli axi doctor
|
|
2365
2416
|
```
|
|
2366
2417
|
|
|
2367
|
-
`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 credential-bridge state, 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.
|
|
2418
|
+
`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). It takes `--bench <index|label>` like every other bench-scoped verb, narrowing the emitted report to one bench; every tier still discovers/refreshes/caches every bench regardless of the selector, and an unknown selector is a `bench.not_found` error (exit 1). 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 credential-bridge state, 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.
|
|
2368
2419
|
|
|
2369
2420
|
`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.
|
|
2370
2421
|
`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.
|
|
@@ -2418,7 +2469,7 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
|
|
|
2418
2469
|
|
|
2419
2470
|
These are agent-only for now: there is no human `cwcli migrate`, `cwcli run-tests`, or `cwcli build` yet. Use `cwcli run <project> --site <site> migrate` for the human equivalent.
|
|
2420
2471
|
|
|
2421
|
-
`cwcli axi init` provisions a new instance, bench, and site the way `cwcli init` does, but non-interactively and as ONE terminal TOON document. It **blocks** for the full 10-20 minute run - the same block-and-emit-one-document shape as `cwcli axi apps update`, because a progress stream would break the one-TOON-document contract - narrating coarse phase labels to stderr; run `cwcli logs <project>` / `cwcli status <project>` from a second shell for live progress. After the bench+site is created, it starts the bench's dev services by default (the same `core.start` behind `cwcli start`/`cwcli axi start`), leaving a running bench rather than a created-but-idle one; `--no-start` creates without starting, for automation/CI, distinct from Docker container startup (stage 1 always brings the containers up regardless of this flag). A dev-services start failure degrades to a stderr warning rather than a non-zero exit, since the bench was already created. The site administrator password comes from the **`CWCLI_ADMIN_PASSWORD` environment variable (recommended)** or `--admin-password`; the flag wins if both are set, and with neither set the verb refuses with a usage error (exit 2) naming both - it never generates a password and never prompts. The env var keeps the secret off the process argv (visible in `ps`/shell history) that the flag exposes; the container-side `bench new-site` still receives it, so treat the value as one-time. The MariaDB root password takes the same shape via `CWCLI_DB_ROOT_PASSWORD` / `--db-root-password` (default `123`). The interactive decisions the human `cwcli init` prompts for become non-prompting errors: an existing bench with neither `--reuse-bench` nor `--no-reuse-bench` is a usage error (exit 2) naming both; a port conflict names `--port` (exit 1); containers that do not come up point at `cwcli status`/`cwcli logs` (exit 1). There is no `--auto-start` (compose `cwcli axi start` then re-run) and no `--verbose` (stdout is always TOON). `--bench` here is the NAME of the bench to create, distinct from the `--bench <index|label>` selector the other verbs use.
|
|
2472
|
+
`cwcli axi init` provisions a new instance, bench, and site the way `cwcli init` does, but non-interactively and as ONE terminal TOON document. It **blocks** for the full 10-20 minute run - the same block-and-emit-one-document shape as `cwcli axi apps update`, because a progress stream would break the one-TOON-document contract - narrating coarse phase labels to stderr; run `cwcli logs <project>` / `cwcli status <project>` from a second shell for live progress. Running several `axi init` calls in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout. After the bench+site is created, it starts the bench's dev services by default (the same `core.start` behind `cwcli start`/`cwcli axi start`), leaving a running bench rather than a created-but-idle one; `--no-start` creates without starting, for automation/CI, distinct from Docker container startup (stage 1 always brings the containers up regardless of this flag). A dev-services start failure degrades to a stderr warning rather than a non-zero exit, since the bench was already created. The site administrator password comes from the **`CWCLI_ADMIN_PASSWORD` environment variable (recommended)** or `--admin-password`; the flag wins if both are set, and with neither set the verb refuses with a usage error (exit 2) naming both - it never generates a password and never prompts. The env var keeps the secret off the process argv (visible in `ps`/shell history) that the flag exposes; the container-side `bench new-site` still receives it, so treat the value as one-time. The MariaDB root password takes the same shape via `CWCLI_DB_ROOT_PASSWORD` / `--db-root-password` (default `123`). The interactive decisions the human `cwcli init` prompts for become non-prompting errors: an existing bench with neither `--reuse-bench` nor `--no-reuse-bench` is a usage error (exit 2) naming both; a port conflict names `--port` (exit 1); containers that do not come up point at `cwcli status`/`cwcli logs` (exit 1). There is no `--auto-start` (compose `cwcli axi start` then re-run) and no `--verbose` (stdout is always TOON). `--bench` here is the NAME of the bench to create, distinct from the `--bench <index|label>` selector the other verbs use.
|
|
2422
2473
|
|
|
2423
2474
|
`cwcli axi rm <project> --yes` permanently removes an instance: its containers, named volumes (databases, sites, and files), own Docker Compose network, and local project directory.
|
|
2424
2475
|
It is the agent form of `cwcli rm`, and it runs the same code.
|
|
@@ -118,6 +118,47 @@ python -m caffeinated_whale_cli ls
|
|
|
118
118
|
|
|
119
119
|
This bypasses the shim entirely and behaves identically to `cwcli`/`caffeinated-whale-cli`.
|
|
120
120
|
|
|
121
|
+
## Shared (multi-user) install
|
|
122
|
+
|
|
123
|
+
By default cwcli's on-disk state is **per-user**: everything lives under `~/.cwcli`, the cache is `0700`, the config `0600`, and the credential-bridge daemon runs as you.
|
|
124
|
+
That is correct for one person per machine, and it is unchanged - if you do not opt in, nothing here applies to you.
|
|
125
|
+
|
|
126
|
+
When several people on **one Linux/UNIX box** need to drive the **same** Frappe instances (they already share the containers through the `docker` group), the per-user modes get in the way: the second person hits `Permission denied` on the cache, cannot edit the shared config, and the credential bridge either locks them out or hands them the first person's token.
|
|
127
|
+
Shared mode fixes this with an opt-in, admin-provisioned layout modelled on Docker's `root:docker` socket and Homebrew-on-Linux's shared tree: a dedicated `cwcli` group, a system state tree with setgid group-writable directories, and one machine-wide credential bridge behind a group-owned socket.
|
|
128
|
+
|
|
129
|
+
**Enable it (once, by an admin):**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sudo cwcli setup shared --users alice,bob # create the group + tree, add members
|
|
133
|
+
sudo cwcli setup shared --users alice,bob --cred-bridge # also stand up the machine-wide credential bridge
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This creates the `cwcli` group and a `cwcli` service account, provisions `/var/lib/cwcli` (config, cache, projects, archive) as `root:cwcli` with setgid `2770` directories, and writes the marker `/etc/cwcli/shared.toml` that turns shared mode on.
|
|
137
|
+
It is idempotent - safe to re-run to add users or repair modes.
|
|
138
|
+
Members must log out and back in for the new group to take effect.
|
|
139
|
+
Windows is unaffected (shared mode is POSIX-only; cwcli stays per-user there).
|
|
140
|
+
|
|
141
|
+
**Consolidate existing per-user homes into the shared tree:**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
sudo cwcli setup migrate --from /home/alice/.cwcli --from /home/bob/.cwcli
|
|
145
|
+
sudo cwcli setup migrate --discover # find every /home/*/.cwcli and /root/.cwcli
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`migrate` copies each user's project state into the shared tree with the correct group ownership and modes.
|
|
149
|
+
It **never deletes a source** and **refuses to overwrite an existing shared project** (a name clash is reported, not clobbered), so it is safe to re-run.
|
|
150
|
+
The per-user SQLite cache is regenerable (`cwcli inspect`) and deliberately not merged.
|
|
151
|
+
|
|
152
|
+
**Opting an invocation back to a private home:** `CWCLI_HOME` always wins over shared mode, so `CWCLI_HOME=~/.cwcli cwcli ...` uses your own home even on a shared box.
|
|
153
|
+
|
|
154
|
+
**Security consequences - state them plainly before enabling:**
|
|
155
|
+
|
|
156
|
+
- **`cwcli` group membership is the trust boundary.** Anyone in the group can read every instance's cache, read the rm/rm-site backups (which contain full database dumps), edit the shared config, and - with `--cred-bridge` - ask the machine-wide bridge for a credential. This is the same "docker group ≈ root-ish capability" trade every shared dev tool makes. Keep membership deliberate.
|
|
157
|
+
- **The credential bridge in shared mode serves one machine-owned, read-only service-account token** to any group member's container git, so its audit log records the host and project but **not which human** triggered a fetch (they all present as the service account). Provision that account with a read-only deploy token; rotate it in one place if it leaks.
|
|
158
|
+
- The group-owned socket (`0660 root:cwcli`) closes the world-connectable `0666` socket that could otherwise leak the first user's token to a second user on the box. A uid outside the `cwcli` group cannot reach it.
|
|
159
|
+
|
|
160
|
+
Run `cwcli doctor` to verify the shared install (group present, tree modes, service account).
|
|
161
|
+
|
|
121
162
|
## Quick Start
|
|
122
163
|
|
|
123
164
|
```bash
|
|
@@ -153,6 +194,8 @@ A cluster containing an unknown short option is rejected as a whole.
|
|
|
153
194
|
|
|
154
195
|
Creates a complete Frappe development environment in a single step. Downloads compose files, starts containers, initializes bench, creates a site, and starts the bench's dev services.
|
|
155
196
|
|
|
197
|
+
Blocks for roughly 10-20 minutes (image pulls, bench init, site creation). Running several `cwcli init` invocations in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance (`cwcli init existing-project --bench another-bench`) never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout.
|
|
198
|
+
|
|
156
199
|
```bash
|
|
157
200
|
cwcli init [OPTIONS] [PROJECT_NAME]
|
|
158
201
|
```
|
|
@@ -894,6 +937,7 @@ cwcli inspect [OPTIONS] PROJECT_NAME
|
|
|
894
937
|
|
|
895
938
|
| Option | Description |
|
|
896
939
|
|--------|-------------|
|
|
940
|
+
| `--bench <index\|label>` | Narrow the report to one bench on a multi-bench project (its numeric index or label). Every bench is still discovered/refreshed/cached regardless; only the output is narrowed |
|
|
897
941
|
| `-v`, `--verbose` | Enable verbose diagnostic output |
|
|
898
942
|
| `-j`, `--json` | Output the result as a JSON object |
|
|
899
943
|
| `-u`, `--update` | Update the cache by re-inspecting the project |
|
|
@@ -948,6 +992,9 @@ cwcli inspect frappe-one --no-refresh
|
|
|
948
992
|
# Get JSON output
|
|
949
993
|
cwcli inspect frappe-one --json
|
|
950
994
|
|
|
995
|
+
# Narrow the report to one bench in a multi-bench project
|
|
996
|
+
cwcli inspect frappe-one --bench 1
|
|
997
|
+
|
|
951
998
|
# Interactively label each bench (durable handle for --bench)
|
|
952
999
|
cwcli inspect frappe-one --interactive
|
|
953
1000
|
```
|
|
@@ -1981,6 +2028,7 @@ This is the same class of exposure as SSH agent forwarding or VS Code's own cred
|
|
|
1981
2028
|
Every request is written to an audit log (`~/.cwcli/run/credbridge-audit.log`, one line per request: timestamp, instance, host, answered - never a credential byte).
|
|
1982
2029
|
For a tighter blast radius, authenticate `gh`/`glab` with a fine-grained, expiring token scoped to only the repos you need; the bridge faithfully forwards whatever the host tools hold, so the token's reach is your auth choice.
|
|
1983
2030
|
On a shared multi-user host, note the per-instance socket lives in the instance's workspace directory under `~/.cwcli/projects/<name>/data/`; `enable` tightens `~/.cwcli/projects` to owner-only (0700) for exactly that reason.
|
|
2031
|
+
This per-user daemon is distinct from the **machine-wide** bridge of an opt-in [shared install](#shared-multi-user-install): there the daemon runs once as the `cwcli` service account (started by a system unit at boot), serves a single machine-owned read-only service-account token, and its socket is `0660 root:cwcli` so a uid outside the `cwcli` group cannot reach it. See that section for the security consequences.
|
|
1984
2032
|
|
|
1985
2033
|
**Host allowlist:** the daemon only answers credential requests for `github.com` and `gitlab.com` by default.
|
|
1986
2034
|
Using a self-hosted GitLab (or GitHub Enterprise)?
|
|
@@ -2257,8 +2305,11 @@ cwcli axi scale frappe-one --to 8 --yes
|
|
|
2257
2305
|
# Every site carries installed_apps_verified: true only on a full read - the
|
|
2258
2306
|
# cache/partial tiers carry a site's apps (and their git refs) forward without
|
|
2259
2307
|
# re-observing them, so a non-full read with a remembered app list also warns.
|
|
2308
|
+
# --bench <index|label> narrows the report to one bench (every tier still
|
|
2309
|
+
# refreshes/caches every bench regardless; only the emitted report is narrowed).
|
|
2260
2310
|
cwcli axi inspect frappe-one
|
|
2261
2311
|
cwcli axi inspect frappe-one --update
|
|
2312
|
+
cwcli axi inspect frappe-one --bench 1
|
|
2262
2313
|
|
|
2263
2314
|
# List a project's benches with their indices and labels. This is what answers
|
|
2264
2315
|
# "pass --bench <index|label>" from any other verb - no other verb can.
|
|
@@ -2324,7 +2375,7 @@ cwcli axi config
|
|
|
2324
2375
|
cwcli axi doctor
|
|
2325
2376
|
```
|
|
2326
2377
|
|
|
2327
|
-
`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 credential-bridge state, 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.
|
|
2378
|
+
`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). It takes `--bench <index|label>` like every other bench-scoped verb, narrowing the emitted report to one bench; every tier still discovers/refreshes/caches every bench regardless of the selector, and an unknown selector is a `bench.not_found` error (exit 1). 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 credential-bridge state, 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.
|
|
2328
2379
|
|
|
2329
2380
|
`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.
|
|
2330
2381
|
`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.
|
|
@@ -2378,7 +2429,7 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
|
|
|
2378
2429
|
|
|
2379
2430
|
These are agent-only for now: there is no human `cwcli migrate`, `cwcli run-tests`, or `cwcli build` yet. Use `cwcli run <project> --site <site> migrate` for the human equivalent.
|
|
2380
2431
|
|
|
2381
|
-
`cwcli axi init` provisions a new instance, bench, and site the way `cwcli init` does, but non-interactively and as ONE terminal TOON document. It **blocks** for the full 10-20 minute run - the same block-and-emit-one-document shape as `cwcli axi apps update`, because a progress stream would break the one-TOON-document contract - narrating coarse phase labels to stderr; run `cwcli logs <project>` / `cwcli status <project>` from a second shell for live progress. After the bench+site is created, it starts the bench's dev services by default (the same `core.start` behind `cwcli start`/`cwcli axi start`), leaving a running bench rather than a created-but-idle one; `--no-start` creates without starting, for automation/CI, distinct from Docker container startup (stage 1 always brings the containers up regardless of this flag). A dev-services start failure degrades to a stderr warning rather than a non-zero exit, since the bench was already created. The site administrator password comes from the **`CWCLI_ADMIN_PASSWORD` environment variable (recommended)** or `--admin-password`; the flag wins if both are set, and with neither set the verb refuses with a usage error (exit 2) naming both - it never generates a password and never prompts. The env var keeps the secret off the process argv (visible in `ps`/shell history) that the flag exposes; the container-side `bench new-site` still receives it, so treat the value as one-time. The MariaDB root password takes the same shape via `CWCLI_DB_ROOT_PASSWORD` / `--db-root-password` (default `123`). The interactive decisions the human `cwcli init` prompts for become non-prompting errors: an existing bench with neither `--reuse-bench` nor `--no-reuse-bench` is a usage error (exit 2) naming both; a port conflict names `--port` (exit 1); containers that do not come up point at `cwcli status`/`cwcli logs` (exit 1). There is no `--auto-start` (compose `cwcli axi start` then re-run) and no `--verbose` (stdout is always TOON). `--bench` here is the NAME of the bench to create, distinct from the `--bench <index|label>` selector the other verbs use.
|
|
2432
|
+
`cwcli axi init` provisions a new instance, bench, and site the way `cwcli init` does, but non-interactively and as ONE terminal TOON document. It **blocks** for the full 10-20 minute run - the same block-and-emit-one-document shape as `cwcli axi apps update`, because a progress stream would break the one-TOON-document contract - narrating coarse phase labels to stderr; run `cwcli logs <project>` / `cwcli status <project>` from a second shell for live progress. Running several `axi init` calls in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout. After the bench+site is created, it starts the bench's dev services by default (the same `core.start` behind `cwcli start`/`cwcli axi start`), leaving a running bench rather than a created-but-idle one; `--no-start` creates without starting, for automation/CI, distinct from Docker container startup (stage 1 always brings the containers up regardless of this flag). A dev-services start failure degrades to a stderr warning rather than a non-zero exit, since the bench was already created. The site administrator password comes from the **`CWCLI_ADMIN_PASSWORD` environment variable (recommended)** or `--admin-password`; the flag wins if both are set, and with neither set the verb refuses with a usage error (exit 2) naming both - it never generates a password and never prompts. The env var keeps the secret off the process argv (visible in `ps`/shell history) that the flag exposes; the container-side `bench new-site` still receives it, so treat the value as one-time. The MariaDB root password takes the same shape via `CWCLI_DB_ROOT_PASSWORD` / `--db-root-password` (default `123`). The interactive decisions the human `cwcli init` prompts for become non-prompting errors: an existing bench with neither `--reuse-bench` nor `--no-reuse-bench` is a usage error (exit 2) naming both; a port conflict names `--port` (exit 1); containers that do not come up point at `cwcli status`/`cwcli logs` (exit 1). There is no `--auto-start` (compose `cwcli axi start` then re-run) and no `--verbose` (stdout is always TOON). `--bench` here is the NAME of the bench to create, distinct from the `--bench <index|label>` selector the other verbs use.
|
|
2382
2433
|
|
|
2383
2434
|
`cwcli axi rm <project> --yes` permanently removes an instance: its containers, named volumes (databases, sites, and files), own Docker Compose network, and local project directory.
|
|
2384
2435
|
It is the agent form of `cwcli rm`, and it runs the same code.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "caffeinated-whale-cli"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.1.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "Christopher McKay", email = "mckay.christopher73@outlook.com" },
|
|
10
10
|
]
|
|
@@ -80,12 +80,18 @@ select = [
|
|
|
80
80
|
"N", # pep8-naming
|
|
81
81
|
"UP", # pyupgrade
|
|
82
82
|
"B", # flake8-bugbear
|
|
83
|
+
"TID251", # banned API imports and access
|
|
83
84
|
]
|
|
84
85
|
ignore = [
|
|
85
86
|
"E501", # line too long (handled by black)
|
|
86
87
|
"B008", # function call in default argument (required for Typer)
|
|
87
88
|
]
|
|
88
89
|
|
|
90
|
+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
91
|
+
"typer._click" = { msg = "Use Typer's public API instead of its private Click shim." }
|
|
92
|
+
"importlib.import_module" = { msg = "Dynamic imports are forbidden. For a justified exception, add `# noqa: TID251` on that line." }
|
|
93
|
+
"builtins.__import__" = { msg = "The __import__ dynamic import is forbidden. For a justified exception, add `# noqa: TID251` on that line." }
|
|
94
|
+
|
|
89
95
|
[tool.mypy]
|
|
90
96
|
python_version = "3.10"
|
|
91
97
|
warn_return_any = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.1.0"
|
|
@@ -23,7 +23,6 @@ stderr; stdout carries only TOON.
|
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
-
import importlib
|
|
27
26
|
import os
|
|
28
27
|
import re
|
|
29
28
|
import sys
|
|
@@ -74,32 +73,34 @@ from ..utils import agent_hooks, cache, toon
|
|
|
74
73
|
# rendering axi-surface parse failures as the same `error:`+`help:` TOON the core
|
|
75
74
|
# errors already use, while leaving the human CLI's rich rendering untouched.
|
|
76
75
|
#
|
|
77
|
-
# Typer >= ~0.17 VENDORS Click
|
|
78
|
-
#
|
|
76
|
+
# Typer >= ~0.17 VENDORS Click, so a TyperGroup then raises vendored
|
|
77
|
+
# ClickException/UsageError/Abort - a DIFFERENT class hierarchy from the
|
|
79
78
|
# separately-installed `click` package this module imports. `except click.X` then
|
|
80
79
|
# silently misses them, and a bare `cwcli` (Missing command) escapes uncaught to
|
|
81
|
-
# the top-level rich TRACEBACK instead of the intended usage error (exit 2).
|
|
82
|
-
# and type-test against BOTH flavors
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
80
|
+
# the top-level rich TRACEBACK instead of the intended usage error (exit 2). So we
|
|
81
|
+
# catch and type-test against BOTH flavors.
|
|
82
|
+
#
|
|
83
|
+
# Get the vendored bases from PUBLIC typer exports, never the private `typer._click`
|
|
84
|
+
# module: its layout is not API and shifts across releases (typer 0.27 dropped
|
|
85
|
+
# `Abort` from `typer._click.exceptions`, which crashed this import at module load).
|
|
86
|
+
# `typer.Abort` is the vendored Abort; `typer.BadParameter` subclasses the vendored
|
|
87
|
+
# `UsageError` -> `ClickException`, so its MRO yields those two bases by name. On
|
|
88
|
+
# Typer <= 0.16 (standalone click) these resolve to the same classes as `click.*`,
|
|
89
|
+
# and the sets below dedup - one code path, no version branch. Deliberately paired
|
|
90
|
+
# with the protocol-based `_is_argument`/`_is_option` param checks below, which
|
|
91
|
+
# vendored params also fail.
|
|
92
|
+
_typer_exc_bases: dict[str, type[BaseException]] = {
|
|
93
|
+
cls.__name__: cls for cls in typer.BadParameter.__mro__ if issubclass(cls, BaseException)
|
|
94
|
+
}
|
|
95
|
+
_CLICK_EXCEPTIONS: tuple[type[click.ClickException], ...] = tuple(
|
|
96
|
+
{click.ClickException, cast("type[click.ClickException]", _typer_exc_bases["ClickException"])}
|
|
97
|
+
)
|
|
98
|
+
_USAGE_ERRORS: tuple[type[click.UsageError], ...] = tuple(
|
|
99
|
+
{click.UsageError, cast("type[click.UsageError]", _typer_exc_bases["UsageError"])}
|
|
100
|
+
)
|
|
101
|
+
_ABORTS: tuple[type[click.exceptions.Abort], ...] = tuple(
|
|
102
|
+
{click.exceptions.Abort, cast("type[click.exceptions.Abort]", typer.Abort)}
|
|
103
|
+
)
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
def _usage_error_message(error: click.UsageError) -> str:
|
|
@@ -1222,6 +1223,7 @@ def axi_inspect(
|
|
|
1222
1223
|
"--no-refresh",
|
|
1223
1224
|
help="Serve the cached data verbatim; zero container calls (may be stale).",
|
|
1224
1225
|
),
|
|
1226
|
+
bench: str = typer.Option(None, "--bench", help="Which bench: numeric index or label."),
|
|
1225
1227
|
) -> None:
|
|
1226
1228
|
"""Inspect a project's benches, sites, and apps; emit the report as TOON.
|
|
1227
1229
|
|
|
@@ -1247,10 +1249,15 @@ def axi_inspect(
|
|
|
1247
1249
|
Deliberately NO ``--yes``: an axi verb must never open a start-from-axi path.
|
|
1248
1250
|
A stopped project on the refresh path is a usage error (exit 2) naming
|
|
1249
1251
|
``cwcli start``, matching every other stopped-project fork on this surface.
|
|
1252
|
+
|
|
1253
|
+
``--bench <index|label>`` narrows the report to one bench (the same selector
|
|
1254
|
+
``axi status``/``axi logs``/``axi apps`` take); an unknown selector is a
|
|
1255
|
+
``bench.not_found`` error (exit 1). Every tier still refreshes/caches every
|
|
1256
|
+
bench regardless of the selector - only the emitted report is narrowed.
|
|
1250
1257
|
"""
|
|
1251
1258
|
refresh = "full" if update else ("cache_only" if no_refresh else "auto")
|
|
1252
1259
|
try:
|
|
1253
|
-
result = core_inspect.inspect(project, refresh=refresh)
|
|
1260
|
+
result = core_inspect.inspect(project, refresh=refresh, bench=bench)
|
|
1254
1261
|
except CwcliError as error:
|
|
1255
1262
|
emit_axi_error(error)
|
|
1256
1263
|
raise typer.Exit(exit_for(error.kind)) from None
|
|
@@ -2223,7 +2230,11 @@ def axi_init(
|
|
|
2223
2230
|
update` blocks on a long update - streaming N documents would break the
|
|
2224
2231
|
one-TOON-document contract. Coarse phase progress goes to STDERR; for live or
|
|
2225
2232
|
deeper progress, run `cwcli logs <project>` / `cwcli status <project>` from a
|
|
2226
|
-
second shell.
|
|
2233
|
+
second shell. Running several `axi init` calls in parallel can OOM-kill the
|
|
2234
|
+
host on a memory-constrained machine - serialize them. Adding a bench to an
|
|
2235
|
+
already-running instance never disturbs it or its other benches: the frappe
|
|
2236
|
+
container is never pulled, recreated, or restarted, so every already-serving
|
|
2237
|
+
bench keeps running throughout.
|
|
2227
2238
|
|
|
2228
2239
|
After the bench+site is created, dev services start by default (reusing the
|
|
2229
2240
|
same `core.start` behind `cwcli start`/`axi start`), so this leaves a running
|
|
@@ -562,6 +562,13 @@ def init(
|
|
|
562
562
|
Creates project directory, downloads compose files, starts containers,
|
|
563
563
|
initializes bench, and creates a site. Optionally installs ERPNext.
|
|
564
564
|
|
|
565
|
+
Blocks for roughly 10-20 minutes (image pulls, bench init, site creation).
|
|
566
|
+
Running several `cwcli init` invocations in parallel can OOM-kill the host
|
|
567
|
+
on a memory-constrained machine - serialize them instead. Adding a bench
|
|
568
|
+
to an already-running instance never disturbs it or its other benches:
|
|
569
|
+
the frappe container is never pulled, recreated, or restarted, so every
|
|
570
|
+
already-serving bench keeps running throughout.
|
|
571
|
+
|
|
565
572
|
If project_name is not provided, prompts interactively.
|
|
566
573
|
|
|
567
574
|
The site's administrator password is generated and printed once when
|
|
@@ -83,6 +83,11 @@ def inspect(
|
|
|
83
83
|
project_name: str = typer.Argument(
|
|
84
84
|
..., help="The Docker Compose project to inspect.", autocompletion=complete_project_names
|
|
85
85
|
),
|
|
86
|
+
bench: str = typer.Option(
|
|
87
|
+
None,
|
|
88
|
+
"--bench",
|
|
89
|
+
help="Narrow the report to one bench: its numeric index or label (multi-bench projects).",
|
|
90
|
+
),
|
|
86
91
|
verbose: bool = typer.Option(
|
|
87
92
|
False, "--verbose", "-v", help="Enable verbose diagnostic output."
|
|
88
93
|
),
|
|
@@ -122,6 +127,11 @@ def inspect(
|
|
|
122
127
|
"""
|
|
123
128
|
Inspects a Project to find all Bench Instances, Sites, and Apps within it.
|
|
124
129
|
Caches the results for faster subsequent inspects.
|
|
130
|
+
|
|
131
|
+
``--bench <index|label>`` narrows the report to one bench, on a multi-bench
|
|
132
|
+
project. Every tier still discovers/refreshes/caches every bench regardless
|
|
133
|
+
of the selector - only the rendered output is narrowed. An unknown selector
|
|
134
|
+
exits non-zero.
|
|
125
135
|
"""
|
|
126
136
|
if verbose:
|
|
127
137
|
console_err.print(f"VERBOSE: --- Inspecting Project: {project_name} ---")
|
|
@@ -141,6 +151,7 @@ def inspect(
|
|
|
141
151
|
refresh=refresh,
|
|
142
152
|
offer_choice=prompt_to_start,
|
|
143
153
|
on_event=on_event,
|
|
154
|
+
bench=bench,
|
|
144
155
|
)
|
|
145
156
|
except CwcliError as e:
|
|
146
157
|
raise render_error_exit(project_name, e) from None
|
|
@@ -185,13 +196,14 @@ def inspect(
|
|
|
185
196
|
)
|
|
186
197
|
|
|
187
198
|
assignments: list[tuple[str, str]] = []
|
|
188
|
-
for position,
|
|
189
|
-
index =
|
|
190
|
-
existing =
|
|
199
|
+
for position, bench_instance in enumerate(bench_instances_data):
|
|
200
|
+
index = bench_instance.get("index", position)
|
|
201
|
+
existing = bench_instance.get("label")
|
|
191
202
|
existing_hint = f" [current: '{existing}']" if existing else ""
|
|
192
203
|
try:
|
|
193
204
|
answer = questionary.text(
|
|
194
|
-
f"Bench [{index}] at {
|
|
205
|
+
f"Bench [{index}] at {bench_instance['path']} on '{project_name}'."
|
|
206
|
+
f"{existing_hint}\n"
|
|
195
207
|
"Label (blank to keep, letters/digits/.-_ only): "
|
|
196
208
|
).ask()
|
|
197
209
|
if answer is None: # User pressed Ctrl+C
|
|
@@ -205,7 +217,7 @@ def inspect(
|
|
|
205
217
|
continue
|
|
206
218
|
|
|
207
219
|
if answer.strip(): # Blank keeps the existing label.
|
|
208
|
-
assignments.append((
|
|
220
|
+
assignments.append((bench_instance["path"], answer))
|
|
209
221
|
|
|
210
222
|
label_result = core_label.set_labels(project_name, assignments)
|
|
211
223
|
for warning in label_result.warnings:
|