caffeinated-whale-cli 3.1.3__tar.gz → 3.2.1__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.
Files changed (100) hide show
  1. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/PKG-INFO +69 -36
  2. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/README.md +68 -35
  3. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/pyproject.toml +1 -1
  4. caffeinated_whale_cli-3.2.1/src/caffeinated_whale_cli/__init__.py +1 -0
  5. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/apps.py +90 -15
  6. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/axi.py +53 -8
  7. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/backup.py +12 -24
  8. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/config.py +109 -8
  9. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/init.py +73 -8
  10. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/inspect.py +16 -1
  11. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/label.py +6 -0
  12. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/list.py +39 -30
  13. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/restart.py +2 -1
  14. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/restore.py +4 -7
  15. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/rm.py +156 -37
  16. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/start.py +3 -1
  17. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/status.py +84 -12
  18. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/unlock.py +3 -4
  19. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/update.py +47 -3
  20. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/utils.py +21 -0
  21. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/apps.py +100 -1
  22. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/auto_inspect.py +25 -4
  23. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/backup.py +5 -3
  24. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/bench_ops.py +204 -7
  25. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/config.py +153 -3
  26. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/cred_bridge.py +45 -10
  27. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/credbridge.py +28 -5
  28. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/docker.py +110 -16
  29. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/doctor.py +67 -0
  30. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/init.py +131 -13
  31. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/inspect.py +44 -1
  32. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/resolvers.py +223 -0
  33. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/restart.py +1 -1
  34. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/restore.py +7 -6
  35. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/rm.py +113 -5
  36. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/rm_bench.py +21 -1
  37. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/start.py +11 -1
  38. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/status.py +31 -2
  39. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/supervision.py +143 -10
  40. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/unlock.py +5 -3
  41. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/update.py +171 -6
  42. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/main.py +18 -0
  43. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/cred_daemon.py +57 -11
  44. caffeinated_whale_cli-3.2.1/src/caffeinated_whale_cli/utils/signals.py +61 -0
  45. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/startup.py +47 -34
  46. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +1 -0
  47. caffeinated_whale_cli-3.1.3/src/caffeinated_whale_cli/__init__.py +0 -1
  48. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/LICENSE +0 -0
  49. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/setup.cfg +0 -0
  50. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/__main__.py +0 -0
  51. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
  52. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/console.html +0 -0
  53. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
  54. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/logs.py +0 -0
  55. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/open.py +0 -0
  56. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/rm_bench.py +0 -0
  57. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
  58. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/run.py +0 -0
  59. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/scale.py +0 -0
  60. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
  61. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/serve.py +0 -0
  62. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/setup.py +0 -0
  63. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/stop.py +0 -0
  64. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/commands/where.py +0 -0
  65. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/__init__.py +0 -0
  66. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/envelope.py +0 -0
  67. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/errors.py +0 -0
  68. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
  69. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/fleet.py +0 -0
  70. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/label.py +0 -0
  71. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/list.py +0 -0
  72. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/logs.py +0 -0
  73. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/open.py +0 -0
  74. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
  75. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/run.py +0 -0
  76. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/scale.py +0 -0
  77. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/setup.py +0 -0
  78. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/stop.py +0 -0
  79. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/url.py +0 -0
  80. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/version.py +0 -0
  81. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/core/where.py +0 -0
  82. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/update_notice.py +0 -0
  83. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
  84. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
  85. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
  86. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/bench_labels.py +0 -0
  87. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
  88. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/cache.py +0 -0
  89. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
  90. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/config_utils.py +0 -0
  91. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/console.py +0 -0
  92. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/daemon_identity.py +0 -0
  93. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/db_utils.py +0 -0
  94. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
  95. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
  96. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
  97. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/shared_home.py +0 -0
  98. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/tips.py +0 -0
  99. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/src/caffeinated_whale_cli/utils/toon.py +0 -0
  100. {caffeinated_whale_cli-3.1.3 → caffeinated_whale_cli-3.2.1}/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.1.3
3
+ Version: 3.2.1
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
@@ -254,6 +254,7 @@ cwcli init [OPTIONS] [PROJECT_NAME]
254
254
  | `-b`, `--bench TEXT` | Bench directory name inside the container (default: frappe-bench) |
255
255
  | `-s`, `--site TEXT` | Primary site name: any hostname/FQDN or IPv4 address (default: development.localhost). `.localhost` names are recommended for local development - they resolve to the machine without DNS. A real domain or IP is accepted too; init prints a one-line note reminding you it must resolve to this machine for browser access |
