caffeinated-whale-cli 3.2.1__tar.gz → 3.4.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.2.1 → caffeinated_whale_cli-3.4.0}/PKG-INFO +36 -2
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/README.md +35 -1
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/pyproject.toml +1 -1
- caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/__init__.py +1 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/apps.py +11 -6
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/axi.py +182 -22
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/config.py +49 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/console.html +8 -3
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/init.py +12 -6
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm.py +10 -2
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm_bench.py +5 -2
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm_site.py +5 -2
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/run.py +20 -8
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/serve.py +31 -2
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/stop.py +8 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/update.py +15 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/apps.py +84 -65
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/auto_inspect.py +8 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/bench_ops.py +159 -1
- caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/core/bench_read.py +393 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/config.py +35 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/docker.py +33 -5
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/exec_stream.py +24 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/init.py +3 -5
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/inspect.py +323 -150
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/resolvers.py +39 -18
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm.py +30 -3
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/setup.py +24 -5
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/start.py +75 -1
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/stop.py +50 -2
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/update.py +38 -33
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/where.py +18 -1
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/bench_labels.py +9 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/bench_sites.py +42 -38
- caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/utils/cache.py +77 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/completion_utils.py +13 -1
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/config_utils.py +60 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/db_utils.py +75 -14
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +1 -0
- caffeinated_whale_cli-3.2.1/src/caffeinated_whale_cli/__init__.py +0 -1
- caffeinated_whale_cli-3.2.1/src/caffeinated_whale_cli/utils/cache.py +0 -46
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/LICENSE +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/setup.cfg +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/__main__.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/open.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/restore.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/setup.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/start.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/status.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/cred_bridge.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/doctor.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/fleet.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/label.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/list.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/open.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/restore.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm_bench.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/run.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/scale.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/status.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/supervision.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/url.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/version.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/main.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/cred_daemon.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/daemon_identity.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/shared_home.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/signals.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/startup.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
- {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.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.4.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
|
|
@@ -626,6 +626,11 @@ supervisor rather than stopping any container. Sibling benches keep serving, the
|
|
|
626
626
|
containers stay up, and `cwcli start --bench` brings it back. Stopping a bench
|
|
627
627
|
that is already stopped is a success, not an error.
|
|
628
628
|
|
|
629
|
+
A whole-instance stop takes the database down last and gives it up to 60 seconds
|
|
630
|
+
to finish its shutdown. If the database exits non-zero (137 means it was killed
|
|
631
|
+
before it finished shutting down), `stop` says so and exits non-zero instead of
|
|
632
|
+
reporting a clean stop.
|
|
633
|
+
|
|
629
634
|
**Example:**
|
|
630
635
|
|
|
631
636
|
```bash
|
|
@@ -2027,7 +2032,7 @@ cwcli config [SUBCOMMAND]
|
|
|
2027
2032
|
|
|
2028
2033
|
##### `config show` - Show the Effective Configuration
|
|
2029
2034
|
|
|
2030
|
-
The one read for everything: the custom search paths, the auto-inspect settings together with live daemon state and boot-hook state, the credential-bridge state (enabled, daemon, transport, wired instances), the tips setting, and the config-file and cache-DB locations.
|
|
2035
|
+
The one read for everything: the cache on/off state (and whether `CWCLI_NO_CACHE` is overriding it), the custom search paths, the auto-inspect settings together with live daemon state and boot-hook state, the credential-bridge state (enabled, daemon, transport, wired instances), the tips setting, and the config-file and cache-DB locations.
|
|
2031
2036
|
|
|
2032
2037
|
```bash
|
|
2033
2038
|
cwcli config show
|
|
@@ -2095,6 +2100,31 @@ cwcli config cache [SUBCOMMAND]
|
|
|
2095
2100
|
- **`list [--json]`** - List all projects currently in the cache
|
|
2096
2101
|
- Example: `cwcli config cache list --json`
|
|
2097
2102
|
|
|
2103
|
+
- **`disable`** / **`enable`** - Turn the on-disk cache off entirely, or back on (the default)
|
|
2104
|
+
- Example: `cwcli config cache disable`
|
|
2105
|
+
- Example: `cwcli config cache enable`
|
|
2106
|
+
- See ["Disabling caching entirely"](#disabling-caching-entirely) below.
|
|
2107
|
+
|
|
2108
|
+
###### Disabling caching entirely
|
|
2109
|
+
|
|
2110
|
+
cwcli keeps an on-disk SQLite cache (under `~/.cwcli/cache`, or `$CWCLI_HOME/cache`) that read commands - `inspect`, `status`, `where`, `open`, the `apps` group, tab completion, and every `axi` read verb - work from, so they do not re-inspect a running instance every time.
|
|
2111
|
+
|
|
2112
|
+
You can turn it off entirely, in two equivalent ways:
|
|
2113
|
+
|
|
2114
|
+
- **Config key:** `cwcli config cache disable` sets `[cache] enabled = false` in the config file. `cwcli config cache enable` reverts it.
|
|
2115
|
+
- **Environment variable:** `CWCLI_NO_CACHE=1` disables caching for a single shell or CI job; `CWCLI_NO_CACHE=0` re-enables it. The environment variable **overrides** the config key, so you can force caching on or off for one invocation regardless of the persisted setting.
|
|
2116
|
+
|
|
2117
|
+
With caching disabled:
|
|
2118
|
+
|
|
2119
|
+
- Every read resolves **live**, as if a fresh `inspect` had just run - a stale cached row can never be served.
|
|
2120
|
+
- **Nothing** is written to or read from the cache DB, and a pre-existing cache file is left in place untouched, so re-enabling restores the previous behaviour.
|
|
2121
|
+
- Tab completion degrades gracefully to a live/empty lookup rather than serving stale names.
|
|
2122
|
+
- `auto-inspect` refuses to start (it exists only to keep the cache warm, which does nothing when caching is off).
|
|
2123
|
+
- Bench labels still work, because they are also stored in each bench's marker file.
|
|
2124
|
+
- Safety-critical commands (`rm`/`rm-site`'s backup step, `apps checkout`'s wrong-copy detection) get correct live data.
|
|
2125
|
+
|
|
2126
|
+
Because every read resolves live, commands are slower than with the cache warm - this is a correctness/isolation mode, not the everyday default.
|
|
2127
|
+
|
|
2098
2128
|
##### `config auto-inspect` - Automatic Project Inspection
|
|
2099
2129
|
|
|
2100
2130
|
Manages automatic background inspection of running Frappe projects to keep cached data fresh (tab completion, project status queries, and other commands that rely on cached data).
|
|
@@ -2554,8 +2584,12 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
|
|
|
2554
2584
|
|
|
2555
2585
|
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.
|
|
2556
2586
|
|
|
2587
|
+
`cwcli axi setup-wizard <project> <site> [--country <country>] [--currency <currency>] [--timezone <timezone>]` headlessly completes a site's Frappe setup wizard. A fresh site - whether created by `cwcli init`/`cwcli axi init` without `--complete-setup`, or by a bare `bench new-site` outside cwcli entirely - is left in the setup-wizard state: `frappe.is_setup_complete()` is `false` and the `desktop:home_page` default stays `setup-wizard`, so a non-System-Manager desk renders with no navbar. This runs the same whitelisted RPC a person finishing the wizard in a browser triggers - `frappe.desk.page.setup_wizard.setup_wizard.setup_complete`, called via `bench execute ... --kwargs` with the args dict the wizard UI would have submitted (System Settings' country/currency/timezone/language; omitted flags fall back to `United States`/`USD`/`UTC`/`English`). It is **idempotent by Frappe's own design**: `setup_complete` checks `frappe.is_setup_complete()` first and no-ops under an advisory lock when the site is already set up, so re-running it is always safe - the report's `already_complete` field says whether it found the site already done. No user is created: `email`/`full_name`/`password` are deliberately left out of the args dict, so the `Administrator` account `cwcli init`'s `--admin-password` already provisioned is left untouched. Like the other bench-scoped verbs it takes `--bench`, has no `--yes`, and reports a stopped project as a usage error naming `cwcli start`.
|
|
2588
|
+
|
|
2557
2589
|
`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.
|
|
2558
2590
|
|
|
2591
|
+
`--complete-setup` runs the new site's Frappe setup wizard headlessly right after creation - the same operation `cwcli axi setup-wizard` performs standalone, sharing its exact implementation - so a fresh site does not land in the unfinished setup-wizard state a non-System-Manager desk renders with no navbar in. `--country`/`--currency`/`--timezone` tune it (require `--complete-setup`; each has a sensible default when omitted) and are usage errors on their own. Its outcome rides as a `setup_wizard` block inside the SAME `InitReport` document, not a second one; a failure there is never exit `0` even though the bench and site above it were already created successfully - the `setup_wizard` block still carries what happened.
|
|
2592
|
+
|
|
2559
2593
|
`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.
|
|
2560
2594
|
It is the agent form of `cwcli rm`, and it runs the same code.
|
|
2561
2595
|
The verified per-bench backup is taken and checked before anything is destroyed, and a backup that cannot be created and verified **aborts the removal with everything still intact**, so data is never destroyed without a confirmed copy on the host.
|
|
@@ -586,6 +586,11 @@ supervisor rather than stopping any container. Sibling benches keep serving, the
|
|
|
586
586
|
containers stay up, and `cwcli start --bench` brings it back. Stopping a bench
|
|
587
587
|
that is already stopped is a success, not an error.
|
|
588
588
|
|
|
589
|
+
A whole-instance stop takes the database down last and gives it up to 60 seconds
|
|
590
|
+
to finish its shutdown. If the database exits non-zero (137 means it was killed
|
|
591
|
+
before it finished shutting down), `stop` says so and exits non-zero instead of
|
|
592
|
+
reporting a clean stop.
|
|
593
|
+
|
|
589
594
|
**Example:**
|
|
590
595
|
|
|
591
596
|
```bash
|
|
@@ -1987,7 +1992,7 @@ cwcli config [SUBCOMMAND]
|
|
|
1987
1992
|
|
|
1988
1993
|
##### `config show` - Show the Effective Configuration
|
|
1989
1994
|
|
|
1990
|
-
The one read for everything: the custom search paths, the auto-inspect settings together with live daemon state and boot-hook state, the credential-bridge state (enabled, daemon, transport, wired instances), the tips setting, and the config-file and cache-DB locations.
|
|
1995
|
+
The one read for everything: the cache on/off state (and whether `CWCLI_NO_CACHE` is overriding it), the custom search paths, the auto-inspect settings together with live daemon state and boot-hook state, the credential-bridge state (enabled, daemon, transport, wired instances), the tips setting, and the config-file and cache-DB locations.
|
|
1991
1996
|
|
|
1992
1997
|
```bash
|
|
1993
1998
|
cwcli config show
|
|
@@ -2055,6 +2060,31 @@ cwcli config cache [SUBCOMMAND]
|
|
|
2055
2060
|
- **`list [--json]`** - List all projects currently in the cache
|
|
2056
2061
|
- Example: `cwcli config cache list --json`
|
|
2057
2062
|
|
|
2063
|
+
- **`disable`** / **`enable`** - Turn the on-disk cache off entirely, or back on (the default)
|
|
2064
|
+
- Example: `cwcli config cache disable`
|
|
2065
|
+
- Example: `cwcli config cache enable`
|
|
2066
|
+
- See ["Disabling caching entirely"](#disabling-caching-entirely) below.
|
|
2067
|
+
|
|
2068
|
+
###### Disabling caching entirely
|
|
2069
|
+
|
|
2070
|
+
cwcli keeps an on-disk SQLite cache (under `~/.cwcli/cache`, or `$CWCLI_HOME/cache`) that read commands - `inspect`, `status`, `where`, `open`, the `apps` group, tab completion, and every `axi` read verb - work from, so they do not re-inspect a running instance every time.
|
|
2071
|
+
|
|
2072
|
+
You can turn it off entirely, in two equivalent ways:
|
|
2073
|
+
|
|
2074
|
+
- **Config key:** `cwcli config cache disable` sets `[cache] enabled = false` in the config file. `cwcli config cache enable` reverts it.
|
|
2075
|
+
- **Environment variable:** `CWCLI_NO_CACHE=1` disables caching for a single shell or CI job; `CWCLI_NO_CACHE=0` re-enables it. The environment variable **overrides** the config key, so you can force caching on or off for one invocation regardless of the persisted setting.
|
|
2076
|
+
|
|
2077
|
+
With caching disabled:
|
|
2078
|
+
|
|
2079
|
+
- Every read resolves **live**, as if a fresh `inspect` had just run - a stale cached row can never be served.
|
|
2080
|
+
- **Nothing** is written to or read from the cache DB, and a pre-existing cache file is left in place untouched, so re-enabling restores the previous behaviour.
|
|
2081
|
+
- Tab completion degrades gracefully to a live/empty lookup rather than serving stale names.
|
|
2082
|
+
- `auto-inspect` refuses to start (it exists only to keep the cache warm, which does nothing when caching is off).
|
|
2083
|
+
- Bench labels still work, because they are also stored in each bench's marker file.
|
|
2084
|
+
- Safety-critical commands (`rm`/`rm-site`'s backup step, `apps checkout`'s wrong-copy detection) get correct live data.
|
|
2085
|
+
|
|
2086
|
+
Because every read resolves live, commands are slower than with the cache warm - this is a correctness/isolation mode, not the everyday default.
|
|
2087
|
+
|
|
2058
2088
|
##### `config auto-inspect` - Automatic Project Inspection
|
|
2059
2089
|
|
|
2060
2090
|
Manages automatic background inspection of running Frappe projects to keep cached data fresh (tab completion, project status queries, and other commands that rely on cached data).
|
|
@@ -2514,8 +2544,12 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
|
|
|
2514
2544
|
|
|
2515
2545
|
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.
|
|
2516
2546
|
|
|
2547
|
+
`cwcli axi setup-wizard <project> <site> [--country <country>] [--currency <currency>] [--timezone <timezone>]` headlessly completes a site's Frappe setup wizard. A fresh site - whether created by `cwcli init`/`cwcli axi init` without `--complete-setup`, or by a bare `bench new-site` outside cwcli entirely - is left in the setup-wizard state: `frappe.is_setup_complete()` is `false` and the `desktop:home_page` default stays `setup-wizard`, so a non-System-Manager desk renders with no navbar. This runs the same whitelisted RPC a person finishing the wizard in a browser triggers - `frappe.desk.page.setup_wizard.setup_wizard.setup_complete`, called via `bench execute ... --kwargs` with the args dict the wizard UI would have submitted (System Settings' country/currency/timezone/language; omitted flags fall back to `United States`/`USD`/`UTC`/`English`). It is **idempotent by Frappe's own design**: `setup_complete` checks `frappe.is_setup_complete()` first and no-ops under an advisory lock when the site is already set up, so re-running it is always safe - the report's `already_complete` field says whether it found the site already done. No user is created: `email`/`full_name`/`password` are deliberately left out of the args dict, so the `Administrator` account `cwcli init`'s `--admin-password` already provisioned is left untouched. Like the other bench-scoped verbs it takes `--bench`, has no `--yes`, and reports a stopped project as a usage error naming `cwcli start`.
|
|
2548
|
+
|
|
2517
2549
|
`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.
|
|
2518
2550
|
|
|
2551
|
+
`--complete-setup` runs the new site's Frappe setup wizard headlessly right after creation - the same operation `cwcli axi setup-wizard` performs standalone, sharing its exact implementation - so a fresh site does not land in the unfinished setup-wizard state a non-System-Manager desk renders with no navbar in. `--country`/`--currency`/`--timezone` tune it (require `--complete-setup`; each has a sensible default when omitted) and are usage errors on their own. Its outcome rides as a `setup_wizard` block inside the SAME `InitReport` document, not a second one; a failure there is never exit `0` even though the bench and site above it were already created successfully - the `setup_wizard` block still carries what happened.
|
|
2552
|
+
|
|
2519
2553
|
`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.
|
|
2520
2554
|
It is the agent form of `cwcli rm`, and it runs the same code.
|
|
2521
2555
|
The verified per-bench backup is taken and checked before anything is destroyed, and a backup that cannot be created and verified **aborts the removal with everything still intact**, so data is never destroyed without a confirmed copy on the host.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.4.0"
|
|
@@ -135,18 +135,23 @@ def _make_renderer(*, json_output, verbose):
|
|
|
135
135
|
return on_event
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def _refresh_cache(project_name, verbose):
|
|
138
|
+
def _refresh_cache(project_name, verbose, bench_path):
|
|
139
139
|
"""Refresh the cache through the existing writer so where/open/inspect stop lying.
|
|
140
140
|
|
|
141
|
-
Uses ``cache.recache_project``
|
|
141
|
+
Uses ``cache.recache_project`` scoped to the mutated bench (it persists via
|
|
142
142
|
``cache_project_data`` -> ``_redact_config_for_cache``), never a direct write.
|
|
143
143
|
Degrades to a warning; the mutation itself already succeeded.
|
|
144
144
|
"""
|
|
145
|
-
|
|
145
|
+
warnings: list = []
|
|
146
|
+
if not cache.recache_project(
|
|
147
|
+
project_name, verbose=verbose, bench_path=bench_path, warnings=warnings
|
|
148
|
+
):
|
|
146
149
|
stderr_console.print(
|
|
147
150
|
"[yellow]Warning:[/yellow] app mutation succeeded but refreshing the cache failed; "
|
|
148
151
|
"run 'cwcli inspect --update' to refresh."
|
|
149
152
|
)
|
|
153
|
+
for warning in warnings:
|
|
154
|
+
stderr_console.print(f"[yellow]Warning:[/yellow] {warning.text}")
|
|
150
155
|
|
|
151
156
|
|
|
152
157
|
def _report_and_exit(report, json_output, *, success_msg):
|
|
@@ -416,7 +421,7 @@ def install_apps(
|
|
|
416
421
|
# Refresh the cache if any step succeeded: a fetch changes available apps, an
|
|
417
422
|
# install changes a site's installed apps - both make the cache stale.
|
|
418
423
|
if any(r.ok for r in report.results):
|
|
419
|
-
_refresh_cache(project_name, verbose)
|
|
424
|
+
_refresh_cache(project_name, verbose, report.bench_path)
|
|
420
425
|
|
|
421
426
|
# The banner must match what actually happened: only claim "installed" when an
|
|
422
427
|
# install-app step ran (not for --fetch-only, a bench with no sites, or an
|
|
@@ -516,7 +521,7 @@ def uninstall_apps(
|
|
|
516
521
|
report = result.data
|
|
517
522
|
assert report is not None # OK/WARNING always carries a report
|
|
518
523
|
if any(r.ok for r in report.results):
|
|
519
|
-
_refresh_cache(project_name, verbose)
|
|
524
|
+
_refresh_cache(project_name, verbose, report.bench_path)
|
|
520
525
|
|
|
521
526
|
_report_and_exit(report, json_output, success_msg="App(s) uninstalled.")
|
|
522
527
|
|
|
@@ -585,7 +590,7 @@ def checkout_app(
|
|
|
585
590
|
# A checkout changes the app's git state (and possibly its reported version), so
|
|
586
591
|
# refresh the cache whenever any git step ran, matching install/update.
|
|
587
592
|
if any(r.ok for r in report.results):
|
|
588
|
-
_refresh_cache(project_name, verbose)
|
|
593
|
+
_refresh_cache(project_name, verbose, report.bench_path)
|
|
589
594
|
|
|
590
595
|
_report_and_exit(report, json_output, success_msg=f"Checked out {ref} into {app}.")
|
|
591
596
|
|
|
@@ -877,7 +877,8 @@ def axi_stop(
|
|
|
877
877
|
|
|
878
878
|
assert result.data is not None # OK always carries a StopOutcome
|
|
879
879
|
emit_result(result.data, warnings=result.warnings)
|
|
880
|
-
|
|
880
|
+
# A database killed mid-shutdown is a failed stop, not a successful one.
|
|
881
|
+
raise typer.Exit(1 if result.data.db_clean_shutdown is False else 0)
|
|
881
882
|
|
|
882
883
|
|
|
883
884
|
def _axi_stop_bench(project: str, bench: str) -> None:
|
|
@@ -925,7 +926,7 @@ def axi_start(
|
|
|
925
926
|
auto-resolves conflicting Frappe projects); a multi-bench project with no
|
|
926
927
|
``--bench`` is a usage error naming ``--bench``.
|
|
927
928
|
"""
|
|
928
|
-
_axi_resolve_port_conflicts(project, yes)
|
|
929
|
+
stop_warnings = _axi_resolve_port_conflicts(project, yes)
|
|
929
930
|
|
|
930
931
|
try:
|
|
931
932
|
result = core_start.start(project, bench=bench, autorestart=autorestart)
|
|
@@ -939,22 +940,23 @@ def axi_start(
|
|
|
939
940
|
raise typer.Exit(2)
|
|
940
941
|
|
|
941
942
|
assert result.data is not None # OK/WARNING always carries a StartOutcome
|
|
942
|
-
emit_result(result.data, warnings=result.warnings)
|
|
943
|
+
emit_result(result.data, warnings=[*stop_warnings, *result.warnings])
|
|
943
944
|
raise typer.Exit(0 if result.status in (CoreStatus.OK, CoreStatus.WARNING) else 1)
|
|
944
945
|
|
|
945
946
|
|
|
946
|
-
def _axi_resolve_port_conflicts(project: str, yes: bool) ->
|
|
947
|
+
def _axi_resolve_port_conflicts(project: str, yes: bool) -> list[Message]:
|
|
947
948
|
"""Never-prompt host-side port pre-step for ``axi start`` (D6).
|
|
948
949
|
|
|
949
950
|
Only meaningful when the container is not already up (a running instance owns
|
|
950
951
|
its ports). A non-Frappe holder cannot be auto-resolved -> ``CONFLICT``; a
|
|
951
952
|
Frappe holder is a ``CONFLICT`` naming ``--yes`` unless ``--yes`` was passed,
|
|
952
|
-
in which case the conflicting Frappe projects are stopped (stdout stays TOON)
|
|
953
|
+
in which case the conflicting Frappe projects are stopped (stdout stays TOON)
|
|
954
|
+
and their stop warnings are returned for the start document.
|
|
953
955
|
"""
|
|
954
956
|
from .start import _frappe_running, detect_port_conflicts
|
|
955
957
|
|
|
956
958
|
if _frappe_running(project):
|
|
957
|
-
return
|
|
959
|
+
return []
|
|
958
960
|
conflicting, non_frappe = detect_port_conflicts(project)
|
|
959
961
|
if non_frappe:
|
|
960
962
|
emit_axi_error(
|
|
@@ -985,9 +987,10 @@ def _axi_resolve_port_conflicts(project: str, yes: bool) -> None:
|
|
|
985
987
|
# contract. A project that vanished or stopped in that window is fine here
|
|
986
988
|
# (its ports are free either way) - the recheck below is what decides.
|
|
987
989
|
|
|
990
|
+
stop_warnings: list[Message] = []
|
|
988
991
|
for proj in conflicting:
|
|
989
992
|
try:
|
|
990
|
-
core_stop.stop(proj)
|
|
993
|
+
stop_warnings.extend(core_stop.stop(proj).warnings)
|
|
991
994
|
except CwcliError as e:
|
|
992
995
|
if e.kind is not ErrorKind.NOT_FOUND:
|
|
993
996
|
raise
|
|
@@ -1008,6 +1011,8 @@ def _axi_resolve_port_conflicts(project: str, yes: bool) -> None:
|
|
|
1008
1011
|
)
|
|
1009
1012
|
)
|
|
1010
1013
|
raise typer.Exit(exit_for(ErrorKind.CONFLICT))
|
|
1014
|
+
return stop_warnings
|
|
1015
|
+
return []
|
|
1011
1016
|
|
|
1012
1017
|
|
|
1013
1018
|
# ---------------------------------------------------------------------------- status
|
|
@@ -1700,7 +1705,10 @@ def axi_apps_checkout(
|
|
|
1700
1705
|
# epilogue gated on a condition already in the returned report. A failed recache
|
|
1701
1706
|
# is a stderr warning, NOT a non-zero exit: the checkout itself landed, and
|
|
1702
1707
|
# failing here would make an agent retry a mutation that already succeeded.
|
|
1703
|
-
|
|
1708
|
+
warnings = list(result.warnings)
|
|
1709
|
+
if any(r.ok for r in report.results) and not cache.recache_project(
|
|
1710
|
+
project, bench_path=report.bench_path, warnings=warnings
|
|
1711
|
+
):
|
|
1704
1712
|
print(
|
|
1705
1713
|
f"Warning: checkout completed, but re-caching '{project}' failed; "
|
|
1706
1714
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -1708,7 +1716,7 @@ def axi_apps_checkout(
|
|
|
1708
1716
|
flush=True,
|
|
1709
1717
|
)
|
|
1710
1718
|
|
|
1711
|
-
emit_result(report, warnings=
|
|
1719
|
+
emit_result(report, warnings=warnings)
|
|
1712
1720
|
# The exit code reads report.ok, NOT result.status: a failed step is a
|
|
1713
1721
|
# WARNING-shaped envelope, and WARNING maps to exit 0 everywhere else, so a
|
|
1714
1722
|
# status-driven code would report success for a checkout git refused.
|
|
@@ -1834,7 +1842,10 @@ def axi_apps_install(
|
|
|
1834
1842
|
# the cache whenever any step landed, matching the human verb. A failed recache
|
|
1835
1843
|
# is a stderr warning, NOT a non-zero exit: the install itself landed, and
|
|
1836
1844
|
# failing here would make an agent retry a mutation that already succeeded.
|
|
1837
|
-
|
|
1845
|
+
warnings = list(result.warnings)
|
|
1846
|
+
if any(r.ok for r in report.results) and not cache.recache_project(
|
|
1847
|
+
project, bench_path=report.bench_path, warnings=warnings
|
|
1848
|
+
):
|
|
1838
1849
|
print(
|
|
1839
1850
|
f"Warning: install completed, but re-caching '{project}' failed; "
|
|
1840
1851
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -1842,7 +1853,7 @@ def axi_apps_install(
|
|
|
1842
1853
|
flush=True,
|
|
1843
1854
|
)
|
|
1844
1855
|
|
|
1845
|
-
emit_result(report, warnings=
|
|
1856
|
+
emit_result(report, warnings=warnings)
|
|
1846
1857
|
# The exit code reads report.ok, NOT result.status: a failed step is a
|
|
1847
1858
|
# WARNING-shaped envelope, and WARNING maps to exit 0 everywhere else, so a
|
|
1848
1859
|
# status-driven code would report success for an install that failed.
|
|
@@ -2091,6 +2102,68 @@ def axi_build(
|
|
|
2091
2102
|
raise typer.Exit(0 if report.ok else 1)
|
|
2092
2103
|
|
|
2093
2104
|
|
|
2105
|
+
@app.command("setup-wizard")
|
|
2106
|
+
def axi_setup_wizard(
|
|
2107
|
+
project: str = typer.Argument(..., help="The Docker Compose project name."),
|
|
2108
|
+
site: str = typer.Argument(..., help="The site whose setup wizard to complete."),
|
|
2109
|
+
country: str = typer.Option(
|
|
2110
|
+
None, "--country", help="Setup wizard country (default: United States)."
|
|
2111
|
+
),
|
|
2112
|
+
currency: str = typer.Option(None, "--currency", help="Setup wizard currency (default: USD)."),
|
|
2113
|
+
timezone: str = typer.Option(None, "--timezone", help="Setup wizard timezone (default: UTC)."),
|
|
2114
|
+
bench: str = typer.Option(None, "--bench", help="Which bench: numeric index or label."),
|
|
2115
|
+
) -> None:
|
|
2116
|
+
"""Headlessly complete an EXISTING site's Frappe setup wizard; emit the report as TOON.
|
|
2117
|
+
|
|
2118
|
+
The standalone twin of `axi init --complete-setup`, sharing its exact
|
|
2119
|
+
implementation (`core.bench_ops.complete_setup_wizard`) - for a site that was
|
|
2120
|
+
created before this existed, or by anything other than cwcli (a bare `bench
|
|
2121
|
+
new-site`), and is stuck in the setup-wizard state: `desktop:home_page` stays
|
|
2122
|
+
`setup-wizard` and a non-System-Manager desk renders with no navbar.
|
|
2123
|
+
|
|
2124
|
+
Runs the SAME whitelisted Frappe RPC a human finishing the wizard in a
|
|
2125
|
+
browser triggers - `frappe.desk.page.setup_wizard.setup_wizard.setup_complete`
|
|
2126
|
+
- via `bench execute ... --kwargs`, with the args dict the wizard UI would
|
|
2127
|
+
have submitted (System Settings' country/currency/timezone/language).
|
|
2128
|
+
|
|
2129
|
+
IDEMPOTENT: Frappe's own `setup_complete` checks `frappe.is_setup_complete()`
|
|
2130
|
+
first and no-ops (under an advisory lock) when the site is already set up, so
|
|
2131
|
+
re-running this against an already-completed site is always safe. The report's
|
|
2132
|
+
`already_complete` field says whether this call found it already done.
|
|
2133
|
+
|
|
2134
|
+
No user is created: `email`/`full_name`/`password` are deliberately left out
|
|
2135
|
+
of the args dict, so the Administrator account cwcli's own `init` already
|
|
2136
|
+
provisioned is left untouched.
|
|
2137
|
+
|
|
2138
|
+
NO --yes and no auto-start: a stopped project is a usage error naming
|
|
2139
|
+
`cwcli start`.
|
|
2140
|
+
"""
|
|
2141
|
+
try:
|
|
2142
|
+
result = core_bench_ops.complete_setup_wizard(
|
|
2143
|
+
project,
|
|
2144
|
+
site=site,
|
|
2145
|
+
bench=bench,
|
|
2146
|
+
country=country,
|
|
2147
|
+
currency=currency,
|
|
2148
|
+
timezone=timezone,
|
|
2149
|
+
auto_start=False,
|
|
2150
|
+
on_event=_bench_op_narrate,
|
|
2151
|
+
)
|
|
2152
|
+
except CwcliError as error:
|
|
2153
|
+
emit_axi_error(error)
|
|
2154
|
+
raise typer.Exit(exit_for(error.kind)) from None
|
|
2155
|
+
|
|
2156
|
+
if result.status is CoreStatus.NEEDS_CHOICE:
|
|
2157
|
+
assert result.choice is not None # NEEDS_CHOICE always carries a Choice
|
|
2158
|
+
emit_axi_choice_as_usage_error(result.choice)
|
|
2159
|
+
raise typer.Exit(2)
|
|
2160
|
+
|
|
2161
|
+
assert result.data is not None # OK/WARNING always carries a SetupWizardReport
|
|
2162
|
+
report = result.data
|
|
2163
|
+
emit_result(report, warnings=result.warnings)
|
|
2164
|
+
raise typer.Exit(0 if report.ok else 1)
|
|
2165
|
+
|
|
2166
|
+
|
|
2094
2167
|
# ---------------------------------------------------------------------------- config
|
|
2095
2168
|
|
|
2096
2169
|
|
|
@@ -2272,6 +2345,25 @@ def axi_init(
|
|
|
2272
2345
|
"so that case already falls back to a safe default automatically (reported in `warnings`); "
|
|
2273
2346
|
"pass --uid to choose a specific uid instead. Must be a positive, non-root integer.",
|
|
2274
2347
|
),
|
|
2348
|
+
complete_setup: bool = typer.Option(
|
|
2349
|
+
False,
|
|
2350
|
+
"--complete-setup",
|
|
2351
|
+
help="After the site is created, headlessly complete its Frappe setup wizard (same "
|
|
2352
|
+
"effect as finishing it in a browser) so 'desktop:home_page' leaves 'setup-wizard' and "
|
|
2353
|
+
"a non-System-Manager desk renders with its navbar. Without this flag a fresh site is "
|
|
2354
|
+
"left exactly as before: in the unfinished setup-wizard state.",
|
|
2355
|
+
),
|
|
2356
|
+
setup_country: str = typer.Option(
|
|
2357
|
+
None,
|
|
2358
|
+
"--country",
|
|
2359
|
+
help="Setup wizard country (default: United States). Requires --complete-setup.",
|
|
2360
|
+
),
|
|
2361
|
+
setup_currency: str = typer.Option(
|
|
2362
|
+
None, "--currency", help="Setup wizard currency (default: USD). Requires --complete-setup."
|
|
2363
|
+
),
|
|
2364
|
+
setup_timezone: str = typer.Option(
|
|
2365
|
+
None, "--timezone", help="Setup wizard timezone (default: UTC). Requires --complete-setup."
|
|
2366
|
+
),
|
|
2275
2367
|
) -> None:
|
|
2276
2368
|
"""Provision a new instance, bench, and site; emit the report as TOON (never prompts).
|
|
2277
2369
|
|
|
@@ -2304,7 +2396,23 @@ def axi_init(
|
|
|
2304
2396
|
--port (exit 1); containers that do not come up point at `cwcli status` /
|
|
2305
2397
|
`cwcli logs` (exit 1). There is no --auto-start (compose `cwcli axi start`
|
|
2306
2398
|
then re-run) and no --verbose (stdout is always TOON).
|
|
2399
|
+
|
|
2400
|
+
--complete-setup runs the site's Frappe setup wizard headlessly right after
|
|
2401
|
+
creation (--country/--currency/--timezone tune it; omitted ones get sensible
|
|
2402
|
+
defaults), and its outcome rides as a `setup_wizard` block inside this SAME
|
|
2403
|
+
document - see `cwcli axi setup-wizard --help` for the standalone verb that
|
|
2404
|
+
completes an EXISTING site's wizard, which shares this exact implementation.
|
|
2307
2405
|
"""
|
|
2406
|
+
if (setup_country or setup_currency or setup_timezone) and not complete_setup:
|
|
2407
|
+
emit_axi_error(
|
|
2408
|
+
CwcliError(
|
|
2409
|
+
ErrorKind.USAGE,
|
|
2410
|
+
"init.setup_flags_without_complete_setup",
|
|
2411
|
+
"--country/--currency/--timezone require --complete-setup.",
|
|
2412
|
+
)
|
|
2413
|
+
)
|
|
2414
|
+
raise typer.Exit(exit_for(ErrorKind.USAGE))
|
|
2415
|
+
|
|
2308
2416
|
resolved_admin = admin_password or os.environ.get("CWCLI_ADMIN_PASSWORD")
|
|
2309
2417
|
if not resolved_admin:
|
|
2310
2418
|
emit_axi_error(
|
|
@@ -2319,11 +2427,17 @@ def axi_init(
|
|
|
2319
2427
|
raise typer.Exit(exit_for(ErrorKind.USAGE))
|
|
2320
2428
|
db_root = db_root_password or os.environ.get("CWCLI_DB_ROOT_PASSWORD") or "123"
|
|
2321
2429
|
|
|
2322
|
-
# Fail-fast on a bad site name or --uid BEFORE stage 1 (the human
|
|
2323
|
-
# ordering): the core re-validates in init_bench, but that runs only
|
|
2324
|
-
# compose has brought real containers up, which a usage error must never
|
|
2325
|
-
# behind.
|
|
2430
|
+
# Fail-fast on a bad project/site name or --uid BEFORE stage 1 (the human
|
|
2431
|
+
# init's ordering): the core re-validates in init_bench, but that runs only
|
|
2432
|
+
# after compose has brought real containers up, which a usage error must never
|
|
2433
|
+
# leave behind. Normalizing the project name here (Docker Compose lowercases it,
|
|
2434
|
+
# and validate_project_slug does the same) is load-bearing, not cosmetic: the
|
|
2435
|
+
# post-creation steps below (dev-services start, recache, setup wizard) look the
|
|
2436
|
+
# container up by this name, so a mixed-case argument would resolve nothing and
|
|
2437
|
+
# they would fail "project not found" - exactly what the human init avoids by
|
|
2438
|
+
# threading the normalized name everywhere.
|
|
2326
2439
|
try:
|
|
2440
|
+
project = core_init.validate_project_slug(project)
|
|
2327
2441
|
site = core_init.validate_new_site_name(site)
|
|
2328
2442
|
core_init.check_uid_override(uid)
|
|
2329
2443
|
except CwcliError as error:
|
|
@@ -2415,7 +2529,8 @@ def axi_init(
|
|
|
2415
2529
|
assert bench_result.data is not None # OK/WARNING always carries an InitReport
|
|
2416
2530
|
report = bench_result.data
|
|
2417
2531
|
|
|
2418
|
-
|
|
2532
|
+
recache_warnings: list = []
|
|
2533
|
+
if not cache.recache_project(project, bench_path=report.bench_path, warnings=recache_warnings):
|
|
2419
2534
|
print(
|
|
2420
2535
|
"Warning: bench created, but caching its bench path failed; "
|
|
2421
2536
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -2439,8 +2554,51 @@ def axi_init(
|
|
|
2439
2554
|
if warning.code in ("start.web_not_ready", "start.no_host_port"):
|
|
2440
2555
|
print(f"Warning: {warning.text}", file=sys.stderr, flush=True)
|
|
2441
2556
|
|
|
2442
|
-
|
|
2443
|
-
|
|
2557
|
+
setup_wizard_payload: dict | None = None
|
|
2558
|
+
setup_wizard_ok = True
|
|
2559
|
+
if complete_setup:
|
|
2560
|
+
print("Completing the setup wizard...", file=sys.stderr, flush=True)
|
|
2561
|
+
try:
|
|
2562
|
+
setup_result = core_bench_ops.complete_setup_wizard(
|
|
2563
|
+
project,
|
|
2564
|
+
site=report.site_name,
|
|
2565
|
+
bench_path=report.bench_path,
|
|
2566
|
+
country=setup_country,
|
|
2567
|
+
currency=setup_currency,
|
|
2568
|
+
timezone=setup_timezone,
|
|
2569
|
+
auto_start=False,
|
|
2570
|
+
on_event=_bench_op_narrate,
|
|
2571
|
+
)
|
|
2572
|
+
except CwcliError as error:
|
|
2573
|
+
setup_wizard_ok = False
|
|
2574
|
+
setup_wizard_payload = {"ok": False, "error": error.message}
|
|
2575
|
+
else:
|
|
2576
|
+
if setup_result.status is CoreStatus.NEEDS_CHOICE:
|
|
2577
|
+
# The container stopped in the window between bench creation and
|
|
2578
|
+
# here; the bench+site above are already real, so this degrades to
|
|
2579
|
+
# a reported failure rather than aborting before the InitReport
|
|
2580
|
+
# an agent needs is ever printed.
|
|
2581
|
+
setup_wizard_ok = False
|
|
2582
|
+
setup_wizard_payload = {
|
|
2583
|
+
"ok": False,
|
|
2584
|
+
"error": f"the frappe container for '{project}' is not running",
|
|
2585
|
+
}
|
|
2586
|
+
else:
|
|
2587
|
+
assert setup_result.data is not None # OK/WARNING always carries a report
|
|
2588
|
+
setup_wizard_payload = asdict(setup_result.data)
|
|
2589
|
+
setup_wizard_ok = setup_result.data.ok
|
|
2590
|
+
|
|
2591
|
+
# ONE document: the setup_wizard outcome, when requested, is a block inside
|
|
2592
|
+
# the SAME encode() call rather than a second emitted document.
|
|
2593
|
+
payload = asdict(bench_result.data)
|
|
2594
|
+
if setup_wizard_payload is not None:
|
|
2595
|
+
payload["setup_wizard"] = setup_wizard_payload
|
|
2596
|
+
typer.echo(toon.encode(payload, warnings=[*bench_result.warnings, *recache_warnings]))
|
|
2597
|
+
|
|
2598
|
+
# Never exit 0 for a failed explicit request: --complete-setup was asked for,
|
|
2599
|
+
# so its own failure must be visible in the exit code, not just the payload.
|
|
2600
|
+
overall_ok = bench_result.status in (CoreStatus.OK, CoreStatus.WARNING) and setup_wizard_ok
|
|
2601
|
+
raise typer.Exit(0 if overall_ok else 1)
|
|
2444
2602
|
|
|
2445
2603
|
|
|
2446
2604
|
# -------------------------------------------------------------------------------- rm
|
|
@@ -2711,7 +2869,8 @@ def axi_rm_site(
|
|
|
2711
2869
|
# A failed recache is a stderr warning, NOT a non-zero exit: the drop
|
|
2712
2870
|
# already happened, and failing here would make an agent retry a mutation
|
|
2713
2871
|
# that already succeeded.
|
|
2714
|
-
|
|
2872
|
+
warnings = list(result.warnings)
|
|
2873
|
+
if not cache.recache_project(project, bench_path=outcome.bench_path, warnings=warnings):
|
|
2715
2874
|
print(
|
|
2716
2875
|
f"Warning: site dropped, but re-caching '{project}' failed; "
|
|
2717
2876
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -2719,7 +2878,7 @@ def axi_rm_site(
|
|
|
2719
2878
|
flush=True,
|
|
2720
2879
|
)
|
|
2721
2880
|
|
|
2722
|
-
emit_result(outcome, warnings=
|
|
2881
|
+
emit_result(outcome, warnings=warnings)
|
|
2723
2882
|
# ok, NOT result.status: a drop with an unsafe archive is a WARNING-shaped
|
|
2724
2883
|
# envelope, which maps to exit 0 everywhere else and would hide leftover
|
|
2725
2884
|
# in-container credentials behind a green exit code.
|
|
@@ -2827,7 +2986,8 @@ def axi_rm_bench(
|
|
|
2827
2986
|
# A failed recache is a stderr warning, NOT a non-zero exit: the removal
|
|
2828
2987
|
# already happened, and failing here would make an agent retry a bench that
|
|
2829
2988
|
# is already gone.
|
|
2830
|
-
|
|
2989
|
+
warnings = list(result.warnings)
|
|
2990
|
+
if not cache.recache_project(project, bench_path=outcome.bench_path, warnings=warnings):
|
|
2831
2991
|
print(
|
|
2832
2992
|
f"Warning: bench removed, but re-caching '{project}' failed; "
|
|
2833
2993
|
"run 'cwcli inspect --update' to refresh.",
|
|
@@ -2835,7 +2995,7 @@ def axi_rm_bench(
|
|
|
2835
2995
|
flush=True,
|
|
2836
2996
|
)
|
|
2837
2997
|
|
|
2838
|
-
emit_result(outcome, warnings=
|
|
2998
|
+
emit_result(outcome, warnings=warnings)
|
|
2839
2999
|
# ok, NOT result.status: a partial removal (a site's backup trapped in the
|
|
2840
3000
|
# container) is a WARNING-shaped envelope, which maps to exit 0 everywhere
|
|
2841
3001
|
# else and would report a green exit for an incomplete removal.
|
|
@@ -101,6 +101,17 @@ def show(
|
|
|
101
101
|
console.print("[bold]Locations[/bold]")
|
|
102
102
|
console.print(f" Config file: [green]{report.config_file}[/green]")
|
|
103
103
|
console.print(f" Cache DB: [green]{report.cache_db}[/green]")
|
|
104
|
+
|
|
105
|
+
console.print("\n[bold]Cache[/bold]")
|
|
106
|
+
if report.cache_enabled:
|
|
107
|
+
console.print(" Enabled: [green]Yes[/green]")
|
|
108
|
+
else:
|
|
109
|
+
source = "CWCLI_NO_CACHE" if report.cache_env_override else "config"
|
|
110
|
+
console.print(f" Enabled: [red]No[/red] [dim](disabled via {source})[/dim]")
|
|
111
|
+
console.print(" [dim]Reads resolve live; nothing is written to the cache DB.[/dim]")
|
|
112
|
+
if report.cache_env_override:
|
|
113
|
+
console.print(" [dim]CWCLI_NO_CACHE is set and overrides the config key.[/dim]")
|
|
114
|
+
|
|
104
115
|
console.print("\n[bold]Search paths[/bold]")
|
|
105
116
|
if report.search_paths:
|
|
106
117
|
for path in report.search_paths:
|
|
@@ -351,6 +362,44 @@ def clear_cache(
|
|
|
351
362
|
)
|
|
352
363
|
|
|
353
364
|
|
|
365
|
+
@cache_app.command("enable")
|
|
366
|
+
def cache_enable():
|
|
367
|
+
"""
|
|
368
|
+
Turn the on-disk cache back on (the default).
|
|
369
|
+
"""
|
|
370
|
+
_apply_cache_setting(True)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@cache_app.command("disable")
|
|
374
|
+
def cache_disable():
|
|
375
|
+
"""
|
|
376
|
+
Turn the on-disk cache off entirely.
|
|
377
|
+
|
|
378
|
+
Every read command then resolves live (as if a fresh inspect ran) and nothing
|
|
379
|
+
is written to or read from the cache DB. The existing cache file is left in
|
|
380
|
+
place, so re-enabling restores it. Set CWCLI_NO_CACHE=1 for the same effect
|
|
381
|
+
per shell / in CI.
|
|
382
|
+
"""
|
|
383
|
+
_apply_cache_setting(False)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _apply_cache_setting(enabled: bool):
|
|
387
|
+
state = core_config.set_cache(enabled).data
|
|
388
|
+
assert state is not None
|
|
389
|
+
if enabled:
|
|
390
|
+
console.print("[green]Caching enabled.[/green] Reads use the on-disk cache.")
|
|
391
|
+
else:
|
|
392
|
+
console.print(
|
|
393
|
+
"[yellow]Caching disabled.[/yellow] Reads resolve live; nothing is written "
|
|
394
|
+
"to the cache DB (the existing cache is left in place)."
|
|
395
|
+
)
|
|
396
|
+
if state.env_override and state.enabled != enabled:
|
|
397
|
+
console.print(
|
|
398
|
+
"[yellow]Note:[/yellow] CWCLI_NO_CACHE is set and overrides this setting; "
|
|
399
|
+
f"caching is currently {'enabled' if state.enabled else 'disabled'} for this shell."
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
354
403
|
@cache_app.command("path")
|
|
355
404
|
def cache_path():
|
|
356
405
|
"""
|