caffeinated-whale-cli 3.3.0__tar.gz → 3.5.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 (192) hide show
  1. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/PKG-INFO +523 -48
  2. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/README.md +521 -47
  3. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/pyproject.toml +7 -3
  4. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/__init__.py +1 -0
  5. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/admin.py +35 -0
  6. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/apps.py +31 -22
  7. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/axi.py +675 -55
  8. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/backups.py +787 -0
  9. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/config.py +17 -6
  10. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/db_access.py +105 -0
  11. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/dev_mode.py +109 -0
  12. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/doctor.py +2 -4
  13. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/init.py +45 -13
  14. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/integrations.py +218 -0
  15. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/new_bench.py +128 -0
  16. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/new_site.py +113 -0
  17. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/restore.py +15 -3
  18. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/rm.py +10 -2
  19. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/rm_bench.py +30 -12
  20. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/rm_site.py +39 -22
  21. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/run.py +20 -8
  22. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/schedule.py +266 -0
  23. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/serve.py +3175 -0
  24. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/start.py +4 -0
  25. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/status.py +26 -14
  26. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/stop.py +8 -0
  27. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/commands/ui.py +298 -0
  28. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/update.py +353 -14
  29. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/accounts.py +460 -0
  30. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/advisories.py +2308 -0
  31. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/app_catalog.py +1755 -0
  32. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/apps.py +112 -79
  33. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/auto_inspect.py +83 -2
  34. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/backup.py +35 -0
  35. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/backup_crypto.py +162 -0
  36. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/backup_drill.py +151 -0
  37. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/backup_store.py +1464 -0
  38. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/bench_ops.py +246 -10
  39. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/bench_read.py +393 -0
  40. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/community.py +947 -0
  41. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/cred_bridge.py +58 -11
  42. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/credbridge.py +41 -2
  43. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/daemon_recovery.py +63 -0
  44. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/db_access.py +289 -0
  45. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/db_password.py +114 -0
  46. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/dev_mode.py +281 -0
  47. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/directory.py +535 -0
  48. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/docker.py +210 -7
  49. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/doctor.py +101 -59
  50. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/editors.py +295 -0
  51. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/exec_stream.py +24 -0
  52. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/failure_causes.py +173 -0
  53. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/fleet.py +32 -18
  54. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/git_refs.py +105 -0
  55. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/init.py +245 -36
  56. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/inspect.py +311 -149
  57. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/integrations.py +760 -0
  58. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/ldap_client.py +416 -0
  59. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/logs.py +132 -0
  60. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/metrics.py +227 -0
  61. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/new_bench.py +150 -0
  62. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/new_site.py +241 -0
  63. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/open.py +3 -4
  64. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/resolvers.py +61 -18
  65. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/restore.py +236 -11
  66. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/rm.py +154 -154
  67. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/rm_bench.py +49 -29
  68. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/rm_site.py +245 -109
  69. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/s3.py +244 -0
  70. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/schedules.py +1098 -0
  71. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/setup.py +24 -5
  72. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/site_icons.py +173 -0
  73. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/site_logs.py +296 -0
  74. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/start.py +194 -18
  75. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/status.py +99 -22
  76. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/stop.py +103 -3
  77. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/supervision.py +120 -59
  78. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/transfer.py +537 -0
  79. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/unlock.py +34 -0
  80. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/update.py +628 -169
  81. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/update_preflight.py +1391 -0
  82. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/core/updates.py +418 -0
  83. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/version.py +28 -1
  84. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/jobs.py +2138 -0
  85. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/main.py +18 -0
  86. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +12 -4
  87. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +33 -89
  88. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/bench_labels.py +9 -0
  89. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/bench_sites.py +42 -38
  90. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/cache.py +35 -9
  91. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/cred_daemon.py +37 -82
  92. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/daemon_identity.py +87 -3
  93. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/db_utils.py +15 -16
  94. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/shared_home.py +12 -0
  95. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/startup.py +57 -19
  96. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/vscode_utils.py +18 -78
  97. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/__init__.py +1 -0
  98. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Advisories-Bf0LSPf4.js +3 -0
  99. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Apps-B16qXdaI.js +1 -0
  100. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Backups-C7O7JFBm.js +1 -0
  101. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Bench-A3tzjA6K.js +3 -0
  102. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Breadcrumbs.vue_vue_type_script_setup_true_lang-Bi51FeCS.js +1 -0
  103. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/DevModeBadge-BfGEDTSw.js +1 -0
  104. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/EmptyState-DUrIMfI-.js +1 -0
  105. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/FollowPane-CKiWroVq.js +2 -0
  106. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Instance-DbVqF51h.js +1 -0
  107. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Instances-DKiyXuo4.js +1 -0
  108. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Inter-Italic.var-BGHziHgI.woff2 +0 -0
  109. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Inter.var-C9xDBOS3.woff2 +0 -0
  110. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Job-CZ_bAUka.js +3 -0
  111. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/JobState-SVqMO--g.js +1 -0
  112. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Jobs-Bd9i_LH-.js +1 -0
  113. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/ListRows-CejsiiPF.css +1 -0
  114. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/ListRows.vue_vue_type_script_setup_true_lang-0MjX71k0.js +1 -0
  115. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/PageHeader.vue_vue_type_script_setup_true_lang-BXReRlyS.js +1 -0
  116. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/PageHero-7UhXbzdL.js +1 -0
  117. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/PerformancePanel-BX1wR4RA.js +1 -0
  118. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Schedules-CNFeg7hI.js +1 -0
  119. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Site-CcJ4RLz0.js +9 -0
  120. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/Sites-5otT_KFe.js +1 -0
  121. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/TabButtons.vue_vue_type_script_setup_true_lang-BEuhlOdD.js +1 -0
  122. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/TimeSeries-aHRzxvy5.js +20 -0
  123. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/UpdatePill-PPPcQPxB.js +1 -0
  124. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/code-insiders--L5huIRx.svg +37 -0
  125. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/code-ofeWxTsy.svg +41 -0
  126. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/cursor-jGcLH-b0.svg +1 -0
  127. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/index-9Bw95NEC.css +1 -0
  128. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/index-DAHKW80p.js +39 -0
  129. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-cyrillic-ext-opsz-normal-CfYZpDVp.woff2 +0 -0
  130. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-cyrillic-opsz-normal-BFQXbQ-f.woff2 +0 -0
  131. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-greek-ext-opsz-normal-BmPQvyvo.woff2 +0 -0
  132. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-greek-opsz-normal-BM0NlUE_.woff2 +0 -0
  133. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-latin-ext-opsz-normal-DpK-iCPk.woff2 +0 -0
  134. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-latin-opsz-normal-BwkfbSeq.woff2 +0 -0
  135. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/inter-vietnamese-opsz-normal-Ci9T9ieq.woff2 +0 -0
  136. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/jetbrains-mono-cyrillic-ext-wght-normal-EocZY2iu.woff2 +0 -0
  137. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  138. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  139. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  140. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  141. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  142. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/opsz-DreOFYE_.css +1 -0
  143. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/set-menu-BEG81cG0.js +1 -0
  144. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/assets/tabs-DWzq8uHr.js +1 -0
  145. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/csp.json +8 -0
  146. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/index.html +15 -0
  147. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/logo-whale.png +0 -0
  148. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli/webui/dist/theme-boot.js +7 -0
  149. caffeinated_whale_cli-3.5.0/src/caffeinated_whale_cli.egg-info/SOURCES.txt +185 -0
  150. caffeinated_whale_cli-3.3.0/src/caffeinated_whale_cli/__init__.py +0 -1
  151. caffeinated_whale_cli-3.3.0/src/caffeinated_whale_cli/commands/console.html +0 -2545
  152. caffeinated_whale_cli-3.3.0/src/caffeinated_whale_cli/commands/serve.py +0 -1292
  153. caffeinated_whale_cli-3.3.0/src/caffeinated_whale_cli.egg-info/SOURCES.txt +0 -96
  154. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/LICENSE +0 -0
  155. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/setup.cfg +0 -0
  156. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/__main__.py +0 -0
  157. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
  158. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
  159. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/inspect.py +0 -0
  160. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/label.py +0 -0
  161. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
  162. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
  163. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/open.py +0 -0
  164. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
  165. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
  166. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
  167. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/setup.py +0 -0
  168. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
  169. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/utils.py +0 -0
  170. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
  171. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
  172. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/config.py +0 -0
  173. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
  174. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
  175. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/label.py +0 -0
  176. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/list.py +0 -0
  177. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
  178. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/run.py +0 -0
  179. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/scale.py +0 -0
  180. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/url.py +0 -0
  181. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/core/where.py +0 -0
  182. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
  183. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
  184. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
  185. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/config_utils.py +0 -0
  186. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
  187. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/docker_utils.py +0 -0
  188. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
  189. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
  190. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/signals.py +0 -0
  191. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
  192. {caffeinated_whale_cli-3.3.0 → caffeinated_whale_cli-3.5.0}/src/caffeinated_whale_cli/utils/toon.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: caffeinated-whale-cli
3
- Version: 3.3.0
3
+ Version: 3.5.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
@@ -26,6 +26,7 @@ Requires-Dist: questionary>=2.1.0
26
26
  Requires-Dist: toml>=0.10.2
27
27
  Requires-Dist: peewee>=3.17.0
28
28
  Requires-Dist: packaging>=24.0
29
+ Requires-Dist: cryptography>=42
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: black>=25.1.0; extra == "dev"
31
32
  Requires-Dist: ruff>=0.8.0; extra == "dev"
@@ -41,8 +42,6 @@ Dynamic: license-file
41
42
  # Caffeinated Whale CLI
42
43
 
43
44
  A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development.
44
- The repository also contains the Phase 1 Tauri desktop shell, which runs the existing Console GUI over the same Python core.
45
- See [`desktop/README.md`](./desktop/README.md) for its current source-build status, supported platforms, and build instructions.
46
45
 
47
46
  ## Features
48
47
 
@@ -53,20 +52,20 @@ See [`desktop/README.md`](./desktop/README.md) for its current source-build stat
53
52
  - **Container Lifecycle** - Start, stop, and restart projects with ease
54
53
  - **Scale Beyond Six Benches** - Widen an instance's published port range on demand with `cwcli scale` so more than six benches are reachable from the host - database-safe (only the frappe service is recreated)
55
54
  - **Development Tools** - VS Code integration, log viewing, and command execution
55
+ - **Web UI** - A local browser dashboard over every instance, bench, site and app with `cwcli ui`: live health, logs, the `doctor` health check in Settings, and the everyday lifecycle actions
56
56
  - **Cache System** - Fast project inspection with SQLite-based caching and configuration storage
57
57
  - **Multi-Bench Support** - Address individual benches in a multi-bench instance by numeric index or a durable label with `--bench`
58
58
  - **Default Site Support** - Optional `--site` flag when default site is configured
59
- - **Backup & Restore** - Interactive site restoration with automatic file archive detection and P2P transfer support
59
+ - **Backup & Restore** - Interactive site restoration with automatic file archive detection and P2P transfer support, plus a verified backup store outside the instances with `cwcli backups` (retention, clone and move, off-site S3 copies)
60
60
  - **App Management** - List, install, uninstall, and update Frappe apps per bench and per site with `cwcli apps` (multi-site by default, `--json`, honest exit codes)
61
61
  - **Private App Repos** - `apps install`/`apps update` authenticate private GitHub/GitLab app fetches through your host's already-signed-in `gh`/`glab`, with no token ever entering the container
62
62
  - **Update Management** - App updates with automatic migrations and lock cleanup
63
63
  - **Self-Update** - Upgrade cwcli itself to the latest release with `cwcli self-update` (install-method aware)