256
256
  | `--bench-parent TEXT` | Directory inside container where bench is created (default: /workspace); also the container mount point for the persisted host `data/` directory, fixed for the life of the instance - changing it on a re-init errors, naming the mounted directory |
257
+ | `--bench-image-tag TEXT` | Pin the `frappe/bench` Docker image to this exact tag (e.g. `v5.29.1`), skipping the Docker Hub latest-tag lookup entirely. Omit to use the short-lived cached tag or a fresh lookup |
257
258
  | `--frappe-branch TEXT` | Frappe branch or tag for bench init, e.g. `version-16` or `v16.26.3` (default: version-16). Mutually exclusive with `--version` |
258
259
  | `--version TEXT` | Frappe version for bench init, resolved by shape: a bare major (`16` → `version-16` branch) or a full semantic version (`16.26.3` → `v16.26.3` tag). Malformed values are rejected with a non-zero exit. Mutually exclusive with `--frappe-branch` |
259
260
  | `--frappe-url TEXT` | Custom Frappe repository URL to build from (a fork), passed to `bench init --frappe-path`. Omit to use the default `frappe/frappe` repo. Pair with `--frappe-branch` to check out the fork's branch. A private fork authenticates through the same credential bridge as `apps install`/`apps update` (host `gh`/`glab`, no token in the container); sign in on the host first |
@@ -264,6 +265,7 @@ cwcli init [OPTIONS] [PROJECT_NAME]
264
265
  | `--auto-start` | Automatically start containers if not running |
265
266
  | `--start` / `--no-start` | After creating the bench+site, start its dev services (supervisord over `bench start`) so init leaves a running dev environment (default: `--start`). `--no-start` creates without starting, for automation/CI. Distinct from `--auto-start`, which only controls Docker container startup |
266
267
  | `--reuse-bench` / `--no-reuse-bench` | Pre-answer the existing-bench question non-interactively: `--reuse-bench` reuses the bench and skips `bench init`; `--no-reuse-bench` requires a fresh `--bench` name and errors if it already exists. Default: ask interactively (a non-TTY without either flag refuses). Distinct from `--auto-start`, which controls container startup |
268
+ | `--uid INTEGER` | Override the uid/gid the container's `frappe` user is aligned to, instead of the host's own uid/gid. Mainly for a root-uid host (e.g. a CI runner running as uid 0), where a root host already falls back to a safe default automatically (see step 7 below); pass `--uid` to choose a specific uid instead. Must be a positive, non-root integer (`--uid 0`, negatives, and non-numeric values are rejected with a usage error before any container or file work). Applies only to this init invocation, including its end-of-init auto-start; it is never persisted, so later `cwcli start`/`restart` do not take it |
267
269
  | `-v`, `--verbose` | Show verbose output with streaming command execution |
268
270
 
269
271
  **What It Does:**
@@ -272,9 +274,9 @@ cwcli init [OPTIONS] [PROJECT_NAME]
272
274
  2. Downloads `docker-compose.yml` from frappe_docker GitHub repository
273
275
  3. Customizes port mappings based on `--port` flag
274
276
  4. Binds the bench workspace to a host `~/.cwcli/projects/{project_name}/data/` directory at `--bench-parent` inside the container (default `/workspace`), so bench files stay directly accessible on the host and survive container recreation
275
- 5. Resolves the latest stable `frappe/bench` image tag from Docker Hub (never uses `:latest`)
277
+ 5. Resolves the `frappe/bench` image tag to pin (never uses `:latest`): `--bench-image-tag` is used verbatim and skips the lookup; otherwise a short-lived on-disk cache serves repeated inits and only a cache miss queries Docker Hub for the latest stable tag
276
278
  6. Pulls Docker images and starts containers
277
- 7. On hosts that expose uid/gid information, aligns the container's `frappe` user to the host user so bench files written to the mounted workspace stay host-owned and removable by `cwcli rm` (no-op when the ids already match; platforms without `os.getuid`, such as Windows, cleanly skip alignment because Docker Desktop handles bind-mount ownership; a failed attempted remap degrades to a warning and bench creation still proceeds)
279
+ 7. On hosts that expose uid/gid information, aligns the container's `frappe` user to the host user so bench files written to the mounted workspace stay host-owned and removable by `cwcli rm` (no-op when the ids already match; platforms without `os.getuid`, such as Windows, cleanly skip alignment because Docker Desktop handles bind-mount ownership; a failed attempted remap degrades to a warning and bench creation still proceeds). On a **root host** (uid 0, a common CI-runner shape) the container `frappe` user is **not** remapped to 0 - that would collide with the container's own root user and break bench's `PATH` (`bench init` fails with exit 127) - so it falls back to the image's default uid (1000) and says so once in a warning; root can still remove those files, so `cwcli rm` keeps working. Pass `--uid <n>` to align to a specific uid instead
278
280
  8. Pins the correct Python version via `PYENV_VERSION` for the branch (installs via pyenv if missing)
