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.
Files changed (102) hide show
  1. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/PKG-INFO +36 -2
  2. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/README.md +35 -1
  3. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/pyproject.toml +1 -1
  4. caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/__init__.py +1 -0
  5. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/apps.py +11 -6
  6. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/axi.py +182 -22
  7. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/config.py +49 -0
  8. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/console.html +8 -3
  9. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/init.py +12 -6
  10. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm.py +10 -2
  11. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm_bench.py +5 -2
  12. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/rm_site.py +5 -2
  13. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/run.py +20 -8
  14. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/serve.py +31 -2
  15. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/stop.py +8 -0
  16. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/update.py +15 -0
  17. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/apps.py +84 -65
  18. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/auto_inspect.py +8 -0
  19. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/bench_ops.py +159 -1
  20. caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/core/bench_read.py +393 -0
  21. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/config.py +35 -0
  22. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/docker.py +33 -5
  23. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/exec_stream.py +24 -0
  24. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/init.py +3 -5
  25. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/inspect.py +323 -150
  26. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/resolvers.py +39 -18
  27. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm.py +30 -3
  28. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/setup.py +24 -5
  29. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/start.py +75 -1
  30. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/stop.py +50 -2
  31. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/update.py +38 -33
  32. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/where.py +18 -1
  33. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/bench_labels.py +9 -0
  34. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/bench_sites.py +42 -38
  35. caffeinated_whale_cli-3.4.0/src/caffeinated_whale_cli/utils/cache.py +77 -0
  36. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/completion_utils.py +13 -1
  37. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/config_utils.py +60 -0
  38. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/db_utils.py +75 -14
  39. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +1 -0
  40. caffeinated_whale_cli-3.2.1/src/caffeinated_whale_cli/__init__.py +0 -1
  41. caffeinated_whale_cli-3.2.1/src/caffeinated_whale_cli/utils/cache.py +0 -46
  42. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/LICENSE +0 -0
  43. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/setup.cfg +0 -0
  44. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/__main__.py +0 -0
  45. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
  46. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
  47. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/doctor.py +0 -0
  48. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
  49. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
  50. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
  51. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
  52. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/open.py +0 -0
  53. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
  54. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/restore.py +0 -0
  55. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
  56. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
  57. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/setup.py +0 -0
  58. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/start.py +0 -0
  59. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/status.py +0 -0
  60. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
  61. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
  62. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
  63. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
  64. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
  65. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/cred_bridge.py +0 -0
  66. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
  67. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/doctor.py +0 -0
  68. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
  69. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
  70. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/fleet.py +0 -0
  71. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/label.py +0 -0
  72. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/list.py +0 -0
  73. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
  74. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/open.py +0 -0
  75. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
  76. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/restore.py +0 -0
  77. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm_bench.py +0 -0
  78. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
  79. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/run.py +0 -0
  80. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/scale.py +0 -0
  81. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/status.py +0 -0
  82. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/supervision.py +0 -0
  83. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
  84. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/url.py +0 -0
  85. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/core/version.py +0 -0
  86. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/main.py +0 -0
  87. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
  88. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
  89. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
  90. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
  91. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
  92. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/cred_daemon.py +0 -0
  93. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/daemon_identity.py +0 -0
  94. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
  95. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
  96. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
  97. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/shared_home.py +0 -0
  98. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/signals.py +0 -0
  99. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/startup.py +0 -0
  100. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
  101. {caffeinated_whale_cli-3.2.1 → caffeinated_whale_cli-3.4.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
  102. {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.2.1
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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "caffeinated-whale-cli"
7
- version = "3.2.1"
7
+ version = "3.4.0"
8
8
  authors = [
9
9
  { name = "Christopher McKay", email = "mckay.christopher73@outlook.com" },
10
10
  ]
@@ -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`` (a full inspect that persists via
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
- if not cache.recache_project(project_name, verbose=verbose):
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
- raise typer.Exit(0)
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) -> None:
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
- if any(r.ok for r in report.results) and not cache.recache_project(project):
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=result.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
- if any(r.ok for r in report.results) and not cache.recache_project(project):
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=result.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 init's
2323
- # ordering): the core re-validates in init_bench, but that runs only after
2324
- # compose has brought real containers up, which a usage error must never leave
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
- if not cache.recache_project(project):
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
- emit_result(bench_result.data, warnings=bench_result.warnings)
2443
- raise typer.Exit(0 if bench_result.status in (CoreStatus.OK, CoreStatus.WARNING) else 1)
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
- if not cache.recache_project(project):
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=result.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
- if not cache.recache_project(project):
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=result.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
  """