64
- - **Environment Preflight** - Read-only checks for Docker, disk space, and tool dependencies with `cwcli doctor` (chainable exit code, `cwcli axi doctor` for agents; the shared cache layer may idempotently initialize cwcli's private cache directory while building the command tree)
64
+ - **Environment Preflight** - Read-only checks for Docker, disk space, and tool dependencies with `cwcli doctor` (chainable exit code, `cwcli axi doctor` for agents)
65
65
  - **Update Notices** - A passive, once/day "a newer cwcli is available" hint on stderr, shown only to a human at a TTY
66
66
  - **Auto-Inspection** - Background process to keep project cache fresh automatically
67
67
  - **System Integration** - Auto-start on system boot with platform-specific configurations
68
68
  - **Contextual Tips** - Helpful tips displayed during long-running operations to help you discover features
69
- - **Desktop Console** - A source-build Tauri 2 shell for the existing Console GUI on Linux and Windows; installer bundles are deferred to a later phase
70
69
 
71
70
  ## Installation
72
71
 
@@ -234,7 +233,7 @@ A cluster containing an unknown short option is rejected as a whole.
234
233
 
235
234
  Creates a complete Frappe development environment in a single step. Downloads compose files, starts containers, initializes bench, creates a site, and starts the bench's dev services.
236
235
 
237
- Blocks for roughly 10-20 minutes (image pulls, bench init, site creation). Running several `cwcli init` invocations in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance (`cwcli init existing-project --bench another-bench`) never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout.
236
+ Blocks for roughly 10-20 minutes (image pulls, bench init, site creation). Running several `cwcli init` invocations in parallel can OOM-kill the host on a memory-constrained machine - serialize them instead. Adding a bench to an already-running instance (`cwcli init existing-project --bench another-bench`) never disturbs it or its other benches: the frappe container is never pulled, recreated, or restarted, so every already-serving bench keeps running throughout. Re-running init against a stopped instance starts its existing containers as they are, without pulling images or recreating them, so its benches keep their Python environments. If the re-run would change that instance's compose settings, such as a different `--port` on an instance still on the default port range (8000-8005), init refuses with exit 1 (`instance.settings_changed`), leaves the compose file unchanged, and names each change; remove the instance with `cwcli rm` (which backs it up) and run init again, or rerun init without the changed options. For an instance created on another `--port` or widened by `cwcli scale`, init does not compare `--port`. An instance whose frappe container has never started, such as one left by a first init that failed at `docker compose up`, has no bench to protect, so init neither refuses nor uses `--no-recreate` there: it pulls images and recreates the containers with the new options.
238
237
 
239
238
  ```bash
240
239
  cwcli init [OPTIONS] [PROJECT_NAME]
@@ -258,7 +257,7 @@ cwcli init [OPTIONS] [PROJECT_NAME]
258
257
  | `--frappe-branch TEXT` | Frappe branch or tag for bench init, e.g. `version-16` or `v16.26.3` (default: version-16). Mutually exclusive with `--version` |
259
258
  | `--version TEXT` | Frappe version for bench init, resolved by shape: a bare major (`16` → `version-16` branch) or a full semantic version (`16.26.3` → `v16.26.3` tag). Malformed values are rejected with a non-zero exit. Mutually exclusive with `--frappe-branch` |
260
259
  | `--frappe-url TEXT` | Custom Frappe repository URL to build from (a fork), passed to `bench init --frappe-path`. Omit to use the default `frappe/frappe` repo. Pair with `--frappe-branch` to check out the fork's branch. A private fork authenticates through the same credential bridge as `apps install`/`apps update` (host `gh`/`glab`, no token in the container); sign in on the host first |
261
- | `--db-root-password TEXT` | MariaDB root password (default: 123) |
260
+ | `--db-root-password TEXT` | MariaDB root password. On a new instance MariaDB is created with it (handed over as a compose secret, never written into `docker-compose.yml`) and cwcli remembers it in `~/.cwcli/projects/{project_name}/conf/db-root-password` for later `rm-site`, `rm-bench`, `restore`, and re-run `init` commands. Omitted, the instance's remembered password is used, else the default `123`. An instance whose MariaDB already exists keeps the password it was created with |
262
261
  | `--admin-password TEXT` | Administrator password for the site, used verbatim. If omitted, a strong password is generated and printed once (interactive runs only); a non-interactive run must supply this flag |
263
262
  | `--install-erpnext` | Install ERPNext application after initialization |
264
263
  | `--erpnext-branch TEXT` | ERPNext branch to use (default: version-16) |
@@ -438,6 +437,78 @@ Without a reuse flag, a non-interactive session (non-TTY) where the bench alread
438
437
 
439
438
  ---
440
439
 
440
+ ### `new-bench` - Add a Bench to an Instance
441
+
442
+ Adds a bench to an existing instance with the same provisioning `init` gives bench 0: `bench init` for the chosen version or fork, the bench configs, a first site, and optionally ERPNext.
443
+ It then starts the bench's processes and fetches and installs any extra apps on the first site.
444
+ The other benches in the instance keep running.
445
+
446
+ ```bash
447
+ cwcli new-bench [OPTIONS] PROJECT_NAME BENCH_NAME
448
+ ```
449
+
450
+ **Options:**
451
+
452
+ | Option | Description |
453
+ |--------|-------------|
454
+ | `--site TEXT` | The first site's name (default: `<BENCH_NAME>.localhost`) |
455
+ | `--version TEXT` | Frappe version: a major (`16`, the default) or a tag (`16.3.0`) |
456
+ | `--frappe-branch TEXT` | A raw Frappe git ref instead of `--version` |
457
+ | `--frappe-url TEXT` | A Frappe fork to build from (private forks use the credential bridge) |
458
+ | `--erpnext` | Install ERPNext on the first site |
459
+ | `-a`, `--app TEXT` | An extra app (git URL or name) to fetch and install on the first site (repeatable) |
460
+ | `--admin-password TEXT` | Administrator password for the first site |
461
+ | `--db-root-password TEXT` | MariaDB root password (default: the one remembered at init; env `CWCLI_DB_ROOT_PASSWORD`) |
462
+ | `-y`, `--yes` | Auto-start a stopped instance without prompting |
463
+ | `-v`, `--verbose` | Stream bench's output |
464
+
465
+ The Administrator password comes from `--admin-password`, else the `CWCLI_ADMIN_PASSWORD` environment variable, else a hidden prompt.
466
+ A non-interactive session with neither refuses with exit 2.
467
+ A bench name that already exists is refused, never reused.
468
+ The command exits 1 when an extra app fails to install or the bench's processes did not start, even though the bench was created.
469
+
470
+ ```bash
471
+ CWCLI_ADMIN_PASSWORD=... cwcli new-bench my-project bench-v15 --version 15 --erpnext
472
+ cwcli new-bench my-project hrms-bench --app https://github.com/frappe/hrms
473
+ ```
474
+
475
+ **Agent surface:** `cwcli axi new-bench` is the TOON spelling.
476
+
477
+ ---
478
+
479
+ ### `new-site` - Add a Site to a Bench
480
+
481
+ Creates a site on an existing bench: `bench new-site`, then `install-app` for each `--app`, `bench use` with `--default`, and developer mode with `--dev-mode`.
482
+
483
+ ```bash
484
+ cwcli new-site [OPTIONS] PROJECT_NAME SITE
485
+ ```
486
+
487
+ **Options:**
488
+
489
+ | Option | Description |
490
+ |--------|-------------|
491
+ | `--bench TEXT` | Which bench: its numeric index or label |
492
+ | `-a`, `--app TEXT` | An app already on the bench to install on the site (repeatable) |
493
+ | `--default` | Make it the bench's default site |
494
+ | `--dev-mode` | Turn developer mode on for the new site |
495
+ | `--admin-password TEXT` | Administrator password |
496
+ | `--db-root-password TEXT` | MariaDB root password (default: the one remembered at init; env `CWCLI_DB_ROOT_PASSWORD`) |
497
+ | `-y`, `--yes` | Auto-start a stopped instance without prompting |
498
+ | `-v`, `--verbose` | Stream bench's output |
499
+
500
+ The Administrator password is resolved the same way as for `new-bench`: the flag, then `CWCLI_ADMIN_PASSWORD`, then a hidden prompt, and a non-interactive session with neither refuses with exit 2.
501
+ A site name that already exists on the bench is refused, and every `--app` must already be in the bench's `apps/` folder (fetch one first with `cwcli apps install <project> <app> --fetch-only`).
502
+
503
+ ```bash
504
+ CWCLI_ADMIN_PASSWORD=... cwcli new-site my-project shop.localhost --app erpnext --default
505
+ cwcli new-site my-project shop.localhost --bench 1 --dev-mode
506
+ ```
507
+
508
+ **Agent surface:** `cwcli axi new-site` is the TOON spelling.
509
+
510
+ ---
511
+
441
512
  ### `ls` - List Projects
442
513
 
443
514
  Scans Docker for Frappe/ERPNext projects and displays their status and ports.
@@ -568,6 +639,7 @@ cwcli start [OPTIONS] [PROJECT_NAME]...
568
639
  - **Per-process supervision:** each Procfile process runs under supervisord, so one can be restarted or auto-healed without disturbing the others
569
640
  - **Waits for the web server:** on a genuine launch, `start` blocks until the bench's **own** web port actually answers before reporting the bench as running, so a scripted `cwcli start && cwcli status` never catches a transient `degraded`. The port is read from that bench's `sites/common_site_config.json`, never assumed - waiting on a hardcoded `:8000` meant `--bench 1` watched bench 0's port and then warned that a healthy bench had not started. If the port cannot be read, the wait is **skipped** (`web_ready` stays unset) rather than spent on a guess. A timeout (60s) does not fail the start (the stack IS launched) - it prints a warning naming the real port and telling you to check `cwcli status` / `cwcli logs`
570
641
  - **Host port verification:** on both a genuine launch and an idempotent no-op, `start` warns when the bench's web server has no live host port binding. The warning names the container port and advises recreating the instance with `cwcli init`
642
+ - **Recovers a missing compose network:** when a Docker or WSL crash has removed the instance's own network (`<project>_default`), `start` recreates it, reconnects the stopped containers with their service aliases, and prints a warning, so the instance starts without manual repair. No container is recreated. A missing network the project does not own, or a recreate that fails, stops the start with the exact `docker network` command to run
571
643
  - **Port Conflict Detection:** Automatically checks if required ports are available
572
644
  - **Interactive Resolution:** Offers to stop conflicting Frappe projects (use `--yes` to auto-confirm)
573
645
  - **Process Identification:** Shows which processes are using ports (cross-platform)
@@ -626,6 +698,11 @@ supervisor rather than stopping any container. Sibling benches keep serving, the
626
698
  containers stay up, and `cwcli start --bench` brings it back. Stopping a bench
627
699
  that is already stopped is a success, not an error.
628
700
 
701
+ A whole-instance stop takes the database down last and gives it up to 60 seconds
702
+ to finish its shutdown. If the database exits non-zero (137 means it was killed
703
+ before it finished shutting down), `stop` says so and exits non-zero instead of
704
+ reporting a clean stop.
705
+
629
706
  **Example:**
630
707
 
631
708
  ```bash
@@ -767,7 +844,8 @@ Removes a Frappe project: its containers, named Docker volumes, own Docker Compo
767
844
  **WARNING:** This action is destructive and cannot be undone.
768
845
  Before deleting anything, the command re-caches the project, backs up the databases and files for all sites across every bench (a live `bench backup --with-files`, run once per bench on a multi-bench project), and archives the `docker-compose.yml`, `site_config.json`, and the project's `conf/` directory into a timestamped folder under `~/.cwcli/archive/` (or `$CWCLI_HOME/archive/` when the `CWCLI_HOME` override is set).
769
846
  The backup and the config archive are written first, and the named volumes and project directory are only deleted once both have succeeded.
770
- When volumes are being deleted (the default `--volumes`), a backup that cannot be fully created and verified blocks removal: each backup artifact is copied out of the container to the host archive and the database dump must be present and non-empty.
847
+ Each site's verified backup is then moved into the [backup store](#backups---the-backup-store) as a `before-removal` set, which outlives the instance.
848
+ When volumes are being deleted (the default `--volumes`), a backup that cannot be fully created and verified blocks removal: each backup artifact is copied out of the container to the host archive, and the database dump must be present and non-empty.
771
849
  If verification fails, the command aborts before any container is removed and exits non-zero, so data is never destroyed without a confirmed backup.
772
850
  Aborting before removal keeps the whole project intact (containers, volumes, network, directory, and cache), so a retry can still take a live backup from the running container.
773
851
  Under `--no-volumes` no volume data is destroyed, so a failed backup does not block the container, network, directory, and cache cleanup.
@@ -842,12 +920,21 @@ Permanently drops ONE site from a running bench, leaving the instance and every
842
920
  This is deliberately a separate, noun-scoped command rather than a `--site` flag on `cwcli rm`: `cwcli rm` destroys the whole instance (containers, volumes, the project directory), while `rm-site` only ever touches the one named site, so forgetting a flag can never turn a single-site cleanup into an instance-wide deletion.
843
921
 
844
922
  **WARNING:** This action is destructive and cannot be undone.
845
- `cwcli rm-site` runs `bench drop-site --force`, which drops the site's database and deletes its files.
923
+ `cwcli rm-site` backs the site up first, then runs `bench drop-site --no-backup`, which drops the site's database and deletes its files.
924
+
925
+ The backup is the same verified gate `cwcli rm` uses, taken by cwcli itself before anything is dropped: `bench --site <site> backup --with-files`, then this run's database dump and file archives are copied out to `~/.cwcli/archive/{project}_dropped_sites/{site}_{timestamp}/backups/` (or `$CWCLI_HOME/archive/...` when that override is set) and the dump is checked non-empty there.
926
+ If the backup fails or cannot be verified, nothing is dropped, and the command exits non-zero with bench's own error.
927
+ cwcli never relies on `bench drop-site`'s own backup, because with `--force` bench ignores a failed backup and drops the database anyway.
928
+ To drop a site whose backup cannot be taken, run `cwcli run <project> --path <bench> -i drop-site <site> --no-backup` yourself; the error names that command.
846
929
 
847
- `bench drop-site` archives the dropped site's full directory - including `site_config.json` (its database credentials and, if set, its encryption key) - inside the container's own `archived/sites/` folder before this command ever sees it.
930
+ `bench drop-site` then archives the dropped site's full directory - including `site_config.json` (its database credentials and, if set, its encryption key) - inside the container's own `archived/sites/` folder.
848
931
  Left there it would grow, unpruned, forever, for the rest of the bench's life.
849
- So `cwcli rm-site` immediately copies that one archive out to `~/.cwcli/archive/{project}_dropped_sites/` (or `$CWCLI_HOME/archive/...` when that override is set) - the same managed archive location `cwcli rm` already uses - and, only once that copy is verified non-empty on disk, deletes the in-container copy.
850
- If the copy cannot be verified, the in-container archive is left in place rather than deleted unbacked, and the command exits non-zero so the leftover credentials are never silently missed.
932
+ So `cwcli rm-site` copies that one archive out beside the backup (as `{site}.tar` in the same directory) and, only once that copy is verified non-empty on disk, deletes the in-container copy.
933
+ If anything cannot be verified, the in-container archive is left in place rather than deleted unbacked, and the command exits non-zero so the leftover credentials are never silently missed.
934
+
935
+ Once the drop has finished, the verified backup files are moved into the [backup store](#backups---the-backup-store) as a `before-removal` set (the `{site}.tar` stays in the archive directory).
936
+ To restore the dropped site, run `cwcli backups restore <project>/<site>/<id> <project> --site <site>`; `cwcli backups list <project>` shows the set's key.
937
+ If the move into the store fails, the files stay in the archive's `backups/` directory and nothing is lost.
851
938
 
852
939
  ```bash
853
940
  cwcli rm-site [OPTIONS] PROJECT_NAME SITE
@@ -866,7 +953,7 @@ cwcli rm-site [OPTIONS] PROJECT_NAME SITE
866
953
  |--------|-------------|
867
954
  | `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
868
955
  | `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
869
- | `--db-root-password TEXT` | MariaDB root password used by `bench drop-site` (default: `123`, matching the compose file's own default) |
956
+ | `--db-root-password TEXT` | MariaDB root password used by `bench drop-site` (default: the password remembered for the instance at `init`, else `123`) |
870
957
  | `-y`, `--yes` | Skip the destructive confirmation and auto-start containers |
871
958
  | `-v`, `--verbose` | Enable verbose output |
872
959
 
@@ -883,7 +970,7 @@ cwcli rm-site my-project task-42.localhost --yes
883
970
  cwcli rm-site my-project task-42.localhost --bench 1 --yes
884
971
  ```
885
972
 
886
- **Agent surface:** `cwcli axi rm-site <project> <site> --yes` emits the outcome as TOON, including `archived_host_path` and `archive_pruned_in_container`.
973
+ **Agent surface:** `cwcli axi rm-site <project> <site> --yes` emits the outcome as TOON, including `archived_host_path` (the directory holding the verified backup and the site archive) and `archive_pruned_in_container`.
887
974
  `--yes` is required (never a prompt) and grants ONLY consent to drop the site - it never auto-starts a stopped project, exactly like every other bench-scoped `axi` verb.
888
975
  See [For agents: the `cwcli axi` surface](#for-agents-the-cwcli-axi-surface).
889
976
 
@@ -898,8 +985,10 @@ A bench is a directory on the instance's shared workspace bind mount, and its si
898
985
  So `cwcli rm-bench` backs up and drops every site on the target bench (deleting each site's database and files), then deletes the bench directory.
899
986
 
900
987
  **WARNING:** This action is destructive and cannot be undone.
901
- Each site is dropped through the same verified copy-out that `cwcli rm-site` uses: `bench drop-site` backs the site up, and cwcli copies that archive out to `~/.cwcli/archive/{project}_dropped_sites/` (or `$CWCLI_HOME/archive/...` when that override is set) and verifies it on disk before the bench directory is deleted.
902
- If any site's backup cannot be copied out, the bench directory is kept (so the trapped backup is never destroyed) and the command exits non-zero.
988
+ Every site is backed up BEFORE any site is dropped, with the same verified gate `cwcli rm-site` and `cwcli rm` use: each site's database dump and file archives are copied out to `~/.cwcli/archive/{project}_dropped_sites/{site}_{timestamp}/backups/` (or `$CWCLI_HOME/archive/...` when that override is set) and the dump is checked non-empty there.
989
+ If any site's backup fails, nothing is dropped and nothing is deleted, and the command exits non-zero with bench's own error.
990
+ Each site is then dropped with `bench drop-site --no-backup` and its archived site directory copied out beside its backup.
991
+ If any site's archive cannot be copied out, the bench directory is kept (so the trapped archive is never destroyed) and the command exits non-zero.
903
992
 
904
993
  A bench that is still running is refused: stop it first with `cwcli stop <project> --bench <selector>`.
905
994
  `cwcli rm-bench` never deletes a bench out from under its own running code.
@@ -920,7 +1009,7 @@ cwcli rm-bench [OPTIONS] PROJECT_NAME
920
1009
  |--------|-------------|
921
1010
  | `--bench TEXT` | Which bench to remove: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
922
1011
  | `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
923
- | `--db-root-password TEXT` | MariaDB root password used by `bench drop-site` (default: `123`, matching the compose file's own default) |
1012
+ | `--db-root-password TEXT` | MariaDB root password used by `bench drop-site` (default: the password remembered for the instance at `init`, else `123`) |
924
1013
  | `-y`, `--yes` | Skip the destructive confirmation and auto-start the container |
925
1014
  | `-v`, `--verbose` | Enable verbose output |
926
1015
 
@@ -1259,23 +1348,51 @@ A bench that was not running is left alone.
1259
1348
 
1260
1349
  **`apps uninstall`** - removes each app from the target site(s) (`bench --site <site> uninstall-app`). This destroys site data, so it is gated by `-y`/`--yes` or an interactive confirmation (a non-TTY without `--yes` refuses). It uses the same resynchronise-and-verify step as install, so no supervised process - web, scheduler, or worker - can keep running against the code and tables of an app that is gone.
1261
1350
 
1262
- **`apps update`** - the canonical app-update path (what the deprecated `cwcli update` now delegates to). Updating the `frappe` framework app runs `bench update --reset`; other apps use the normal git-pull + migrate flow. After the migrations finish and maintenance mode is lifted, it runs the same **resynchronise** step as `apps install`, so a `git pull` cannot leave the bench's web, scheduler, and workers on the code that was there before it; every migrated site must answer Frappe before the run reports success. `--site` narrows which affected sites are migrated; if none of the named site(s) actually have the app installed, the command refuses and exits non-zero rather than silently migrating nothing (a genuine typo/mismatch guard - a bench with no affected sites at all still exits zero). It accepts the same migration flags as the [deprecated `update` command](#update---update-apps-and-migrate) (`--clear-cache`, `--clear-website-cache`, `--build`, `--skip-maintenance`, `--no-recache`). When updating the `frappe` framework app the flow runs the bench-wide `bench update --reset`, so `--site` and those per-app migration flags do not apply and are reported as ignored.
1263
- `--force` is an opt-in, **destructive** recovery for a conflicting update: if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`. `--force` also clears a git **"dubious ownership"** refusal before pulling: when an app's repository is owned by a different user than the container runs as - common on a shared-mode or migrated instance whose app sources are owned by a pre-migration uid - it re-owns the repository to the container user so the pull proceeds, rather than surfacing a raw git ownership error. A `cwcli start`/`cwcli restart` self-heals the same ownership so a plain `apps update` works too; `--force` fixes it inline without a restart.
1351
+ **`apps update`** - the canonical app-update path (what the deprecated `cwcli update` now delegates to). Updating the `frappe` framework app (or `erpnext`) is a **core update**: only the core app you name is reset to its remote branch (`apps update <p> frappe` never touches erpnext) (`git fetch <remote> <branch>` then `git reset --hard <remote>/<branch>`, for the app's own remote and checked-out branch, never bench's bench-wide `bench update --reset`, which hard-reset every app on the bench), every site's stale `locks` folder is cleared first and listed (a site whose migrate lock is held by a running migrate refuses the update before anything runs, with or without the preflight), the bench's requirements are reinstalled, then every affected site migrates under the same per-site maintenance lifecycle as any other app; other apps use the normal git-pull + migrate flow. After the migrations finish and maintenance mode is lifted, it runs the same **resynchronise** step as `apps install`, so a `git pull` cannot leave the bench's web, scheduler, and workers on the code that was there before it; every migrated site must answer Frappe before the run reports success. `--site` narrows which affected sites are migrated; if none of the named site(s) actually have the app installed, the command refuses and exits non-zero rather than silently migrating nothing (a genuine typo/mismatch guard - a bench with no affected sites at all still exits zero). It accepts the same migration flags as the [deprecated `update` command](#update---update-apps-and-migrate) (`--clear-cache`, `--clear-website-cache`, `--build`, `--skip-maintenance`, `--no-recache`). A core update honours `--site` and those flags like any other app, and always rebuilds assets.
1352
+ Because the core update never runs bench's own bench-wide update, nothing is set in `sites/common_site_config.json`: a site that fails to migrate is named as such (`failed_migrations`), its maintenance mode is cleared by cwcli's `finally`, and the summary states the bench state - which sites migrated, which did not and why, and whether any site is still in maintenance.
1353
+ `--force` is an opt-in, **destructive** recovery for a conflicting update (updating `frappe` gives it a different meaning, described under [the data-loss guard](#apps---manage-frappe-apps) below): if an app's `git pull` hits a **conflict** (local changes that would be overwritten, or a conflicted merge), it hard-resets that app to its remote tracking branch (`git fetch` then `git reset --hard @{u}`), discarding the local changes, so the update completes instead of failing. It fires **only on a conflict** - a clean update is untouched, and a pull that failed for another reason (network, auth) is left as the failure it is rather than having work discarded. Without `--force`, a conflict fails safely and discards nothing. Every force-reset app is reported (`force_reset_apps`) and a warning is printed at the reset. `--force` does not delete untracked files (it never runs `git clean`), matching `apps checkout --reset`. `--force` also clears a git **"dubious ownership"** refusal before pulling: when an app's repository is owned by a different user than the container runs as - common on a shared-mode or migrated instance whose app sources are owned by a pre-migration uid - it re-owns the repository to the container user so the pull proceeds, rather than surfacing a raw git ownership error. A `cwcli start`/`cwcli restart` self-heals the same ownership so a plain `apps update` works too; `--force` fixes it inline without a restart.
1354
+ **The `frappe` preflight.** A core update resets the core app's git checkout and reinstalls the bench's requirements before any site migrates, so a git problem (a branch the remote lacks, an unreachable remote, a detached HEAD) or a code requirement the bench cannot meet would otherwise surface mid-run.
1355
+ Updating `frappe` therefore runs a preflight first that predicts those failures in seconds, by evaluating bench's own rules against the same state bench reads.
1356
+ It reports everything it finds at once, grouped as blockers, data loss, could-not-verify and notices, each with the cwcli command that fixes it.
1357
+ Any blocker refuses the update with nothing changed and exits 1.
1358
+ Blockers are: an app on a branch its remote does not have, or one its remote's fetch refspec does not cover (a single-branch clone after `cwcli apps checkout` of another branch), an unreachable remote (bench contacts every remote unless `shallow_clone` is set), a detached HEAD, `frappe`/`erpnext` on `master` or not a git checkout, `frappe` without an `upstream` remote, a remote bench would pick by mistake, a repository git refuses for dubious ownership, a path the container user cannot write, a missing `mariadb-dump`/`gzip`/`yarn`/`node`, an unreachable database or `redis_cache`, a site that cannot be opened, a held migrate lock, a nearly full disk, an unreadable `common_site_config.json`, and `release_bench`.
1359
+ The preflight also reads the code the reset would install, from the same `git fetch` of each app's branch that bench runs, which moves only the remote-tracking branch and never the checked-out code.
1360
+ It blocks when that code needs a newer Python than the bench's environment (`requires-python`) or a newer Node than the container's (`engines.node`), since bench would fail reinstalling it after every backup and every reset, and when it is a frappe major-version upgrade, which bench stops to confirm and cwcli's run cannot answer.
1361
+ A `[tool.bench.frappe-dependencies]` the bench cannot satisfy - an app on `develop` declaring `frappe >=17` on a frappe 16 bench, or one app requiring another app's older line - is a blocker, because bench only warns and the site migrate then fails: the finding names the switch (`cwcli apps checkout <project> <app> version-N`), and `--fix-branches` has cwcli perform it before the update (a dirty tree still refuses).
1362
+ A checked-out branch the remote's fetch refspec does not cover (a narrow clone switched by hand) is widened in place - `git remote set-branches --add <remote> <branch>` plus a fetch - and logged, since the reset needs `<remote>/<branch>` to exist; `cwcli apps checkout` tracks a branch the same way.
1363
+ Uncommitted changes and unpushed commits that the reset would discard are reported as data loss, and refuse the update through the data-loss guard below.
1364
+ Any other check that cannot run or times out is reported as unverified and never blocks.
1365
+ No preflight can predict a failing patch, schema sync or asset build in the new code; those still surface only in the real run.
1366
+ `--no-preflight` skips the checks, for a false positive such as a remote that only a proxy-configured fetch can reach; bench then fails wherever it fails.
1367
+ It does not skip the data-loss guard below.
1368
+ The findings ride the report as `preflight` (each with a stable `code`, `severity`, `app`/`site`, `message` and `remedy`) plus `preflight_blocked`, on `--json` and `cwcli axi apps update` alike.
1369
+
1370
+ **The data-loss guard.** The core reset hard-resets the named core apps (`frappe`, `erpnext`) to their remote branch, so it silently discards their uncommitted changes to tracked files and commits that exist on no remote, branch or tag; other apps are not touched, so their local work is not at risk.
1371
+ Untracked files survive, because `git reset --hard` leaves them in place.
1372
+ Updating `frappe` therefore refuses, with nothing run, while a core app being reset holds such work, and exits 1 naming each app.
1373
+ An app whose local work cannot be checked refuses the same way, rather than being assumed clean.
1374
+ `--force` accepts the loss: the reset runs and discards that work.
1375
+ On this path `--force` also re-owns an app repository git refuses for dubious ownership before the checks run, and it never gets past a blocker.
1376
+ The guard is a safety check, not a prediction, so `--no-preflight` does not skip it; under `--no-preflight` it runs alone, with no remote or service probe.
1377
+ A terminal gets the same refusal as a script: there is no prompt, and `--force` is the only way through.
1378
+ With `shallow_clone` set in the bench config, bench also expires the reflog and prunes after each reset, so the discarded work cannot be recovered afterwards.
1379
+ The report carries `data_loss_refused` (the guard refused) and `data_loss_apps`: the apps holding the work, or after `--force`, the apps whose work bench was allowed to discard.
1380
+
1264
1381
  When `apps/<app>` is a symlink, the update writes through it and the run names the real directory it operated on.
1265
1382
  After the pull, update verifies that the copy the bench actually imports is the copy it just updated; when they differ it does **not** report plain success - it warns, names both paths, and exits non-zero so a script notices the site is still running the other copy.
1266
1383
 
1267
1384
  **`apps checkout`** - fetches and checks out an arbitrary branch, tag, or commit (`REF`) into an app that is **already present** in the bench (`apps/<app>`), so a specific feature branch can be put under test in the instance the app lives in.
1268
1385
  Unlike `apps install` (bench acquisition when needed, then site installation) and `apps update` (the tracked upstream on every app), this targets one existing checkout: it runs `git fetch <remote> <ref>` then `git checkout -B <ref> FETCH_HEAD` in the app directory (the remote is auto-detected - `upstream` for a bench-installed app, `origin` for a hand-cloned one).
1269
1386
  A **dirty working tree is refused** before anything is fetched, so uncommitted work in the in-instance checkout is never carried across a branch switch.
1270
- Once the checkout step moves the tree, the command runs the same **resynchronise** step `apps install` uses, even if a later `--reset` step fails.
1387
+ Once a step moves the tree (the `--reset` hard reset or the checkout), the command runs the same **resynchronise** step `apps install` uses, even if the checkout then fails.
1271
1388
  Swapping the code under a running bench and leaving it serving the branch you just moved off is the quietest form of that defect, because nothing errors at all.
1272
1389
  The sites proved are the ones with that app installed, so the command exits non-zero rather than reporting a checkout that left the app unable to serve.
1273
1390
  If one site's installed-app list cannot be read, the command warns that the site was not checked and excludes it from the proof set.
1274
1391
  If the bench's site list itself cannot be read, the resynchronisation fails.
1275
1392
  Dirty means precisely **anything `git status --porcelain` reports: staged changes, unstaged modifications to tracked files, and untracked files**.
1276
1393
  Files matched by `.gitignore` are not reported by git and so never count, which is why ordinary build residue (`__pycache__`, `node_modules`, `*.egg-info`) does not block a checkout.
1277
- `--reset` is the explicit opt-in through that refusal: it hard-resets the working tree to the fetched ref, discarding tracked local edits, which guarantees the clean tree a subsequent `build`/`migrate` needs.
1278
- Note one honest limit: `--reset` does **not** delete untracked files, because cwcli never runs `git clean` - it lets the checkout proceed and leaves them where they are.
1394
+ `--reset` is the explicit opt-in through that refusal: it hard-resets the working tree before switching, discarding tracked local edits, so the checkout lands the clean tree a subsequent `build`/`migrate` needs on the fetched ref.
1395
+ Note one honest limit: `--reset` does **not** delete untracked files, because cwcli never runs `git clean` - it lets the checkout proceed and leaves them where they are, and an untracked file the target ref would overwrite still makes the checkout fail.
1279
1396
  Like `apps update`, checkout names the real directory when `apps/<app>` is a symlink, and after moving the tree it verifies the bench imports the copy it just changed; if the bench imports a different copy it warns, names both paths, and exits non-zero rather than reporting a checkout the site will not actually run.
1280
1397
 
1281
1398
  **Private repos:** `apps install`/`apps update`/`apps checkout` (and the deprecated `update`), plus any bench subcommand run through [`cwcli run`](#run---execute-bench-commands) (`cwcli run <project> get-app <private-url>`, `cwcli run <project> update --pull`), transparently authenticate git fetches against private GitHub/GitLab app repos through your host's already-signed-in `gh`/`glab` - nothing to configure, no token ever stored in the container, and public repos are unaffected. Sign in on the host first (`gh auth login` / `glab auth login`). This credential bridge only serves operations run **through cwcli**; a raw `git` you run yourself inside the container (in a `docker exec` shell, or `cwcli open`) is not bridged and will prompt or fail auth. Run the fetch through `cwcli run <project> <bench-args>` instead, or - to bridge **interactive** git inside `cwcli open` or a `docker exec` shell (not just cwcli's own operations) - opt into the persistent [`config cred-bridge`](#config-cred-bridge---persistent-credential-bridge).
@@ -1318,7 +1435,7 @@ cwcli apps uninstall frappe-one --app payments --yes
1318
1435
 
1319
1436
  # Update an app (or the framework) - multi-bench aware
1320
1437
  cwcli apps update frappe-one erpnext
1321
- cwcli apps update frappe-one frappe # runs 'bench update --reset'
1438
+ cwcli apps update frappe-one frappe # core update: frappe only, locks cleared, requirements, migrate
1322
1439
 
1323
1440
  # Check a feature branch out into an already-installed app (private repo authed via the bridge)
1324
1441
  cwcli apps checkout frappe-one erpnext feature/new-report
@@ -1332,7 +1449,7 @@ cwcli apps checkout frappe-one erpnext feature/new-report --reset # force a cl
1332
1449
  > **Deprecated:** use [`cwcli apps update`](#apps---manage-frappe-apps) instead. `cwcli update` keeps working as an alias (and now prints a deprecation notice), so existing scripts are unaffected.
1333
1450
 
1334
1451
  Updates specified Frappe apps and migrates all sites where they are installed.
1335
- Updating the `frappe` framework app runs `bench update --reset`.
1452
+ Updating the `frappe` framework app is the core update described under [`apps update`](#apps---manage-frappe-apps).
1336
1453
 
1337
1454
  ```bash
1338
1455
  cwcli update [OPTIONS] PROJECT_NAME
@@ -1357,6 +1474,9 @@ cwcli update [OPTIONS] PROJECT_NAME
1357
1474
  | `-w`, `--clear-website-cache` | Clear website cache for all affected sites after migration |
1358
1475
  | `-b`, `--build` | Build assets after updating apps |
1359
1476
  | `--skip-maintenance` | Skip enabling maintenance mode for affected sites during update |
1477
+ | `--force` | Destructive. Updating `frappe`: run the core reset even though it discards uncommitted or unpushed work in that app (see [the data-loss guard](#apps---manage-frappe-apps)). Other apps: hard-reset an app whose `git pull` hits a conflict instead of failing. Either way, re-own app repositories git refuses for dubious ownership |
1478
+ | `--fix-branches` | Updating `frappe`: switch an app whose declared frappe-dependencies do not fit this bench to the matching `version-N` branch first, instead of refusing |
1479
+ | `--no-preflight` | Updating `frappe` only: skip the checks that run before the core reset (see [the `frappe` preflight](#apps---manage-frappe-apps)); the data-loss guard still runs |
1360
1480
  | `-y`, `--yes` | Auto-start stopped containers without prompting |
1361
1481
 
1362
1482
  **What It Does:**
@@ -1490,6 +1610,75 @@ Removed locks folder: /workspace/frappe-bench/sites/development.localhost/locks
1490
1610
 
1491
1611
  ---
1492
1612
 
1613
+ ### `dev-mode` - Developer Mode for a Site
1614
+
1615
+ Shows, turns on, or turns off Frappe's developer mode for one site.
1616
+ Frappe reads `developer_mode` from `common_site_config.json` (bench-wide), and a site's `site_config.json` overrides it, so the read names where the value you see comes from.
1617
+
1618
+ ```bash
1619
+ cwcli dev-mode [OPTIONS] PROJECT_NAME SITE [on|off]
1620
+ ```
1621
+
1622
+ | Option | Description |
1623
+ |--------|-------------|
1624
+ | `--bench TEXT` | Which bench: its numeric index or label |
1625
+ | `--common` | Set it bench-wide in `common_site_config.json` instead of the site's `site_config.json` |
1626
+ | `-y`, `--yes` | Skip the confirmation (and auto-start a stopped instance) |
1627
+
1628
+ Without `on` or `off` the command only reads the state.
1629
+ A change asks for confirmation, runs `bench set-config developer_mode 1|0` for the site (or with `-g` for `--common`), and then clears the site's cache.
1630
+ The command exits 1 if the cache could not be cleared.
1631
+
1632
+ ```bash
1633
+ cwcli dev-mode my-project example.localhost
1634
+ cwcli dev-mode my-project example.localhost on
1635
+ cwcli dev-mode my-project example.localhost off --common
1636
+ ```
1637
+
1638
+ ---
1639
+
1640
+ ### `db-access` - Check and Repair Site Database Users
1641
+
1642
+ Frappe grants each site's database user on the address the frappe container had when the site was created (`'<user>'@'172.21.0.5'`).
1643
+ When a restart gives the container a new address, every site answers HTTP 500 with `OperationalError 1045 (Access denied)` while nothing in the site itself changed.
1644
+ cwcli closes this three ways: every site it creates or restores gets its user widened to `'<user>'@'%'` right away, `start`/`restart` and the core update check the sites afterwards and report "database login refused for site X" instead of a bare 500, and `db-access` is the read and the one repair.
1645
+
1646
+ ```bash
1647
+ cwcli db-access check PROJECT_NAME [--bench SELECTOR] [--json]
1648
+ cwcli db-access repair PROJECT_NAME [--bench SELECTOR] [--site NAME]... --yes [--json]
1649
+ ```
1650
+
1651
+ **Options:**
1652
+
1653
+ | Option | Description |
1654
+ |--------|-------------|
1655
+ | `--bench`, `-b` | Bench selector (index or label) on a multi-bench instance |
1656
+ | `--site`, `-s` | `repair` only: limit the repair to these sites (default: every refused site) |
1657
+ | `--mariadb-root-password` | MariaDB root password (default: the one stored for the instance; env `CWCLI_DB_ROOT_PASSWORD`) |
1658
+ | `--yes`, `-y` | `repair` only: rename the users without asking |
1659
+ | `--json` | Machine-readable output |
1660
+
1661
+ **Examples:**
1662
+
1663
+ ```bash
1664
+ # Which sites would be refused from the container's current address? (exit 1 if any)
1665
+ cwcli db-access check staging16
1666
+
1667
+ # Rename each refused site's user to '%' so any container address may connect
1668
+ cwcli db-access repair staging16 --yes
1669
+ ```
1670
+
1671
+ **Output:**
1672
+
1673
+ ```
1674
+ tmpcore.localhost _a72993d7fe8f325a@{172.99.0.9} container 172.21.0.5 REFUSED
1675
+ ```
1676
+
1677
+ The repair is a `RENAME USER` of the host part only: the user name, password and grants are untouched, and a user already on `'%'` is left alone.
1678
+ The web UI shows the same finding on the restart or update job with a "Repair database access" button, and `cwcli axi db-access check` is the agent read.
1679
+
1680
+ ---
1681
+
1493
1682
  ### `backup` - Back Up a Site
1494
1683
 
1495
1684
  Runs `bench backup` for a site inside the project's frappe container, optionally including public and private files.
@@ -1533,6 +1722,77 @@ cwcli backup my-project --bench staging --with-files
1533
1722
 
1534
1723
  ---
1535
1724
 
1725
+ ### `backups` - The Backup Store
1726
+
1727
+ `cwcli backup` leaves its files inside the bench.
1728
+ `cwcli backups` keeps backup sets outside every instance, under `~/.cwcli/backups/<project>/<site>/<id>/`, so they survive `cwcli rm`.
1729
+ A set is identified by its key, `project/site/id`.
1730
+ Taking a set copies the bench backup out to the host and checks every part: the database dump must be complete, the file archives must list, and the site config must parse.
1731
+ Only a fully verified set has its copies inside the bench removed, and a set that fails a check is kept and marked unverified.
1732
+
1733
+ ```bash
1734
+ cwcli backups list [PROJECT] [--site NAME] [--json]
1735
+ cwcli backups capture PROJECT [--site NAME] [--bench SEL] [--no-files] [--reason REASON] [--note TEXT] [--json]
1736
+ cwcli backups verify KEY [--json]
1737
+ cwcli backups delete KEY [--yes]
1738
+ cwcli backups restore KEY PROJECT --site NAME [--bench SEL] [--admin-password PW] [--keep-config] [--no-migrate] [--yes] [--json]
1739
+ cwcli backups transfer PROJECT --site SOURCE[:TARGET]... [--mode clone|move] [--to-project NAME] [--to-bench SEL] [--no-files] [--keep-config] [--yes] [--json]
1740
+ cwcli backups send KEY...
1741
+ cwcli backups receive TICKET [--project NAME --site NAME] [--json]
1742
+ cwcli backups download KEY [--to DIR]
1743
+ cwcli backups upload TAR [--project NAME --site NAME] [--json]
1744
+ cwcli backups drill PROJECT --site NAME [--bench SEL] [--set KEY] [--json]
1745
+ cwcli backups prune PROJECT [--dry-run] [--yes] [--json]
1746
+ cwcli backups settings PROJECT [--daily N] [--weekly N] [--monthly N] [--safety-days N] [--safety-max N] [--min-free-gb N] [--stale-after-days N] [--schedule on|off] [--daily-at HH:MM] [--copy-on-backup/--no-copy-on-backup] [--json]
1747
+ cwcli backups s3 configure [--endpoint URL] [--bucket NAME] [--access-key KEY] [--prefix P] [--region R] [--enable/--disable]
1748
+ cwcli backups s3 list|copy|fetch|prune ...
1749
+ cwcli backups key export --to FILE | import --from FILE [--replace]
1750
+ ```
1751
+
1752
+ | Subcommand | What it does |
1753
+ |------------|--------------|
1754
+ | `list` | Lists the sets, newest first, with each set's verdict and the store's disk use |
1755
+ | `capture` | Takes a verified set of one site into the store (the site defaults like `cwcli backup`); exits 1 if the set did not verify |
1756
+ | `verify` | Runs every check on a stored set again |
1757
+ | `delete` | Deletes a set; a site's last verified set is never deleted |
1758
+ | `restore` | Restores a set into a site: the same site, a new one, another bench, or another instance. It replaces all of the target site's data, so it asks first; a non-interactive session needs `--yes`. By default the set's app settings are carried over and the target's `db_*` keys are kept |
1759
+ | `transfer` | Clones (default) or moves one or more sites to another name, bench, or instance, one site at a time |
1760
+ | `send` / `receive` | Sends sets to another machine as a sendme ticket, and files a received ticket into this machine's store |
1761
+ | `download` / `upload` | Writes one set as a plain (unencrypted) tar, and files such a tar into a store |
1762
+ | `drill` | Restores the site's latest verified set into a throwaway site, checks that it answers, drops it, and records the result |
1763
+ | `prune` | Applies the instance's retention policy; `--dry-run` lists what would be removed, and removing needs `--yes` |
1764
+ | `settings` | Shows or changes the instance's retention, safety, disk floor, daily schedule, and whether new backups are copied off-site |
1765
+ | `s3` | Encrypted off-site copies in an S3 bucket (AWS, MinIO, ...): `configure`, `list`, `copy`, `fetch`, `prune`. `list`, `copy`, `fetch` and `prune` work with whichever backup target integration is on, the S3 bucket or a community one. `configure` sets up the machine's S3 backup target integration, one bucket for every instance (the web UI's Settings > Integrations); it reads the secret key from `CWCLI_S3_SECRET_KEY`, tests the bucket, and turns the target on only when the bucket answers, exiting 1 otherwise. When another integration is the backup target it refuses unless `--switch`, which turns that one off once the bucket answers. `--disable` turns it off and keeps its settings |
1766
+ | `key` | Exports or imports the key that encrypts off-site copies; restoring an off-site copy on another machine needs it |
1767
+
1768
+ **Safety rules:**
1769
+
1770
+ - A clone or move onto a target site that already exists asks for confirmation (the web UI asks you to type the site name), and a non-interactive session needs `--yes`. Before the target is replaced, cwcli takes a verified `before-overwrite` set of it into the store; if that set does not verify, the target is left alone.
1771
+ - A move needs `--yes`, and drops each source only after its copy answered its checks, through the same backup gate `cwcli rm-site` uses.
1772
+ - Retention keeps a site's newest verified set always, keeps safety sets (`before-*` reasons) for 14 days with at most 5 per site, and keeps the newest set of each of the last 7 days and 4 weeks (S3: 7 days, 4 weeks, 12 months). Change these with `cwcli backups settings`.
1773
+ - `prune`, the scheduled prune, and the S3 prune never delete an unverified set. An unverified set takes no retention slot and stays until you delete it with `cwcli backups delete`.
1774
+ - Apart from the daily backup you can switch on with `cwcli backups settings --schedule on`, cwcli takes an automatic backup in one case only: a core (frappe or erpnext) update started from the web UI or a schedule first takes a verified `before-core-update` set of every site on the bench, and stops before changing anything if one does not verify. `cwcli rm`, `rm-site`, and `rm-bench` also move their removal backups into the store as `before-removal` sets.
1775
+
1776
+ ```bash
1777
+ # Take a verified set of the default site, then list the store
1778
+ cwcli backups capture my-project
1779
+ cwcli backups list my-project
1780
+
1781
+ # Restore a set into a new site on bench 1
1782
+ cwcli backups restore my-project/example.localhost/20261027_020000-b0 my-project --site copy.localhost --bench 1
1783
+
1784
+ # Clone a site to another instance under a new name
1785
+ cwcli backups transfer my-project --site example.localhost:staging.localhost --to-project other-project
1786
+
1787
+ # See what the retention policy would remove, then remove it
1788
+ cwcli backups prune my-project --dry-run
1789
+ cwcli backups prune my-project --yes
1790
+ ```
1791
+
1792
+ **Agent surface:** `cwcli axi backups list|capture|verify|prune|settings` is the TOON spelling.
1793
+
1794
+ ---
1795
+
1536
1796
  ### `restore` - Restore Site from Backup
1537
1797
 
1538
1798
  Interactively restore a site from a backup with automatic detection of file archives and encryption keys. Supports both local restoration and peer-to-peer backup transfers via sendme.
@@ -1557,7 +1817,7 @@ cwcli restore [OPTIONS] PROJECT_NAME
1557
1817
  | `--bench TEXT` | Which bench to target: its numeric index or label (see [Working with Multiple Benches](#working-with-multiple-benches)) |
1558
1818
  | `-p`, `--path TEXT` | Explicit bench directory inside the container (lower-level alternative to `--bench`; cannot be combined with it) |
1559
1819
  | `--mariadb-root-username TEXT` | MariaDB root username (defaults to `root`; prompted interactively when omitted) |
1560
- | `--mariadb-root-password TEXT` | MariaDB root password. Prompted interactively when omitted; a non-TTY without this flag refuses rather than proceeding with an empty password |
1820
+ | `--mariadb-root-password TEXT` | MariaDB root password. When omitted, the password remembered for the instance at `init` is used for the `root` user; with none remembered it is prompted interactively, and a non-TTY without this flag refuses rather than proceeding with an empty password |
1561
1821
  | `--admin-password TEXT` | Set administrator password after restore |
1562
1822
  | `--send` | **P2P Mode:** Share backup with another machine via peer-to-peer transfer |
1563
1823
  | `--receive` | **P2P Mode:** Receive backup from another machine via peer-to-peer transfer |
@@ -1915,15 +2175,13 @@ hint to run `cwcli start` to bring it under cwcli's supervisor. In this mode the
1915
2175
  per-process supervisord `state` is unavailable (there is no supervisord to ask);
1916
2176
  `up` comes straight from `ps`. `status` only reads - it never launches supervisord.
1917
2177
 
1918
- **Maintenance mode is called out plainly.** A site stuck in maintenance mode answers
1919
- HTTP 503 to every user with no other signal - the state a `migrate` / `apps update`
1920
- killed mid-run (a closed terminal, a `kill`, a service stop) leaves behind. When a
1921
- bench's site is in maintenance mode, `status` prints `MAINTENANCE MODE ON` for that
1922
- site (even without `-v`, since it is a fault, not detail) and names the way out
1923
- (`cwcli run <project> --site <site> set-maintenance-mode off`); `cwcli axi status`
1924
- carries it as `maintenance_mode` plus a `status.maintenance_mode` warning. It is read
1925
- live from the site's `site_config.json`, not inferred from the 503, and is skipped in
1926
- the `--watch` loop (which makes no web probe).
2178
+ **Maintenance mode is called out plainly.**
2179
+ A site stuck in maintenance mode answers HTTP 503 to every user with no other signal - the state a `migrate` / `apps update` killed mid-run (a closed terminal, a `kill`, a service stop) leaves behind.
2180
+ When a bench's site is in maintenance mode, `status` prints `MAINTENANCE MODE ON` for that site (even without `-v`, since it is a fault, not detail) and names the way out (`cwcli run <project> --site <site> set-maintenance-mode off`); `cwcli axi status` carries it as `maintenance_mode` plus a `status.maintenance_mode` warning.
2181
+ It is read live the way Frappe reads it, not inferred from the 503: the bench's `sites/common_site_config.json` first, with the site's own `site_config.json` overriding it.
2182
+ A failed `bench update` (the `cwcli apps update <project> frappe` path) leaves `maintenance_mode` and `pause_scheduler` set in that bench-wide file, which holds down every site on the bench that does not override them.
2183
+ `status` reports each one it finds there (`status.maintenance_mode` and `status.scheduler_paused`) with the bench-wide way out, `cwcli run <project> set-config --global --parse maintenance_mode 0` (and the same for `pause_scheduler`); `set-maintenance-mode off` would bring back only the one site.
2184
+ These reads are skipped in the `--watch` loop (which makes no web probe).
1927
2185
 
1928
2186
  **Per-bench, per-process detail (stderr):** one sub-block per bench, headed by its
1929
2187
  index, path, label, and own aggregate, with a web line naming the host-reachable
@@ -1960,6 +2218,212 @@ cwcli status -w --interval 5 frappe-one # refresh every 5s
1960
2218
 
1961
2219
  ---
1962
2220
 
2221
+ ### `ui` - Open the Web UI
2222
+
2223
+ Opens a local browser dashboard over your instances: every instance, bench, site and app in one place, live process health, a resumable log pane, Settings (with the `doctor` health check inside it), a command palette (Ctrl+K) that searches the cache, and the everyday lifecycle actions.
2224
+ Long work runs as detached jobs with a durable event log, so it continues when the tab closes and can be opened again from Jobs.
2225
+ A failed job has a Retry button on its page that starts a new job with the same settings, so a creation that failed at the image pull continues from the project directory it left behind.
2226
+ The passwords entered for a job are kept in the daemon's memory only (never in the job record or on disk) so Retry, and answering a decision the job stopped on, can reuse them; if the daemon has restarted, a creation reopens the form on its password step instead.
2227
+ A retry never carries a destructive confirmation over: a removal asks for its typed name again, and a job that discards local work (`apps checkout --reset`, `apps update --force`) has no Retry and must be started again to confirm it.
2228
+ A job that was interrupted, whose process vanished, that lost the output stream of a command inside the instance, or an `apps update` with a step whose outcome is unknown, has no Retry either, because its work may still be running inside the instance; its page says to review the last event before running it again.
2229
+ The daemon binds `127.0.0.1` only and is served by cwcli itself; nothing is installed or fetched, and no Node is needed because the UI ships prebuilt inside the package.
2230
+
2231
+ ```bash
2232
+ cwcli ui [OPTIONS]
2233
+ ```
2234
+
2235
+ **Options:**
2236
+
2237
+ | Option | Description |
2238
+ |--------|-------------|
2239
+ | `-p`, `--port INTEGER` | Port to listen on (default: 8765) |
2240
+ | `--foreground` | Run the daemon in this terminal (Ctrl-C to stop) instead of detached |
2241
+ | `--stop` | Stop the detached daemon |
2242
+ | `--no-browser` | Print the address instead of opening a browser |
2243
+
2244
+ **How it runs:** `cwcli ui` starts the daemon detached if none is running (otherwise it reuses the live one, unless cwcli was reinstalled since that daemon started, in which case it restarts it onto the new code), then opens your browser on `http://127.0.0.1:8765/`.
2245
+ It also starts auto-inspect or the credential bridge when either is enabled with its boot unit but not running, and says so, or says why it could not.
2246
+ The page asks for the **administrator account**: the username is `admin` and the first password is `admin`, and that first sign-in (and every sign-in after a reset) has to choose a new password - at least 8 characters, not the default - before anything else in the UI answers.
2247
+ Sessions last 8 hours idle and 7 days at most; "Keep me signed in" makes both 30 days.
2248
+ Five failed sign-ins within ten minutes lock the address for a minute and refuse further wrong passwords for the username, while the username's correct password still signs in, so nobody can lock the owner out.
2249
+ Your account (in Settings) changes the password, lists the other signed-in browsers, signs them out, and signs out.
2250
+ Appearance (in Settings, or the sidebar's Appearance row) picks the gradient theme and light, dark or the system setting (the default), and turns the moving visual effects off; the choice is kept in this browser.
2251
+ If the password is lost, `cwcli admin reset-password` on the host puts the account back to `admin` / `admin` with the change forced again (see below).
2252
+ With LDAP / Active Directory sign-in turned on (Settings > Integrations, below), directory users sign in on the same page with their own directory password, and the local `admin` account keeps working beside them as the way in when the directory is unreachable or misconfigured.
2253
+
2254
+ **Roles:** every signed-in person is an Admin, an Operator or a Viewer, and the daemon checks each request against the role it needs.
2255
+ A Viewer reads everything the UI shows except the contents of backup sets (no download).
2256
+ An Operator also runs the fleet: lifecycle actions, jobs, backups, transfers and schedules.
2257
+ An Admin also changes Settings, Integrations and each instance's backup settings (retention, the daily schedule and off-site copies), updates cwcli itself, and runs everything that destroys data or code: removing or moving instances, benches, sites and backup sets, restoring a set over a site, pruning local or off-site backups, checking out an app discarding local changes, and forcing an app update past its data-loss guard.
2258
+ A schedule that runs one of those (a prune) is created, changed, enabled or run by hand only by an Admin, and each scheduled run checks that the Admin who last saved it is still an Admin (a directory user is looked up in the directory again); otherwise the run is skipped and the schedule shows why.
2259
+ The local `admin` account and a programmatic client holding the bearer token are Admins; a directory user's role comes from their directory groups.
2260
+ The page hides what a role cannot do, and a request beyond the role is refused with `auth.role_forbidden`.
2261
+ Programmatic clients (scripts, the self-update restart) keep using the bearer token in `CWCLI_SERVE_TOKEN`; nothing about a sign-in is ever in a URL.
2262
+ `cwcli ui --stop` stops it.
2263
+ While the daemon runs it polls process health for every running instance; the web check only runs for an instance a page has open.
2264
+ That check is a bare TCP connect to the bench's web port, not an HTTP request, so polling writes nothing to the bench's web access log; the Bench page shows its verdict as `Listening` or `Not listening` next to the web port, or `unknown` while the latest health probe has failed and the page is showing the last successful probe's readings.
2265
+ It refuses any request whose `Host` is not this machine, so another site in your browser cannot read or drive it.
2266
+
2267
+ **What it can do:** everything the CLI reads (`ls`, `status`, `inspect`, `where`, `doctor`, `logs`, `axi url`, `config show`) and the safe lifecycle set: start, stop and restart an instance or a bench, restart one process, set a bench label, unlock a site, take a site out of maintenance mode, widen the port range (the same core confirmation `cwcli scale` asks for, typed back as the instance name), and check out an app ref without `--reset`.
2268
+ These lifecycle changes run as jobs too, so their steps and outcome remain visible after navigation.
2269
+ When an action needs a stopped instance running, the page offers to start it first: an action on a bench, site or app starts that bench (`cwcli start <project> --bench <n>`), so a multi-bench instance never asks which bench, and an instance-level action starts only the containers, never a bench.
2270
+ Remove bench on a stopped instance starts only its containers, and stops them again if the removal does not complete.
2271
+ Restarting an instance from the page brings back exactly the benches that were serving (under cwcli's supervisor or `bench start`), unlike `cwcli restart <project>`, which brings back the first bench.
2272
+ The long-job forms cover `init`, `apps install`, `apps update`, `apps checkout`, `backup`, `migrate`, `run-tests`, `build`, `setup-wizard`, and `self-update`.
2273
+ `apps update` runs the same preflight as the CLI and shows every finding in the job view before the reset begins.
2274
+ A failed job leads with its plain cause (for example MariaDB rejecting the database root password, or git failing to authenticate) when cwcli recognizes one in the failing step's output, and otherwise says it did not and points at that output.
2275
+ Interrupt requests use the command's cleanup path; Detach only leaves the page, and an active record whose process disappears becomes `unknown` instead of being reported as failed or successful.
2276
+ On Windows a job that has not begun its cleanup five minutes after Interrupt, or has not finished it two minutes after beginning, is terminated; one that never began is marked `unknown` because its cleanup may not have run.
2277
+ Jobs keeps every active job and the 100 most recent finished ones; older finished records are pruned when a new job starts.
2278
+ The `self-update` job restarts the local daemon and the open page reloads after its protocol check sees the replacement.
2279
+ The guarded destructive verbs run as jobs too, each behind a typed confirmation of the exact name of what it destroys: remove an instance (`rm`: every site backed up first and the named volumes deletable only while the instance runs, mirroring `cwcli axi rm`, unless Force is ticked, which skips the verified backup like `cwcli rm --no-backup` after a red warning naming what is lost for good), drop a site (`rm-site`), remove a stopped bench (`rm-bench`), check out an app ref discarding local changes (`apps checkout --reset`, typed back as the app's name), and force a `frappe` update past the data-loss guard (`apps update` with Force ticked, typed back as the bench's name).
2280
+ A failed backup drops nothing, exactly as on the command line, and the job page shows bench's own reason.
2281
+ Settings is writable for an Admin: cache on/off, forget one remembered project or clear the whole cache (confirmed first), add, remove and prune bench search paths (the prune check names any stopped instance that keeps it from pruning), auto-inspect and its interval and boot hook, the persistent credential bridge and its boot unit, a Start (or Restart) button for either daemon while its feature is enabled, with the daemon log's last line shown while it is down, tips, and the agent SessionStart hooks `cwcli axi setup` installs; every control sits beside the terminal command that does the same.
2282
+ Schedules puts any of those jobs on a timetable (templates, a friendly recurrence picker with the cron underneath, one-off runs, recipes of chained steps, "after a successful job" and "when the instance starts" triggers, a week's timeline, and "Schedule this…" on every job's page); see `schedule` below.
2283
+ `cwcli restore` (the UI restores sets from the backup store instead), `apps uninstall`, `run`, `cwcli open`'s Docker shell (the UI opens VS Code, VS Code Insiders or Cursor only), an in-browser terminal, access from another machine and the shared multi-user mode are not in the UI; each needs its own decision first.
2284
+
2285
+ **Integrations (Settings > Integrations):** connections to systems outside cwcli, each off until an Admin turns it on.
2286
+ Turning one on opens its setup, tests the connection, and turns it on only when the test passes; a change to an integration that is already on is applied only when the changed settings pass the test, so a typo never takes a working one down.
2287
+ Each integration shows one of four states: Off, Needs setup (the last attempt to turn it on failed its test), On, or Error (it is on, and its last use failed).
2288
+ Secrets are stored encrypted (AES-256-GCM) in `~/.cwcli/integrations.json` under a key in `~/.cwcli/keys/integrations.key`, both 0600; after a save the page shows only that a secret is set, and a new value replaces it.
2289
+ A change to where a secret is sent (the LDAP server URL, StartTLS, CA certificate or service account, or the S3 endpoint) is refused with `integration.secret_retype` unless the secret is typed again, so a stored secret never goes to a server it was not saved for.
2290
+ Two official integrations ship with cwcli, and an Admin can add community integrations from a repository (below).
2291
+ Each integration provides hook points that cwcli calls: **sign-in** (who may sign in to the web UI, and with which role) and **the backup target** (where encrypted off-site copies go).
2292
+ Only one integration serves a hook point at a time.
2293
+ Turning on a second one that provides it asks the Admin to choose: **Test and switch** runs the new one's test and, only if it passes, turns it on and turns the other off, keeping its settings; nothing switches silently.
2294
+
2295
+ - **LDAP / Active Directory sign-in:** the server URL (`ldaps://` preferred, or `ldap://` with StartTLS; cwcli refuses to send passwords in clear text), the CA certificate that signed the directory's certificate (empty uses this machine's trusted CAs), Active Directory or OpenLDAP, a read-only service account and its password, the base DN, the user search filter (`{username}` is the typed name, escaped), the username, email and display-name attributes, and the allowed groups, each mapped to a role.
2296
+ At every sign-in cwcli finds the user with the service account, reads their groups (Active Directory: nested groups too; OpenLDAP: `groupOfNames`, `groupOfUniqueNames` and `posixGroup`), checks the password by binding as the user, and refuses an account the directory reports as disabled and anyone outside the allowed groups.
2297
+ Someone in several allowed groups gets the highest role, and a directory user can never take the local account's name.
2298
+ Directory users have no password change in cwcli and are never asked to pick one; the usual rate limiting and lockout apply, and their jobs record their directory username.
2299
+ The setup has a **Test connection** button and a **Look up a user** check that shows what sign-in would decide for a name (found, groups, disabled, role) without asking for a password.
2300
+ Saving the settings or turning sign-in off signs every directory user out.
2301
+ - **S3 backup target:** one bucket (AWS, MinIO, Garage, Ceph) for every instance's encrypted off-site backup copies; the test lists the bucket and creates it when it does not exist.
2302
+ Each instance still chooses whether its new backups are copied there (Backups > Retention and schedule, or `cwcli backups settings --copy-on-backup/--no-copy-on-backup`).
2303
+ Upgrading from a version that set an S3 bucket per instance: cwcli turns the first such bucket (by instance name) into this integration once, and only the instances that were copying to that same bucket keep copying.
2304
+ Copies of an instance that used a different bucket are paused, never silently: the S3 card here and the Backups page name the instance and its old endpoint and bucket until its copies are turned back on.
2305
+ Every per-instance plaintext secret file (`~/.cwcli/backups/keys/s3-<instance>.secret`) is removed; type the secret key again in this integration if you set up that bucket here.
2306
+ `cwcli backups s3 configure` sets up the same integration from a terminal.
2307
+ - **Community integrations:** **Add from a repository…** takes a GitHub, GitLab.com or self-hosted GitLab repository URL and an optional branch or tag.
2308
+ cwcli fetches the repository and reads its declaration without building or running anything, then shows the trust screen: the integration's name, publisher, description, the hook points it declares, the repository, and the exact commit the branch or tag points at.
2309
+ Nothing is installed until the Admin clicks **Install**, and then only that commit; if the branch moved in the meantime, cwcli refuses and asks for a new review.
2310
+ A public repository needs no sign-in.
2311
+ A private one is read with the sign-in this machine already has, the same way the credential bridge works: `gh` for GitHub, `glab` for GitLab.com and self-hosted GitLab.
2312
+ When that sign-in is missing, the error names the command to run, for example `glab auth login --hostname gitlab.example.org`.
2313
+ The card is marked **Community, not reviewed** and shows the repository and commit; it starts off and turns on through its own connection test like any other.
2314
+ **Check for update** shows the installed and the latest commit with the trust screen, and **Update** installs only that reviewed commit and turns the integration off, keeping its settings and secrets, so the new code's test runs before it is on again; nothing updates by itself.
2315
+ **Remove…** deletes the package, its settings and its secrets after the id is typed back.
2316
+ See `integrations` below for the terminal commands, the declaration a repository needs, and where the code is installed.
2317
+
2318
+ **Honest states:** the UI distinguishes `unknown` (not probed) from `offline`,
2319
+ labels cache-backed data as remembered versus verified, and never guesses a
2320
+ port - a site whose host port cannot be resolved reads "not serving".
2321
+
2322
+ **Under WSL:** a Windows browser reaches `http://localhost:8765` through WSL's
2323
+ localhost forwarding; the address `cwcli ui` prints already works from there.
2324
+
2325
+ **Example:**
2326
+
2327
+ ```bash
2328
+ cwcli ui # start (or reuse) and open the browser
2329
+ cwcli ui --no-browser # print the address instead
2330
+ cwcli ui --stop # stop the detached daemon
2331
+ cwcli ui --foreground -p 9000 # run in this terminal on port 9000
2332
+ ```
2333
+
2334
+ There is deliberately no `cwcli axi ui`: an agent does not stand up a
2335
+ browser-facing daemon.
2336
+
2337
+ ---
2338
+
2339
+ ### `admin` - The Web UI Administrator Account
2340
+
2341
+ ```bash
2342
+ cwcli admin reset-password [--username admin]
2343
+ ```
2344
+
2345
+ Recovery from the host when the web UI password is lost: the account goes back to `admin` / `admin`, the next sign-in has to choose a new password, and every signed-in browser is signed out.
2346
+ This local account always stays an Admin and always signs in with its own password, including while LDAP / Active Directory sign-in is on.
2347
+ The account store is `~/.cwcli/accounts.json` (0600; passwords are scrypt hashes, sessions are stored as hashes of their ids), or the shared state tree on a shared install, where the reset needs root like the rest of `cwcli setup`.
2348
+ There is deliberately no `cwcli axi` spelling: an agent must not reset a person's web UI credential.
2349
+
2350
+ ---
2351
+
2352
+ ### `integrations` - Community Integrations
2353
+
2354
+ ```bash
2355
+ cwcli integrations list [--json]
2356
+ cwcli integrations add URL [--ref BRANCH_OR_TAG] [--yes]
2357
+ cwcli integrations update ID [--check] [--yes]
2358
+ cwcli integrations remove ID [--yes]
2359
+ ```
2360
+
2361
+ `list` shows every integration, official and community, with its state; a community one also shows its repository, commit, publisher and who installed it.
2362
+ `add` fetches the repository (an `https://` GitHub, GitLab.com or self-hosted GitLab URL), prints what it declares and the exact commit, and installs that commit after you confirm; `--yes` confirms in a script, and without it a non-interactive run refuses.
2363
+ A public repository is read with no sign-in: cwcli switches off every git credential helper and prompt for that first attempt.
2364
+ Only when the host answers that a sign-in is required does git ask this machine's `gh` (GitHub) or `glab` (GitLab) sign-in, and a missing one fails with the command that fixes it.
2365
+ A URL that carries a user name or token is refused; cwcli stores no token.
2366
+ `update` shows the installed and the latest commit on the integration's branch or tag; `--check` stops there, otherwise it installs the latest commit after you confirm and turns the integration off until it is turned on again.
2367
+ `remove` deletes the package, its settings and its secrets.
2368
+ Every `add`, `update` and `remove` appends a line to `~/.cwcli/integrations-audit.log` with who did it, the URL and the commit.
2369
+ On a shared install only an Admin changes community integrations: root here, or an Admin in the web UI.
2370
+ There is deliberately no `cwcli axi` spelling: installing third-party code is a person's decision.
2371
+
2372
+ A community integration is a Python package whose repository declares it in `pyproject.toml`, which cwcli reads before running anything; a repository without a valid declaration is refused before anything is installed:
2373
+
2374
+ ```toml
2375
+ [project]
2376
+ name = "cwcli-integration-localdir"
2377
+ version = "0.1.0"
2378
+ dependencies = [] # only your own; cwcli provides itself
2379
+
2380
+ [project.entry-points."cwcli.integrations"]
2381
+ localdir = "cwcli_localdir:INTEGRATION" # one per repository; the name is the integration's id
2382
+
2383
+ [tool.cwcli.integration]
2384
+ name = "Local directory backup target"
2385
+ publisher = "Example Org"
2386
+ description = "Keeps off-site copies in a directory on this machine."
2387
+ provides = ["backup_target"] # sign_in and/or backup_target
2388
+ ```
2389
+
2390
+ The entry point names a `caffeinated_whale_cli.core.integrations.Integration` (its settings fields, connection test, optional actions) whose `hooks` maps each hook point it provides to the function cwcli calls; cwcli refuses one whose id or hook points differ from the declaration or that lacks a hook's function, and shows the declared name and description.
2391
+ Once an Admin turns it on (its test runs first), cwcli calls it exactly where it calls the official integrations:
2392
+
2393
+ - `sign_in(settings, secrets, username, password)` returns the user (a dict with `username`, `role` - `admin`, `operator` or `viewer` - and optionally `name` and `email`) or `None` for a wrong name or password.
2394
+ It is asked for every name except the local `admin` account's, which always signs in locally; turning it off or changing it signs its users out.
2395
+ An optional `current_role(settings, secrets, username)` lets a schedule confirm its owner is still an Admin; without it, an Admin-only schedule saved by one of its users is skipped with that reason.
2396
+ - `backup_target(settings, secrets)` returns the store off-site copies go to: an object with `location` (shown to people), `key(*parts)`, `ensure()`, `put_file(key, path)`, `put_bytes(key, data)`, `get_to_file(key, path)`, `get_bytes(key)`, `delete(key)` and `list(prefix)` (a list of `{"key": ...}`).
2397
+ cwcli encrypts every part before it reaches the target, exactly as for the S3 bucket, and `cwcli backups s3 list/copy/fetch/prune` work with whichever target is on.
2398
+
2399
+ A failure inside a hook reads as that integration's Error state, never as a crash: a sign-in that fails is "the directory could not check your sign-in", and a failing target is an off-site copy that failed.
2400
+ cwcli installs each package with pip into its own directory under `~/.cwcli/integrations/community/`, never into cwcli's environment or the system, using a private venv there for pip; cwcli's own packages take precedence over any copy a package brings along.
2401
+ The standalone Windows build cannot install packages.
2402
+
2403
+ ---
2404
+
2405
+ ### `schedule` - Scheduled Jobs
2406
+
2407
+ ```bash
2408
+ cwcli schedule add NAME --verb VERB --project NAME [--bench SEL] [--site NAME] [--process LABEL] (--daily HH:MM | --weekly "DAY HH:MM" | --cron "m h dom mon dow" | --at ISO) [--arg k=v]... [--allow-unattended] [--missed skip] [--start-if-stopped] [--in-window]
2409
+ cwcli schedule list [--json]
2410
+ cwcli schedule run ID
2411
+ cwcli schedule enable ID | disable ID | rm ID
2412
+ cwcli schedule daemon [--install-service]
2413
+ ```
2414
+
2415
+ A schedule starts an ordinary job on a timetable (every run is a job with `origin.schedule = <id>`, so Jobs, logs and Retry work unchanged).
2416
+ Schedulable: backup (every site or one), prune, off-site copy and prune, restore drill, app or core update, migrate, restart, one process, start, stop, stop a bench, developer mode, clear maintenance, build.
2417
+ Removals can never be scheduled.
2418
+ Backups, restarts, starts and stops run unattended freely; an update or migrate needs `--allow-unattended` (the "Allowed unattended" tick in the UI), and a scheduled core update still takes its automatic backup first.
2419
+ A missed run (the machine was off) runs once on wake unless the schedule says `--missed skip`.
2420
+ Guards at fire time: the instance's maintenance window (`--in-window`), the quiet hours, a stopped instance (started first with `--start-if-stopped`, else skipped), free disk and memory thresholds, and one job per instance at a time (retried the next minute).
2421
+ Windows, quiet hours and thresholds are set in the web UI (Schedules › Windows and guards); the store is `~/.cwcli/schedules.json`.
2422
+ The scheduler ticks inside `cwcli ui`; on a shared install `sudo cwcli schedule daemon --install-service` installs the machine-wide `cwcli-scheduler.service` so schedules fire with no UI open (both lock the store, so a slot never fires twice).
2423
+ `cwcli axi schedule list|add|run-now|remove` is the agent spelling.
2424
+
2425
+ ---
2426
+
1963
2427
  ### `doctor` - Preflight Your Environment
1964
2428
 
1965
2429
  Checks whether cwcli can operate on this machine at all - Docker, cwcli's own
@@ -1980,13 +2444,6 @@ cwcli doctor [OPTIONS]
1980
2444
 
1981
2445
  **Read-only checks:** doctor never starts a container, installs anything, or
1982
2446
  writes a config file.
1983
- There is one accepted shared-infrastructure exception before the checks run:
1984
- building the command tree for `cwcli doctor` or `cwcli axi doctor` imports the
1985
- pre-existing cache layer, which may idempotently create cwcli's private cache
1986
- directory and apply mode `0700`.
1987
- It does not touch project data.
1988
- Making that initialization lazy is tracked separately as
1989
- `cwcli-cache-dir-lazy-init`; this caveat should disappear when that lands.
1990
2447
  When a direct remedy is available, the finding includes the command or action
1991
2448
  to apply yourself.
1992
2449
 
@@ -1995,6 +2452,12 @@ Docker, cwcli, Storage, Transfer, and Git hosting, plus a cross-instance
1995
2452
  port-range collision check under Instances. Each check is `pass`, `warn`, or
1996
2453
  `fail`; an absent *optional* tool (sendme, `gh`, `glab`) is `warn`, never
1997
2454
  `fail`.
2455
+ `gh`/`glab` sign-in is checked per host the credential bridge sends to that
2456
+ tool (the bridge's allowed hosts, plus `github.com` for `gh`), and warns only
2457
+ when the tool is signed in to none of them; a signed-out host beside a
2458
+ signed-in one is listed with its `auth login --hostname` command.
2459
+ An enabled but stopped auto-inspect or credential-bridge daemon is a `warn`
2460
+ naming its start command; the web UI's Doctor view offers a Start button for it.
1998
2461
 
1999
2462
  **Exit codes:** `0` when every check is pass/warn (a warning never blocks -
2000
2463
  doctor is a chainable preflight gate), `1` when any check fails.
@@ -2483,7 +2946,7 @@ cwcli axi apps list frappe-one --site erp.localhost
2483
2946
  # prints as ONE TOON document. Blocks until it finishes (like axi backup) -
2484
2947
  # there is no progress stream, because an agent needs a verdict to branch on.
2485
2948
  cwcli axi apps update frappe-one erpnext
2486
- cwcli axi apps update frappe-one frappe # runs 'bench update --reset'
2949
+ cwcli axi apps update frappe-one frappe # the core update: resets frappe alone
2487
2950
 
2488
2951
  # Put ONE branch, tag, or commit under test in an app already in the bench -
2489
2952
  # the gap 'apps install' (bench acquisition and site installation) and 'apps update' (the tracked
@@ -2540,6 +3003,11 @@ There is no human `cwcli url` yet.
2540
3003
  Use `cwcli open`'s banner for the host address and `cwcli status` for the HTTP observation.
2541
3004
 
2542
3005
  `cwcli axi apps update` blocks until the update finishes and emits ONE terminal document, exactly as `cwcli axi backup` does for a minutes-long `bench backup`: progress is deliberately not streamed, because N documents on stdout would break the one-TOON-document contract and an agent needs a verdict it can branch on rather than a progress bar (use `cwcli logs`/`cwcli status --watch` if you want live progress). Its exit code is `0` only when the report's `ok` is true; any failed phase, any stuck site, or any unknown outcome exits `1`, and an ambiguous multi-bench project is a `--bench` usage error (exit 2). A stopped project is likewise a usage error pointing at `cwcli start` (exit 2) - there is deliberately no `--yes` on this verb, because starting a container is UI-coupled and the core stays UI-pure about it, so an agent composes `cwcli axi start` then this verb, exactly as `cwcli axi unlock`/`cwcli axi backup` already document. **`failed_*` and `unknown_*` are not the same thing and must not be collapsed:** a `failed_*` item ran and failed, so retrying it is safe, while an `unknown_*` item's output stream was lost - its exit code is unknowable and **it may still be running**, so retrying it (a migration above all) can do real harm. Check before retrying. There is deliberately no `cwcli axi update`: the deprecated `cwcli update` spelling does not get an agent-facing verb. JSON output stays on the human commands (`cwcli ls --json`, `cwcli where --json`, `cwcli apps update --json`). `--force` is available here too and is **destructive**: an app whose git update hits a conflict is hard-reset to its remote tracking branch (discarding local changes) so the update completes, reported in the `force_reset_apps` field; it fires only on a conflict and never on a non-conflict failure.
3006
+ Updating `frappe` runs the same preflight as the human command before the core reset.
3007
+ Its findings are the `preflight` table (`severity,code,app,site,message,remedy`); branch on `code`, never on the message.
3008
+ A blocker sets `preflight_blocked: true` and exits 1 with nothing run, and `--no-preflight` is available here too, because it skips predictions rather than a safety gate.
3009
+ The data-loss guard runs here too, under `--no-preflight` as well: uncommitted or unpushed work in any app (or work that could not be checked) sets `data_loss_refused: true`, names the apps in `data_loss_apps`, and exits 1 with nothing run.
3010
+ `--force` accepts the loss; the reset then runs and `data_loss_apps` names the apps it was allowed to discard work in.
2543
3011
 
2544
3012
  `cwcli axi apps list` is the read that answers what is *on* the bench `cwcli axi benches` names: the bench's available apps, and with `--installed`/`--site` which apps are installed on which site (only the app name, never the version column `bench list-apps` prints). A site whose read FAILED is reported as `null` and exits `1`, never as an empty list - "has no apps" and "could not tell" are different facts, and only one of them is safe to act on. Like every other bench-scoped verb it takes `--bench`, has no `--yes`, and reports a stopped project as a usage error naming `cwcli start` (exit 2). **`cwcli axi apps uninstall` deliberately does not exist:** letting an agent drop the tables of a real site is a product decision that deserves its own evidence, not something settled as a side effect of moving code onto the logic core. Use the human `cwcli apps uninstall` (it has `--json` and honest exit codes) until that decision is taken. `cwcli axi apps install` was held under that same shared rationale and now exists, scoped to the half of it that rationale never covered - see below.
2545
3013
 
@@ -2571,7 +3039,7 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
2571
3039
 
2572
3040
  `cwcli axi migrate <project>` runs `bench migrate` against **exactly one site**, under maintenance mode. It is the migrate on its own: `cwcli axi apps update` migrates too, but only as the tail of a `git pull` across every named app, so an agent that has just pinned a feature branch with `axi apps checkout` cannot use it without a pull that moves the ref it pinned. Understand what it does before pointing an agent at it: it applies every pending schema patch from every installed app to that site's **live database**, altering tables and running patch code the apps ship. It is not transactional across patches, so one that fails partway leaves the database partially migrated, and there is no rollback - recovery is from a backup. It deliberately does not take one for you (`apps update` does not either, and a backup you did not ask for is not a guard); run `cwcli axi backup <project> --site <site>` first when the data matters. Its guards each protect against something named: it resolves **one** site (explicit `--site`, else the bench default) and never fans out, so it cannot migrate a site you did not name, and the resolved site is in the output so you can always read back what it touched; a genuinely held migrate lock refuses the operation before maintenance mode is touched and names the matching `cwcli unlock` command; maintenance mode is then enabled, and a **failed enable refuses the migrate** rather than running it against a live-serving site; the disable runs even if the migrate blows up, and a site left in maintenance is reported as `maintenance_left_on` and fails the command, because that site is down. There is no `--skip-maintenance` and no `--yes` - a stopped project is a usage error pointing at `cwcli start`. bench's own output goes to stderr in full: the patch that failed names itself there and nowhere else.
2573
3041
 
2574
- `cwcli axi run-tests <project> --site <site> --app <app>` runs that app's test suite against that site. **Both flags are required and neither has a default**, which is a deliberate break from `axi backup`, `axi unlock`, and `axi migrate` - they all fall back to the bench's default site. The reason is what this command actually is: it imports and executes the app's own test modules inside the container, against the named site's live database, and cwcli cannot bound what that code does because it *is* your repository's code. A Frappe test suite creates, modifies, and deletes records. Honestly stated, it is arbitrary Python from the repo under test, run against a live site - so point it at a dedicated test site. cwcli cannot tell a test site from one holding real data, so that is a practice it can state and not enforce. Making you name the site is the cheapest way to keep "I am willing for this site to be written to" an explicit act; requiring `--app` stops a bare run from executing every installed app's suite, Frappe's own included. This is **not** the general command passthrough cwcli deliberately does not offer: the difference is who writes the command. There is no `cwcli axi run`, and there will not be one - here you select an app whose tests already exist in the bench, and no flag on this command can express a second command. The runner's output goes to stderr in full and unparsed; cwcli reports only the honest pass/fail, because it does not own your test runner's output format.
3042
+ `cwcli axi run-tests <project> --site <site> --app <app>` runs that app's test suite against that site. **Both flags are required and neither has a default**, which is a deliberate break from `axi backup`, `axi unlock`, and `axi migrate` - they all fall back to the bench's default site. The reason is what this command actually is: it imports and executes the app's own test modules inside the container, against the named site's live database, and cwcli cannot bound what that code does because it *is* your repository's code. A Frappe test suite creates, modifies, and deletes records. Honestly stated, it is arbitrary Python from the repo under test, run against a live site - so point it at a dedicated test site. cwcli cannot tell a test site from one holding real data, so that is a practice it can state and not enforce. Making you name the site is the cheapest way to keep "I am willing for this site to be written to" an explicit act; requiring `--app` stops a bare run from executing every installed app's suite, Frappe's own included. This is **not** the general command passthrough cwcli deliberately does not offer: the difference is who writes the command. There is no `cwcli axi run`, and there will not be one - here you select an app whose tests already exist in the bench, and no flag on this command can express a second command. The runner's output goes to stderr in full and unparsed; cwcli reports only the honest pass/fail, because it does not own your test runner's output format. That pass/fail does not trust the exit code alone: Frappe v14/v15 exit 0 on a failed suite unless `CI` is set, so a run whose output ends in unittest's `FAILED (...)` summary, or that Frappe refused because `allow_tests` is off on the site, reports `ok: false`, exits non-zero, and names the reason.
2575
3043
 
2576
3044
  `--module <dotted.path>` narrows a run to one test module. It only ever reduces what runs, so unlike `--site` and `--app` it needs no guard of its own; it exists because iterating on a single failing module is the common case, and without it that dropped back to a raw `cwcli run`.
2577
3045
 
@@ -2579,7 +3047,11 @@ It exits non-zero if a restart fails or the named site does not answer Frappe's
2579
3047
 
2580
3048
  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.
2581
3049
 
2582
- `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.
3050
+ `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`.
3051
+
3052
+ `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. Re-running init against a stopped instance starts its existing containers as they are, without pulling images or recreating them, so its benches keep their Python environments. If the re-run would change that instance's compose settings, such as a different `--port` on an instance still on the default port range (8000-8005), init refuses with exit 1 (`instance.settings_changed`), leaves the compose file unchanged, and names each change; remove the instance with `cwcli rm` (which backs it up) and run init again, or rerun init without the changed options. For an instance created on another `--port` or widened by `cwcli scale`, init does not compare `--port`. An instance whose frappe container has never started, such as one left by a first init that failed at `docker compose up`, has no bench to protect, so init neither refuses nor uses `--no-recreate` there: it pulls images and recreates the containers with the new options. 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: the instance's remembered password, else `123`); on a new instance it becomes MariaDB's own root password, as on `cwcli init`. 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.
3053
+
3054
+ `--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.
2583
3055
 
2584
3056
  `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.
2585
3057
  It is the agent form of `cwcli rm`, and it runs the same code.
@@ -2602,9 +3074,11 @@ There is deliberately **no `--no-backup`** here. That flag turns off the one gua
2602
3074
  `cwcli axi rm-site <project> <site> --yes` permanently drops ONE site - its database and its files - leaving the instance and every other site on it running.
2603
3075
  It is the per-site inverse of `cwcli axi init`: a workflow that creates a new site per task on a shared, already-running bench needs a way to drop it again at teardown, and unlike `cwcli axi rm` it carries none of the whole-instance blast radius, so it needs none of that verb's deferral history.
2604
3076
  `--yes` is required and means consent only, same as `cwcli axi rm`; there is no auto-start, so a stopped project is a usage error naming `cwcli start` (exit 2), exactly like every other bench-scoped verb.
2605
- `bench drop-site` archives the dropped site's full directory - including `site_config.json` and its database credentials - inside the container's own `archived/sites/` folder before this verb ever sees it; left there it would grow, unpruned, forever.
2606
- So this immediately copies that one archive out to the same managed host location `cwcli rm` uses (`archived_host_path` in the TOON report) and, only once that copy is verified on disk, deletes the in-container copy (`archive_pruned_in_container: true`).
2607
- If the copy could not be verified, the in-container archive is left in place rather than deleted unbacked, and `ok: false` plus a warning names exactly what remains - a caller that only checks the exit code still learns its site's credentials may not be safely out of the container.
3077
+ The site is backed up before it is dropped, with the same verified gate `cwcli axi rm` uses: `bench backup --with-files`, this run's database dump and file archives copied out to the managed host archive, and the dump checked non-empty there.
3078
+ If that fails, nothing is dropped, and the error (exit 1) carries bench's own output.
3079
+ `bench drop-site --no-backup` then archives the dropped site's full directory - including `site_config.json` and its database credentials - inside the container's own `archived/sites/` folder; left there it would grow, unpruned, forever.
3080
+ So this copies that one archive out beside the verified backup (`archived_host_path` in the TOON report holds both) and, only once that copy is verified on disk, deletes the in-container copy (`archive_pruned_in_container: true`).
3081
+ If anything could not be verified, the in-container archive is left in place rather than deleted unbacked, and `ok: false` plus a warning names exactly what remains - a caller that only checks the exit code still learns it.
2608
3082
 
2609
3083
  #### Making agents aware of the surface
2610
3084
 
@@ -2710,6 +3184,7 @@ The CLI uses:
2710
3184
  - **Cache**: `~/.cwcli/cache/cwc-cache.db` - Project inspection cache
2711
3185
  - **Runtime**: `~/.cwcli/run/` - PID and log files for background services
2712
3186
  - **Archive**: `~/.cwcli/archive/` - Pre-deletion backups and config snapshots from `cwcli rm`, plus dropped-site archives from `cwcli rm-site` and `cwcli rm-bench`
3187
+ - **Backups**: `~/.cwcli/backups/<project>/<site>/<id>/` - The backup store managed by [`cwcli backups`](#backups---the-backup-store), outside every instance
2713
3188
  - **Transfers**: `~/.cwcli/tmp/` by default, or `$TMPDIR/cwcli/`, `$TEMP/cwcli/`, or `$TMP/cwcli/` when the first non-empty variable in that order is set. Temporary files used by `restore --send` and `restore --receive`; each attempt is removed when it finishes or fails
2714
3189
 
2715
3190
  **Relocating cwcli's data (`CWCLI_HOME`):**