279
281
  9. Pins the correct Node.js version via nvm for older branches (installs via nvm if missing)
280
282
  10. Installs `yarn` globally for the activated Node.js version (older branches only)
@@ -778,6 +780,7 @@ The same fail-closed refusal applies when Docker cannot verify whether named vol
778
780
  If Docker confirms that no named volumes remain, no backup is needed: `cwcli rm` cleans up the orphan's network and local directory under the default settings.
779
781
  The volume state is checked again before cleanup, and any change or verification failure aborts instead of risking unbacked data.
780
782
  Under `--no-volumes` no volume data is destroyed, so a stopped project is cleaned up without a backup as before.
783
+ A half-provisioned instance that holds no site on any bench has nothing to back up, so - like a confirmed volume-free orphan - the removal proceeds instead of refusing and sending you back for a second `--no-backup` run; this relaxes only on a *live* proof that every bench is site-less, and any ambiguity keeps the fail-closed refusal.
781
784
 
782
785
  ```bash
783
786
  cwcli rm [OPTIONS] [PROJECT_NAME]...
@@ -808,7 +811,7 @@ If a project has no containers but still has a network, named volumes, or a loca
808
811
  Confirmed volume-free orphans are cleaned under the defaults.
809
812
  Orphans with named volumes, or an unknown volume state, remain protected by the backup gate.
810
813
 
811
- An invalid project name (empty, `.`, `..`, an absolute path, or one containing a path separator) is rejected before anything is removed, since such a name could otherwise escape the projects directory.
814
+ An invalid project name (empty, blank/whitespace-only, `.`, `..`, an absolute path, or one containing a path separator) is rejected before anything is removed, since such a name could otherwise escape the projects directory or resolve to a phantom project; a blank name is reported as `(empty)` rather than an empty quote.
812
815
  `cwcli rm` exits non-zero whenever any removal step fails, including a blocked backup, a rejected name, or a container, volume, network, or directory that could not be removed.
813
816
  It does not print the success summary for a project that was not fully removed; the project stays in the cache (and `cwcli ls`) so the leftover state remains visible and can be retried.
814
817
 
@@ -1013,6 +1016,9 @@ This means an app you just installed is picked up automatically, without a manua
1013
1016
  If nothing changed, the cached data is served unchanged; if that pass detects a change, it transparently falls back to a full re-inspect so the per-site installed-app lists are refreshed too.
1014
1017
  Use `--no-refresh` to skip the pass and return the cached data as-is (fastest, but possibly stale), or `--update` to force a full re-inspect.
1015
1018
 
1019
+ **App-copy flags:** on a full inspect (not a cache-served read) `inspect` flags an app whose `apps/<app>` is a symlink (showing the real directory) and, more seriously, an app whose imported copy differs from `apps/<app>` (the bench imports a different copy, so the site runs code that changes to `apps/<app>` never reach).
1020
+ These are live-observed and not persisted to the cache.
1021
+
1016
1022
  **Security Note:** The cache never stores Frappe secrets. Site and common configurations are whitelist-filtered before they are written, so database passwords, per-site encryption keys, admin/root passwords, and Redis URLs are stripped and never persisted (nothing reads them back from the cache - every credential consumer reads live from the container or from CLI flags/prompts). Any cache written before this behavior shipped is cleaned in place on the next run. As defense-in-depth, the cache is also stored with restricted filesystem permissions (directory: `0700`, database: `0600`) so only the current user can read it; still, do not share the cache directory (`~/.cwcli/cache/`) with untrusted users.
1017
1023
 
1018
1024
  ```bash
@@ -1230,17 +1236,18 @@ After a successful mutation the bench's cached app lists are refreshed so `where
1230
1236
 
1231
1237
  ```bash
1232
1238
  cwcli apps list [OPTIONS] PROJECT_NAME
1233
- cwcli apps install [OPTIONS] PROJECT_NAME --app APP [--app APP ...]
1239
+ cwcli apps install [OPTIONS] PROJECT_NAME [APP ...] # or -a/--app APP
1234
1240
  cwcli apps uninstall [OPTIONS] PROJECT_NAME --app APP [--app APP ...]
1235
1241
  cwcli apps update [OPTIONS] PROJECT_NAME APPS...
1236
1242
  cwcli apps checkout [OPTIONS] PROJECT_NAME APP REF
1237
1243
  ```
1238
1244
 
1239
1245
  **`apps list`** - lists apps available in the bench (live `ls apps/`); with `--installed`/`--site` it also lists the apps installed per site (all sites by default, grouped by site).
1246
+ It flags an app whose `apps/<app>` is a symlink (naming the real directory it points at) and, more seriously, an app whose imported copy differs from `apps/<app>` (the bench's Python actually imports a different copy, so operating on `apps/<app>` leaves the site running stale code); in `--json` an `app_copies` array carries the same detail for any flagged app.
1240
1247
 
1241
1248
  **`apps install`** - ensures each app is present on the bench, then installs it on the target site(s).
1242
- An app absent from `apps/` is fetched with `bench get-app` (honoring `--branch`); an app already present is not fetched again, so pre-warmed benches install it without failing on the existing directory.
1243
- Each `--app` is a known app name **or** a git URL (passed straight to `bench get-app` when a fetch is needed, so custom apps not in bench's registry work); repeat `--app` to install more than one.
1249
+ An app absent from `apps/` is fetched with `bench get-app` (honoring `--branch`); an app already present **and registered** (in `sites/apps.txt`) is not fetched again, so pre-warmed benches install it without failing on the existing directory. A present directory that is *not* a valid install - the leftover of an earlier fetch that cloned but failed at its `pip install` step - is refused with an actionable message telling you to remove it and re-run, rather than reporting a false "get-app ok" and then dumping a raw install traceback on every retry. When a fresh fetch itself fails, the partial `apps/<app>` directory it created is removed so a retry genuinely re-fetches (a pre-existing app directory is never touched).
1250
+ Each app is a known app name **or** a git URL (passed straight to `bench get-app` when a fetch is needed, so custom apps not in bench's registry work). Apps may be given positionally (matching `apps update`/`apps checkout` and `axi apps install`) or with `-a`/`--app`; both forms combine, and repeat either to install more than one.
1244
1251
  `--fetch-only` ensures the app is present without installing it on any site.
1245
1252
  `--if-not-present` makes install idempotent: an app already installed on a target site is skipped (reported, not reinstalled, with its install hooks never rerun) and the command still exits 0, so "ensure this app is installed" is a single call whose exit code a script can trust; an app the site does not have is still installed normally, and a genuine install failure still exits non-zero.
1246
1253
  A bench that is already running may still serve code it loaded before the install and fail to see the new app: install therefore **resynchronises** that bench - it restarts every cwcli-supervised program that runs the bench's Python (`web`, `schedule`, and every worker) and requires every changed site to answer Frappe before it reports success.
@@ -1254,6 +1261,8 @@ A bench that was not running is left alone.
1254
1261
 
1255
1262
  **`apps update`** - the canonical app-update path (what the deprecated `cwcli update` now delegates to). Updating the `frappe` framework app runs `bench update --reset`; other apps use the normal git-pull + migrate flow. After the migrations finish and maintenance mode is lifted, it runs the same **resynchronise** step as `apps install`, so a `git pull` cannot leave the bench's web, scheduler, and workers on the code that was there before it; every migrated site must answer Frappe before the run reports success. `--site` narrows which affected sites are migrated; if none of the named site(s) actually have the app installed, the command refuses and exits non-zero rather than silently migrating nothing (a genuine typo/mismatch guard - a bench with no affected sites at all still exits zero). It accepts the same migration flags as the [deprecated `update` command](#update---update-apps-and-migrate) (`--clear-cache`, `--clear-website-cache`, `--build`, `--skip-maintenance`, `--no-recache`). When updating the `frappe` framework app the flow runs the bench-wide `bench update --reset`, so `--site` and those per-app migration flags do not apply and are reported as ignored.
1256
1263
  `--force` is an opt-in, **destructive** recovery for a conflicting update: if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`. `--force` also clears a git **"dubious ownership"** refusal before pulling: when an app's repository is owned by a different user than the container runs as - common on a shared-mode or migrated instance whose app sources are owned by a pre-migration uid - it re-owns the repository to the container user so the pull proceeds, rather than surfacing a raw git ownership error. A `cwcli start`/`cwcli restart` self-heals the same ownership so a plain `apps update` works too; `--force` fixes it inline without a restart.
1264
+ When `apps/<app>` is a symlink, the update writes through it and the run names the real directory it operated on.
1265
+ After the pull, update verifies that the copy the bench actually imports is the copy it just updated; when they differ it does **not** report plain success - it warns, names both paths, and exits non-zero so a script notices the site is still running the other copy.
1257
1266
 
1258
1267
  **`apps checkout`** - fetches and checks out an arbitrary branch, tag, or commit (`REF`) into an app that is **already present** in the bench (`apps/<app>`), so a specific feature branch can be put under test in the instance the app lives in.
1259
1268
  Unlike `apps install` (bench acquisition when needed, then site installation) and `apps update` (the tracked upstream on every app), this targets one existing checkout: it runs `git fetch <remote> <ref>` then `git checkout -B <ref> FETCH_HEAD` in the app directory (the remote is auto-detected - `upstream` for a bench-installed app, `origin` for a hand-cloned one).
@@ -1267,8 +1276,9 @@ Dirty means precisely **anything `git status --porcelain` reports: staged change
1267
1276
  Files matched by `.gitignore` are not reported by git and so never count, which is why ordinary build residue (`__pycache__`, `node_modules`, `*.egg-info`) does not block a checkout.
1268
1277
  `--reset` is the explicit opt-in through that refusal: it hard-resets the working tree to the fetched ref, discarding tracked local edits, which guarantees the clean tree a subsequent `build`/`migrate` needs.
1269
1278
  Note one honest limit: `--reset` does **not** delete untracked files, because cwcli never runs `git clean` - it lets the checkout proceed and leaves them where they are.
1279
+ Like `apps update`, checkout names the real directory when `apps/<app>` is a symlink, and after moving the tree it verifies the bench imports the copy it just changed; if the bench imports a different copy it warns, names both paths, and exits non-zero rather than reporting a checkout the site will not actually run.
1270
1280
 
1271
- **Private repos:** `apps install`/`apps update`/`apps checkout` (and the deprecated `update`), plus any bench subcommand run through [`cwcli run`](#run---execute-bench-commands) (`cwcli run <project> get-app <private-url>`, `cwcli run <project> update --pull`), transparently authenticate git fetches against private GitHub/GitLab app repos through your host's already-signed-in `gh`/`glab` - nothing to configure, no token ever stored in the container, and public repos are unaffected. Sign in on the host first (`gh auth login` / `glab auth login`). To extend this to **interactive** git inside `cwcli open` or a `docker exec` shell (not just cwcli's own operations), opt into the persistent [`config cred-bridge`](#config-cred-bridge---persistent-credential-bridge).
1281
+ **Private repos:** `apps install`/`apps update`/`apps checkout` (and the deprecated `update`), plus any bench subcommand run through [`cwcli run`](#run---execute-bench-commands) (`cwcli run <project> get-app <private-url>`, `cwcli run <project> update --pull`), transparently authenticate git fetches against private GitHub/GitLab app repos through your host's already-signed-in `gh`/`glab` - nothing to configure, no token ever stored in the container, and public repos are unaffected. Sign in on the host first (`gh auth login` / `glab auth login`). This credential bridge only serves operations run **through cwcli**; a raw `git` you run yourself inside the container (in a `docker exec` shell, or `cwcli open`) is not bridged and will prompt or fail auth. Run the fetch through `cwcli run <project> <bench-args>` instead, or - to bridge **interactive** git inside `cwcli open` or a `docker exec` shell (not just cwcli's own operations) - opt into the persistent [`config cred-bridge`](#config-cred-bridge---persistent-credential-bridge).
1272
1282
 
1273
1283
  **Common Options:**
1274
1284
 
@@ -1276,7 +1286,7 @@ Note one honest limit: `--reset` does **not** delete untracked files, because cw
1276
1286
  |--------|-------------|
1277
1287
  | `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
1278
1288
  | `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`) |
1279
- | `-a`, `--app TEXT` | App name (`install` also accepts a git URL); repeatable and required for `install`/`uninstall` |
1289
+ | `-a`, `--app TEXT` | App name (`install` also accepts a git URL); repeatable. Required for `uninstall`; for `install` the app may instead be given positionally |
1280
1290
  | `--site TEXT` | Target site(s); repeatable. Omit for all sites (list/install/uninstall) or all affected sites (update) |
1281
1291
  | `--json` | Machine-readable JSON output (for `install`/`uninstall`, includes the per-`(app, site)` results; for `update`, the full per-phase report). Stdout carries only the document - bench output never corrupts it |
1282
1292
  | `-y`, `--yes` | Auto-start stopped containers without prompting; for `uninstall`, also skip the destructive confirmation |
@@ -1291,8 +1301,8 @@ cwcli apps list frappe-one
1291
1301
  # List apps installed across every site, as JSON
1292
1302
  cwcli apps list frappe-one --installed --json
1293
1303
 
1294
- # Install ERPNext on every site of the bench
1295
- cwcli apps install frappe-one --app erpnext
1304
+ # Install ERPNext on every site of the bench (positional app, or -a/--app)
1305
+ cwcli apps install frappe-one erpnext
1296
1306
 
1297
1307
  # Install more than one app in the same call
1298
1308
  cwcli apps install frappe-one --app erpnext --app hrms
@@ -1423,7 +1433,7 @@ cwcli unlock [OPTIONS] PROJECT_NAME
1423
1433
 
1424
1434
  | Option | Description |
1425
1435
  |--------|-------------|
1426
- | `-s`, `--site TEXT` | Site name to unlock. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`) |
1436
+ | `-s`, `--site TEXT` | Site name to unlock. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`), falling back to the sole site of a single-site bench |
1427
1437
  | `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
1428
1438
  | `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
1429
1439
  | `-y`, `--yes` | Auto-start stopped containers without prompting |
@@ -1435,9 +1445,9 @@ Removes the `{bench_path}/sites/{site_name}/locks` directory, which can help res
1435
1445
  A site with no locks folder is reported as an honest "already unlocked" success rather than a generic "unlocked" message.
1436
1446
 
1437
1447
  **Smart Defaults:**
1438
- - If `--site` is not specified, automatically uses your bench's default site (from `common_site_config.json`'s `default_site`, or `sites/currentsite.txt` when that key is absent)
1439
- - Shows "Using default site: {site}" when using the default
1440
- - Run `cwcli inspect {project}` first to cache the configuration
1448
+ - If `--site` is not specified, uses your bench's configured default site (from `common_site_config.json`'s `default_site`, or `sites/currentsite.txt` when that key is absent); when no default is configured it falls back to the sole site of a single-site bench (the shape a fresh `cwcli init` leaves)
1449
+ - A multi-site bench with no configured default still requires `--site`, and the error lists the available sites
1450
+ - Shows "Using site '{site}'" when the site was resolved for you
1441
1451
 
1442
1452
  **Examples:**
1443
1453
 
@@ -1445,9 +1455,9 @@ A site with no locks folder is reported as an honest "already unlocked" success
1445
1455
  # Unlock a specific site
1446
1456
  cwcli unlock my-project --site development.localhost
1447
1457
 
1448
- # Use default site (no --site flag needed)
1458
+ # Use the resolved site (no --site flag needed)
1449
1459
  cwcli unlock my-project
1450
- # Output: Using default site: development.localhost
1460
+ # Output: Using site 'development.localhost'
1451
1461
 
1452
1462
  # Verbose mode with default site
1453
1463
  cwcli unlock my-project -v
@@ -1498,7 +1508,7 @@ cwcli backup [OPTIONS] PROJECT_NAME
1498
1508
 
1499
1509
  | Option | Description |
1500
1510
  |--------|-------------|
1501
- | `-s`, `--site TEXT` | Site name to back up. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`) |
1511
+ | `-s`, `--site TEXT` | Site name to back up. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`), falling back to the sole site of a single-site bench; a multi-site bench with no default requires `--site` and the error lists the sites |
1502
1512
  | `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
1503
1513
  | `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
1504
1514
  | `--with-files` | Include public and private files in the backup |
@@ -1541,7 +1551,7 @@ cwcli restore [OPTIONS] PROJECT_NAME
1541
1551
 
1542
1552
  | Option | Description |
1543
1553
  |--------|-------------|
1544
- | `-s`, `--site TEXT` | Site name to restore. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`) |
1554
+ | `-s`, `--site TEXT` | Site name to restore. If not provided, uses the default site (from `common_site_config.json`'s `default_site` or `sites/currentsite.txt`), falling back to the sole site of a single-site bench; a multi-site bench with no default requires `--site` and the error lists the sites |
1545
1555
  | `--latest` | Non-interactive: select the most recent backup set for the target site (bypasses the backup menu). A non-TTY without any selector exits non-zero instead of hanging |
1546
1556
  | `--backup-file TEXT` | Non-interactive: select the backup set whose database file matches this filename (or full container path). Bypasses the backup menu |
1547
1557
  | `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
@@ -1618,7 +1628,7 @@ cwcli restore my-project --site production.localhost --latest --yes \
1618
1628
  **Example Session:**
1619
1629
 
1620
1630
  ```
1621
- Using default site: development.localhost
1631
+ Using site 'development.localhost'
1622
1632
 
1623
1633
  ? Select a backup to restore: (Use arrow keys)
1624
1634
 
@@ -1831,14 +1841,14 @@ read from that bench's `sites/common_site_config.json`, which is what bench's ow
1831
1841
  `make_ports` writes - so a bench's HTTP code always describes that bench.
1832
1842
 
1833
1843
  ```bash
1834
- cwcli status [OPTIONS] PROJECT_NAME
1844
+ cwcli status [OPTIONS] [PROJECT_NAME]
1835
1845
  ```
1836
1846
 
1837
1847
  **Arguments:**
1838
1848
 
1839
1849
  | Argument | Description |
1840
1850
  |----------|-------------|
1841
- | `PROJECT_NAME` | The Docker Compose project name to check (required) |
1851
+ | `PROJECT_NAME` | The Docker Compose project name to check. Omit it for the all-instances summary (the same table `cwcli ls` gives) - a bare `cwcli status` reads like "how is everything", not a missing-argument error |
1842
1852
 
1843
1853
  **Options:**
1844
1854
 
@@ -1879,15 +1889,20 @@ is a gap in cwcli's knowledge, not a fault in the bench.
1879
1889
  `Host` header, so a request naming no site is correctly answered `404` by a
1880
1890
  perfectly healthy bench - which is what `status` used to report on every read. The
1881
1891
  probe now sends the bench's site as its `Host`, so a healthy bench reads `200`, and
1882
- the report says which site it asked for (`web_site`, shown as `web <site>:<port> ->
1883
- <code>`). That keeps the code attributable the same way the port does: it is that
1884
- site's code, not a claim about the bench as a whole. A bench with no site recorded
1885
- probes without a `Host` header, as before, and reports `web_site: null`.
1892
+ the report says which site it asked for (`web_site`). That keeps the code
1893
+ attributable the same way the port does: it is that site's code, not a claim about
1894
+ the bench as a whole. A bench with no site recorded probes without a `Host` header,
1895
+ as before, and reports `web_site: null`. The human `cwcli status` web line shows the
1896
+ **host-reachable** address (`web <site>:<host-port> -> <code>`) - the same one the
1897
+ init banner and `cwcli axi url` print, so it is copy-pasteable - resolved by mapping
1898
+ the bench's container web port through the container's live host bindings; when that
1899
+ host binding cannot be resolved it falls back to the container port labelled
1900
+ `(in-container)` so nobody tries to open an unreachable address.
1886
1901
 
1887
1902
  A truly-nonexistent project name (a typo, or one never created - no containers
1888
- with the label at all) is NOT `offline`: it exits non-zero with a "no such
1889
- project" error on stderr, so a script can tell a stopped instance apart from a
1890
- name that does not exist.
1903
+ with the label at all) is NOT `offline`: it exits non-zero with a "Project
1904
+ '<name>' not found." error on stderr, so a script can tell a stopped instance apart
1905
+ from a name that does not exist.
1891
1906
 
1892
1907
  **Not under cwcli supervision:** if a bench is running but was NOT started by
1893
1908
  cwcli's supervisord - a plain `bench start`, or an instance already running before
@@ -1900,9 +1915,20 @@ hint to run `cwcli start` to bring it under cwcli's supervisor. In this mode the
1900
1915
  per-process supervisord `state` is unavailable (there is no supervisord to ask);
1901
1916
  `up` comes straight from `ps`. `status` only reads - it never launches supervisord.
1902
1917
 
1918
+ **Maintenance mode is called out plainly.** A site stuck in maintenance mode answers
1919
+ HTTP 503 to every user with no other signal - the state a `migrate` / `apps update`
1920
+ killed mid-run (a closed terminal, a `kill`, a service stop) leaves behind. When a
1921
+ bench's site is in maintenance mode, `status` prints `MAINTENANCE MODE ON` for that
1922
+ site (even without `-v`, since it is a fault, not detail) and names the way out
1923
+ (`cwcli run <project> --site <site> set-maintenance-mode off`); `cwcli axi status`
1924
+ carries it as `maintenance_mode` plus a `status.maintenance_mode` warning. It is read
1925
+ live from the site's `site_config.json`, not inferred from the 503, and is skipped in
1926
+ the `--watch` loop (which makes no web probe).
1927
+
1903
1928
  **Per-bench, per-process detail (stderr):** one sub-block per bench, headed by its
1904
- index, path, label, and own aggregate, with a web line naming the port that was
1905
- actually probed (`web :8001 -> 404`). Beneath each, every Procfile process (`web`, `socketio`,
1929
+ index, path, label, and own aggregate, with a web line naming the host-reachable
1930
+ address that was probed (`web dev.localhost:17001 -> 200`, or the container port
1931
+ labelled `(in-container)` when the host binding cannot be resolved). Beneath each, every Procfile process (`web`, `socketio`,
1906
1932
  `worker`, `schedule`, `watch`, `redis_cache`, `redis_queue`) with up/down plus
1907
1933
  its PID, uptime, CPU%, RSS, and supervisord's authoritative `state`
1908
1934
  (`RUNNING`/`STARTING`/`BACKOFF`/`EXITED`/`FATAL`/`STOPPED`) - so a crash-looping or
@@ -2034,11 +2060,17 @@ cwcli config paths # list, one path per line
2034
2060
  cwcli config paths --json # the same list as JSON
2035
2061
  cwcli config paths add /home/user/benches # absolute paths only
2036
2062
  cwcli config paths remove /home/user/benches
2063
+ cwcli config paths prune # dry-run: list dead paths
2064
+ cwcli config paths prune --yes # actually drop them
2037
2065
  ```
2038
2066
 
2039
2067
  `add` refuses a non-absolute path with a usage error (exit 2) after `~` expansion, and normalizes before duplicate detection, so `/a/b` and `/a/b/` are one entry, not two.
2040
2068
  Adding an already-present path and removing an absent one are both no-op successes (exit 0, saying which no-op occurred).
2041
2069
 
2070
+ `prune` removes custom search paths that no live instance references (they accumulate a graveyard: a path added for an instance later removed lingers forever and is re-scanned on every `inspect`).
2071
+ It checks each path live against every instance's frappe container and, with `--yes`, drops the ones absent from all of them; without `--yes` it is a dry-run that only lists what it would drop (`--json` for machine output).
2072
+ It is fail-closed: a path present in any instance is never dropped, and if any instance cannot be checked (stopped or unreachable) nothing is pruned, so a path a stopped instance still needs is never lost.
2073
+
2042
2074
  ##### `config cache` - Manage Cache
2043
2075
 
2044
2076
  Manages the cache for project inspection data.
@@ -2137,11 +2169,12 @@ Honestly stated: the allowlist keeps the long-lived channel from silently wideni
2137
2169
 
2138
2170
  **Cred-Bridge Subcommands:**
2139
2171
 
2140
- - **`enable [--startup/--no-startup]`** - Enable the bridge AND start its background daemon, in one verb
2172
+ - **`enable [--project <name>] [--startup/--no-startup]`** - Enable the bridge AND start its background daemon, in one verb
2141
2173
  - Idempotent; missing `gh`/`glab` is a warning, not a refusal (the bridge simply answers nothing until a tool is installed and authenticated).
2142
- - Takes effect on any running instance immediately, and self-heals onto each instance on the next `cwcli open`/`cwcli start`/`cwcli run`.
2143
- - `--startup` also installs an automatic start at system boot/login (LaunchAgent on macOS, systemd user service on Linux, Task Scheduler on Windows) - this covers reopening VS Code straight after a reboot, before any cwcli verb has run. `--no-startup` removes the boot unit; omit both to leave it untouched.
2144
- - Example: `cwcli config cred-bridge enable --startup`
2174
+ - Takes effect immediately on every currently-running instance cwcli manages (a Frappe container cwcli did not create is never touched), and self-heals onto each instance on the next `cwcli open`/`cwcli start`/`cwcli run`. When the default sweep wires more than one instance it names them, so an operator on a multi-instance machine is never surprised.
2175
+ - `--project <name>` scopes the sweep to the named instance(s) (repeatable; an unknown name is the usual `not found` error, and a running container cwcli does not manage is refused rather than wired). Omit it to wire every running managed instance.
2176
+ - `--startup` also installs an automatic start at system boot/login (LaunchAgent on macOS, systemd user service on Linux, Task Scheduler on Windows) - this covers reopening VS Code straight after a reboot, before any cwcli verb has run. `--no-startup` removes the boot unit; omit both to leave it untouched. If the boot unit cannot be installed (for example a host with no systemd user session), the bridge still enables and runs - the message says so and names `cwcli config cred-bridge start` for a manual start.
2177
+ - Example: `cwcli config cred-bridge enable --project my-instance --startup`
2145
2178
 
2146
2179
  - **`disable`** - Stop the daemon, set enabled = false, make every wired shim inert, and remove the boot unit (non-destructive; an `enable` recreates everything)
2147
2180
  - Example: `cwcli config cred-bridge disable`
@@ -2521,7 +2554,7 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
2521
2554
 
2522
2555
  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.
2523
2556
 
2524
- `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.
2557
+ `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); an invalid `--uid` (0, negative, or non-numeric) is rejected before any container work. `--uid` behaves as it does on `cwcli init` (including the root-host fallback, whose one-time warning is folded into the report's `warnings`). 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.
2525
2558
 
2526
2559
  `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.
2527
2560
  It is the agent form of `cwcli rm`, and it runs the same code.