caffeinated-whale-cli 2.0.0__tar.gz → 2.2.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 (193) hide show
  1. {caffeinated_whale_cli-2.0.0/src/caffeinated_whale_cli.egg-info → caffeinated_whale_cli-2.2.0}/PKG-INFO +141 -19
  2. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/README.md +140 -18
  3. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/pyproject.toml +4 -1
  4. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/__init__.py +1 -0
  5. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/apps.py +10 -0
  6. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/axi.py +464 -37
  7. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/commands/console.html +2545 -0
  8. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/commands/doctor.py +79 -0
  9. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/init.py +14 -0
  10. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/inspect.py +10 -8
  11. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/label.py +27 -13
  12. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/open.py +7 -5
  13. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/restore.py +285 -12
  14. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/commands/serve.py +1292 -0
  15. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/status.py +14 -1
  16. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/update.py +17 -0
  17. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/utils.py +120 -8
  18. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/apps.py +201 -9
  19. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/bench_ops.py +81 -1
  20. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/docker.py +111 -12
  21. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/core/doctor.py +604 -0
  22. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/core/fleet.py +475 -0
  23. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/init.py +104 -18
  24. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/inspect.py +46 -11
  25. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/label.py +107 -29
  26. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/open.py +15 -14
  27. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/resolvers.py +92 -4
  28. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/restore.py +80 -34
  29. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/scale.py +1 -0
  30. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/start.py +3 -3
  31. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/status.py +158 -21
  32. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/supervision.py +563 -39
  33. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/update.py +102 -2
  34. caffeinated_whale_cli-2.2.0/src/caffeinated_whale_cli/core/url.py +154 -0
  35. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/version.py +20 -5
  36. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/main.py +22 -6
  37. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/bench_labels.py +12 -8
  38. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/config_utils.py +11 -0
  39. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/db_utils.py +122 -16
  40. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/docker_utils.py +48 -19
  41. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/toon.py +13 -6
  42. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli.egg-info/SOURCES.txt +7 -105
  43. caffeinated_whale_cli-2.0.0/PKG-INFO +0 -2378
  44. caffeinated_whale_cli-2.0.0/src/caffeinated_whale_cli/__init__.py +0 -1
  45. caffeinated_whale_cli-2.0.0/src/caffeinated_whale_cli.egg-info/dependency_links.txt +0 -1
  46. caffeinated_whale_cli-2.0.0/src/caffeinated_whale_cli.egg-info/entry_points.txt +0 -3
  47. caffeinated_whale_cli-2.0.0/src/caffeinated_whale_cli.egg-info/requires.txt +0 -21
  48. caffeinated_whale_cli-2.0.0/src/caffeinated_whale_cli.egg-info/top_level.txt +0 -1
  49. caffeinated_whale_cli-2.0.0/tests/test_agent_hooks.py +0 -397
  50. caffeinated_whale_cli-2.0.0/tests/test_apps.py +0 -1312
  51. caffeinated_whale_cli-2.0.0/tests/test_apps_characterization.py +0 -387
  52. caffeinated_whale_cli-2.0.0/tests/test_auto_inspect.py +0 -451
  53. caffeinated_whale_cli-2.0.0/tests/test_axi.py +0 -628
  54. caffeinated_whale_cli-2.0.0/tests/test_axi_apps_checkout.py +0 -313
  55. caffeinated_whale_cli-2.0.0/tests/test_axi_apps_list.py +0 -294
  56. caffeinated_whale_cli-2.0.0/tests/test_axi_apps_update.py +0 -279
  57. caffeinated_whale_cli-2.0.0/tests/test_axi_bench_ops.py +0 -346
  58. caffeinated_whale_cli-2.0.0/tests/test_axi_config.py +0 -93
  59. caffeinated_whale_cli-2.0.0/tests/test_axi_init.py +0 -527
  60. caffeinated_whale_cli-2.0.0/tests/test_axi_inspect.py +0 -180
  61. caffeinated_whale_cli-2.0.0/tests/test_axi_label.py +0 -252
  62. caffeinated_whale_cli-2.0.0/tests/test_axi_logs.py +0 -274
  63. caffeinated_whale_cli-2.0.0/tests/test_axi_restart.py +0 -97
  64. caffeinated_whale_cli-2.0.0/tests/test_axi_rm.py +0 -246
  65. caffeinated_whale_cli-2.0.0/tests/test_axi_rm_site.py +0 -21
  66. caffeinated_whale_cli-2.0.0/tests/test_axi_skill.py +0 -172
  67. caffeinated_whale_cli-2.0.0/tests/test_axi_start_status.py +0 -334
  68. caffeinated_whale_cli-2.0.0/tests/test_axi_unlock_stop.py +0 -270
  69. caffeinated_whale_cli-2.0.0/tests/test_backup_command_cap.py +0 -82
  70. caffeinated_whale_cli-2.0.0/tests/test_bench_label_db_and_command.py +0 -266
  71. caffeinated_whale_cli-2.0.0/tests/test_bench_labels.py +0 -147
  72. caffeinated_whale_cli-2.0.0/tests/test_bench_selector.py +0 -89
  73. caffeinated_whale_cli-2.0.0/tests/test_completion_utils.py +0 -417
  74. caffeinated_whale_cli-2.0.0/tests/test_config_characterization.py +0 -450
  75. caffeinated_whale_cli-2.0.0/tests/test_config_frontend.py +0 -275
  76. caffeinated_whale_cli-2.0.0/tests/test_config_validation.py +0 -140
  77. caffeinated_whale_cli-2.0.0/tests/test_core_apps.py +0 -534
  78. caffeinated_whale_cli-2.0.0/tests/test_core_auto_inspect.py +0 -276
  79. caffeinated_whale_cli-2.0.0/tests/test_core_backup.py +0 -229
  80. caffeinated_whale_cli-2.0.0/tests/test_core_bench_ops.py +0 -262
  81. caffeinated_whale_cli-2.0.0/tests/test_core_config.py +0 -195
  82. caffeinated_whale_cli-2.0.0/tests/test_core_credbridge.py +0 -267
  83. caffeinated_whale_cli-2.0.0/tests/test_core_docker.py +0 -83
  84. caffeinated_whale_cli-2.0.0/tests/test_core_envelope.py +0 -305
  85. caffeinated_whale_cli-2.0.0/tests/test_core_exec_stream.py +0 -354
  86. caffeinated_whale_cli-2.0.0/tests/test_core_init.py +0 -879
  87. caffeinated_whale_cli-2.0.0/tests/test_core_inspect.py +0 -510
  88. caffeinated_whale_cli-2.0.0/tests/test_core_label.py +0 -501
  89. caffeinated_whale_cli-2.0.0/tests/test_core_list.py +0 -119
  90. caffeinated_whale_cli-2.0.0/tests/test_core_logs.py +0 -475
  91. caffeinated_whale_cli-2.0.0/tests/test_core_open.py +0 -390
  92. caffeinated_whale_cli-2.0.0/tests/test_core_resolvers.py +0 -365
  93. caffeinated_whale_cli-2.0.0/tests/test_core_restart.py +0 -143
  94. caffeinated_whale_cli-2.0.0/tests/test_core_restore.py +0 -477
  95. caffeinated_whale_cli-2.0.0/tests/test_core_rm.py +0 -427
  96. caffeinated_whale_cli-2.0.0/tests/test_core_rm_site.py +0 -387
  97. caffeinated_whale_cli-2.0.0/tests/test_core_run.py +0 -682
  98. caffeinated_whale_cli-2.0.0/tests/test_core_scale.py +0 -464
  99. caffeinated_whale_cli-2.0.0/tests/test_core_start.py +0 -262
  100. caffeinated_whale_cli-2.0.0/tests/test_core_status.py +0 -562
  101. caffeinated_whale_cli-2.0.0/tests/test_core_stop.py +0 -263
  102. caffeinated_whale_cli-2.0.0/tests/test_core_supervision.py +0 -681
  103. caffeinated_whale_cli-2.0.0/tests/test_core_unlock.py +0 -226
  104. caffeinated_whale_cli-2.0.0/tests/test_core_update.py +0 -625
  105. caffeinated_whale_cli-2.0.0/tests/test_core_version.py +0 -438
  106. caffeinated_whale_cli-2.0.0/tests/test_core_where.py +0 -270
  107. caffeinated_whale_cli-2.0.0/tests/test_cwcli_home.py +0 -142
  108. caffeinated_whale_cli-2.0.0/tests/test_db_security.py +0 -403
  109. caffeinated_whale_cli-2.0.0/tests/test_exec_stream_decode.py +0 -231
  110. caffeinated_whale_cli-2.0.0/tests/test_exit_codes.py +0 -321
  111. caffeinated_whale_cli-2.0.0/tests/test_exit_codes_on_failure.py +0 -234
  112. caffeinated_whale_cli-2.0.0/tests/test_init_admin_password.py +0 -246
  113. caffeinated_whale_cli-2.0.0/tests/test_init_characterization.py +0 -562
  114. caffeinated_whale_cli-2.0.0/tests/test_init_frappe_version.py +0 -200
  115. caffeinated_whale_cli-2.0.0/tests/test_init_mariadb_flag.py +0 -40
  116. caffeinated_whale_cli-2.0.0/tests/test_init_reuse_bench.py +0 -308
  117. caffeinated_whale_cli-2.0.0/tests/test_inspect_apps_error.py +0 -78
  118. caffeinated_whale_cli-2.0.0/tests/test_inspect_characterization.py +0 -526
  119. caffeinated_whale_cli-2.0.0/tests/test_inspect_label_recovery.py +0 -170
  120. caffeinated_whale_cli-2.0.0/tests/test_inspect_partial_refresh.py +0 -616
  121. caffeinated_whale_cli-2.0.0/tests/test_list.py +0 -93
  122. caffeinated_whale_cli-2.0.0/tests/test_logs.py +0 -362
  123. caffeinated_whale_cli-2.0.0/tests/test_open_characterization.py +0 -235
  124. caffeinated_whale_cli-2.0.0/tests/test_open_inspect_fallback.py +0 -91
  125. caffeinated_whale_cli-2.0.0/tests/test_restart_bench_selector.py +0 -151
  126. caffeinated_whale_cli-2.0.0/tests/test_restore_characterization.py +0 -363
  127. caffeinated_whale_cli-2.0.0/tests/test_restore_inspect_fixes.py +0 -604
  128. caffeinated_whale_cli-2.0.0/tests/test_restore_safety.py +0 -906
  129. caffeinated_whale_cli-2.0.0/tests/test_rm_characterization.py +0 -498
  130. caffeinated_whale_cli-2.0.0/tests/test_rm_safety.py +0 -1282
  131. caffeinated_whale_cli-2.0.0/tests/test_rm_stopped.py +0 -368
  132. caffeinated_whale_cli-2.0.0/tests/test_rm_stopped_backup.py +0 -430
  133. caffeinated_whale_cli-2.0.0/tests/test_rm_truth.py +0 -407
  134. caffeinated_whale_cli-2.0.0/tests/test_self_update.py +0 -172
  135. caffeinated_whale_cli-2.0.0/tests/test_sendme_utils.py +0 -367
  136. caffeinated_whale_cli-2.0.0/tests/test_status_frontend.py +0 -201
  137. caffeinated_whale_cli-2.0.0/tests/test_status_watch.py +0 -208
  138. caffeinated_whale_cli-2.0.0/tests/test_tips.py +0 -240
  139. caffeinated_whale_cli-2.0.0/tests/test_trailing_options.py +0 -472
  140. caffeinated_whale_cli-2.0.0/tests/test_unlock.py +0 -81
  141. caffeinated_whale_cli-2.0.0/tests/test_unlock_command_cli.py +0 -61
  142. caffeinated_whale_cli-2.0.0/tests/test_update_characterization.py +0 -420
  143. caffeinated_whale_cli-2.0.0/tests/test_update_notice.py +0 -96
  144. caffeinated_whale_cli-2.0.0/tests/test_version_output.py +0 -64
  145. caffeinated_whale_cli-2.0.0/tests/test_where.py +0 -146
  146. caffeinated_whale_cli-2.0.0/tests/test_yes_flag.py +0 -466
  147. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/LICENSE +0 -0
  148. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/setup.cfg +0 -0
  149. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/__init__.py +0 -0
  150. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/backup.py +0 -0
  151. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/config.py +0 -0
  152. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/list.py +0 -0
  153. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/logs.py +0 -0
  154. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/restart.py +0 -0
  155. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/rm.py +0 -0
  156. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/rm_site.py +0 -0
  157. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/run.py +0 -0
  158. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/scale.py +0 -0
  159. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/self_update.py +0 -0
  160. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/start.py +0 -0
  161. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/stop.py +0 -0
  162. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/unlock.py +0 -0
  163. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/commands/where.py +0 -0
  164. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/__init__.py +0 -0
  165. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/auto_inspect.py +0 -0
  166. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/backup.py +0 -0
  167. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/config.py +0 -0
  168. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/credbridge.py +0 -0
  169. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/envelope.py +0 -0
  170. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/errors.py +0 -0
  171. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/exec_stream.py +0 -0
  172. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/list.py +0 -0
  173. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/logs.py +0 -0
  174. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/restart.py +0 -0
  175. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/rm.py +0 -0
  176. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/rm_site.py +0 -0
  177. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/run.py +0 -0
  178. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/stop.py +0 -0
  179. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/unlock.py +0 -0
  180. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/core/where.py +0 -0
  181. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/update_notice.py +0 -0
  182. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/__init__.py +0 -0
  183. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/agent_hooks.py +0 -0
  184. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/auto_inspect.py +0 -0
  185. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/bench_sites.py +0 -0
  186. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/cache.py +0 -0
  187. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/completion_utils.py +0 -0
  188. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/console.py +0 -0
  189. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/port_utils.py +0 -0
  190. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/sendme_utils.py +0 -0
  191. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/startup.py +0 -0
  192. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/tips.py +0 -0
  193. {caffeinated_whale_cli-2.0.0 → caffeinated_whale_cli-2.2.0}/src/caffeinated_whale_cli/utils/vscode_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: caffeinated-whale-cli
3
- Version: 2.0.0
3
+ Version: 2.2.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
@@ -40,7 +40,9 @@ Dynamic: license-file
40
40
 
41
41
  # Caffeinated Whale CLI
42
42
 
43
- A command-line interface (CLI) for managing Frappe/ERPNext Docker instances during local development. Simplify container management, bench operations, and development workflows with an intuitive set of commands.
43
+ 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.
44
46
 
45
47
  ## Features
46
48
 
@@ -59,15 +61,21 @@ A command-line interface (CLI) for managing Frappe/ERPNext Docker instances duri
59
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
60
62
  - **Update Management** - App updates with automatic migrations and lock cleanup
61
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)
62
65
  - **Update Notices** - A passive, once/day "a newer cwcli is available" hint on stderr, shown only to a human at a TTY
63
66
  - **Auto-Inspection** - Background process to keep project cache fresh automatically
64
67
  - **System Integration** - Auto-start on system boot with platform-specific configurations
65
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
66
70
 
67
71
  ## Installation
68
72
 
69
73
  `cwcli` requires Python 3.10+.
70
74
 
75
+ `cwcli init` and `cwcli scale` also require the Docker CLI with the Docker Compose v2 plugin (`docker compose version`).
76
+ Docker Desktop includes the plugin; on a bare Docker Engine host, install the `docker-compose-plugin` package.
77
+ Both commands verify this prerequisite before changing project state and return an actionable precondition error when it is unavailable.
78
+
71
79
  ### With uv (recommended)
72
80
 
73
81
  [uv](https://docs.astral.sh/uv/) installs `cwcli` into an isolated environment and puts the `cwcli` command on your `PATH`, without touching your system or project Python.
@@ -165,6 +173,10 @@ cwcli apps update my-project erpnext
165
173
 
166
174
  ## Command Reference
167
175
 
176
+ For the variadic `start`, `stop`, `restart`, and `rm` commands, options may appear after project names.
177
+ Short flags can be clustered (`-vy` means `-v -y`), and a short option's value can be attached (`-pweb` means `--process web`).
178
+ A cluster containing an unknown short option is rejected as a whole.
179
+
168
180
  ### `init` - Initialize New Project
169
181
 
170
182
  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.
@@ -206,7 +218,7 @@ cwcli init [OPTIONS] [PROJECT_NAME]
206
218
  4. Binds the bench workspace to a host `~/.cwcli/projects/{project_name}/data/` directory at `--bench-parent` inside the container (default `/workspace`), so bench files stay directly accessible on the host and survive container recreation
207
219
  5. Resolves the latest stable `frappe/bench` image tag from Docker Hub (never uses `:latest`)
208
220
  6. Pulls Docker images and starts containers
209
- 7. Aligns the container's `frappe` user to the host user's uid/gid, so bench files written to the mounted workspace stay host-owned and removable by `cwcli rm` on any host (no-op when the ids already match; a failed remap degrades to a warning and bench creation still proceeds)
221
+ 7. On hosts that expose uid/gid information, aligns the container's `frappe` user to the host user so bench files written to the mounted workspace stay host-owned and removable by `cwcli rm` (no-op when the ids already match; platforms without `os.getuid`, such as Windows, cleanly skip alignment because Docker Desktop handles bind-mount ownership; a failed attempted remap degrades to a warning and bench creation still proceeds)
210
222
  8. Pins the correct Python version via `PYENV_VERSION` for the branch (installs via pyenv if missing)
211
223
  9. Pins the correct Node.js version via nvm for older branches (installs via nvm if missing)
212
224
  10. Installs `yarn` globally for the activated Node.js version (older branches only)
@@ -959,7 +971,8 @@ cwcli inspect frappe-one --interactive
959
971
 
960
972
  ### `label` - Manage Bench Labels
961
973
 
962
- Assigns, clears, or lists per-bench user labels for a project. A user label is a durable, human-friendly handle for a bench in a [multi-bench](#working-with-multiple-benches) project (numeric indices are positional and can shift), and is the value you pass to `--bench` on the bench-operating commands.
974
+ Assigns, clears, or lists per-bench user labels for a project.
975
+ A user label is a durable, human-friendly alternative to the bench's stable numeric identity, and is the value you pass to `--bench` on the bench-operating commands.
963
976
 
964
977
  ```bash
965
978
  cwcli label [OPTIONS] PROJECT_NAME [SELECTOR] [NEW_LABEL]
@@ -989,7 +1002,9 @@ cwcli label [OPTIONS] PROJECT_NAME [SELECTOR] [NEW_LABEL]
989
1002
  **Behavior:**
990
1003
 
991
1004
  - Run `cwcli inspect <project>` first so the benches are cached; without a cache the command errors.
992
- - Listing benches (bare `cwcli label <project>`) is read-only and needs no running container.
1005
+ - Listing benches (bare `cwcli label <project>`) is read-only and never starts anything, but it does check each cached bench against the running container so a bench whose directory has been removed is marked **GONE** instead of listed as if it were there.
1006
+ With no container to ask (a stopped project, an unreachable daemon) the rows are marked *cached, not verified* rather than presented as confirmed; the listing still works, so it can still tell you which `--bench` to pass to `cwcli start`.
1007
+ A stale row is reported, never deleted - refreshing the cache is `cwcli inspect --update`'s job.
993
1008
  - Setting or clearing a label writes it to **both** the SQLite cache and a marker file (`<bench-root>/.cwcli/.bench-label`) inside the bench, so labels survive a cache wipe and can be rebuilt by a full `cwcli inspect --update`. Because the marker lives inside the bench, setting or clearing a label requires a running frappe container; it will **not** auto-start a stopped project.
994
1009
 
995
1010
  **Examples:**
@@ -1099,13 +1114,20 @@ cwcli apps checkout [OPTIONS] PROJECT_NAME APP REF
1099
1114
 
1100
1115
  **`apps list`** - lists apps available in the bench (live `ls apps/`); with `--installed`/`--site` it also lists the apps installed per site (all sites by default, grouped by site).
1101
1116
 
1102
- **`apps install`** - fetches (`bench get-app`, honoring `--branch`) and installs each app on the target site(s). Each `APP` is a known app name **or** a git URL (passed straight to `bench get-app`, so custom apps not in bench's registry work). `--fetch-only` fetches without installing on any site.
1117
+ **`apps install`** - fetches (`bench get-app`, honoring `--branch`) and installs each app on the target site(s). Each `APP` is a known app name **or** a git URL (passed straight to `bench get-app`, so custom apps not in bench's registry work). `--fetch-only` fetches without installing on any site. A bench that is already running may still serve code it loaded before the install and fail to see the new app: install therefore **resynchronises** that bench - it restarts every cwcli-supervised program that runs the bench's Python (`web`, `schedule`, and every worker) and requires every changed site to answer Frappe before it reports success. Each restart is announced as it happens and the step is reported as a `restart-processes` row, and the command exits non-zero rather than claiming success for a site it could not confirm. A running manager that cwcli does not own is verified without being restarted; an unhealthy site fails with a manual-restart remedy. `socketio`, `watch`, and the redis programs are deliberately left alone: they import no Frappe app, and cycling redis would drop the cache and the job queue for nothing. Restarting a worker does interrupt a job in flight, which is the honest cost of not leaving background jobs running code that no longer exists. A bench that was not running is left alone.
1103
1118
 
1104
- **`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).
1119
+ **`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.
1105
1120
 
1106
- **`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. `--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.
1121
+ **`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.
1107
1122
 
1108
- **`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. Unlike `apps install` (a fresh `bench get-app` clone) 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). 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.
1123
+ **`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.
1124
+ Unlike `apps install` (a fresh `bench get-app` clone) 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).
1125
+ 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.
1126
+ Once the checkout step moves the tree, the command runs the same **resynchronise** step `apps install` uses, even if a later `--reset` step fails.
1127
+ 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.
1128
+ 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.
1129
+ 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.
1130
+ If the bench's site list itself cannot be read, the resynchronisation fails.
1109
1131
  Dirty means precisely **anything `git status --porcelain` reports: staged changes, unstaged modifications to tracked files, and untracked files**.
1110
1132
  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.
1111
1133
  `--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.
@@ -1390,7 +1412,7 @@ cwcli restore [OPTIONS] PROJECT_NAME
1390
1412
  | `--admin-password TEXT` | Set administrator password after restore |
1391
1413
  | `--send` | **P2P Mode:** Share backup with another machine via peer-to-peer transfer |
1392
1414
  | `--receive` | **P2P Mode:** Receive backup from another machine via peer-to-peer transfer |
1393
- | `--ticket TEXT` | The sendme ticket for `--receive`. When supplied, the ticket prompt is skipped; a non-TTY without `--ticket` refuses with a non-zero exit |
1415
+ | `--ticket TEXT` | The sendme ticket for `--receive`. When supplied, the ticket prompt is skipped; a non-TTY without `--ticket` refuses with a non-zero exit. Obviously malformed or truncated tickets are rejected before the transfer starts |
1394
1416
  | `--no-recache` | **Deprecated no-op:** the missing-apps check now reads app availability live from the bench, so it never re-caches. Kept for backward compatibility |
1395
1417
  | `--no-migrate` | Skip the post-restore `bench migrate` and instance restart. By default a successful restore runs `bench migrate` (bringing the restored DB to the code's schema) then restarts the instance |
1396
1418
  | `-y`, `--yes` | Skip the interactive confirmation prompts on both the normal and `--receive` restore paths (the destructive-restore confirmation and the missing-apps prompt). A non-TTY without `--yes` refuses these and exits non-zero. Does not remove the sendme-ticket or MariaDB-credential prompts |
@@ -1527,10 +1549,15 @@ Backup: 20251112_105638-development_localhost-database.sql.gz
1527
1549
  ```
1528
1550
 
1529
1551
  For scripted (non-interactive) receives, pass `-y`/`--yes` to skip the confirmation and the missing-apps prompt. Without a TTY and without `--yes`, cwcli refuses and exits non-zero rather than silently overwriting the site's data.
1552
+ Before transferring, cwcli requires at least 2 GiB free in its temporary [transfer directory](#architecture).
1553
+ It checks before the sender stages a backup and before the receiver downloads one.
1554
+ The receiver also validates the target site before downloading.
1555
+ The backup may require more space.
1556
+ Set `TMPDIR`, `TEMP`, or `TMP` to place transfers on a larger filesystem, in that priority order, or relocate all cwcli data with `CWCLI_HOME`.
1557
+ Copying the downloaded backup into the container streams through a bounded pipe instead of creating another full host-side copy.
1530
1558
 
1531
1559
  **P2P Transfer Features:**
1532
1560
  - **Hash-verified transfers** - BLAKE3 cryptographic verification ensures data integrity
1533
- - **Resumable downloads** - Interrupted transfers can resume from where they stopped
1534
1561
  - **NAT traversal** - Works behind firewalls and corporate networks
1535
1562
  - **No cloud intermediary** - Direct peer-to-peer connections
1536
1563
  - **Cross-platform** - Works on macOS, Linux, and Windows
@@ -1760,6 +1787,60 @@ cwcli status -w --interval 5 frappe-one # refresh every 5s
1760
1787
 
1761
1788
  ---
1762
1789
 
1790
+ ### `doctor` - Preflight Your Environment
1791
+
1792
+ Checks whether cwcli can operate on this machine at all - Docker, cwcli's own
1793
+ on-disk footprint, disk space, sendme, and the `gh`/`glab` credential-bridge
1794
+ dependencies - as a fast, read-only report. `status`/`inspect` answer "is this
1795
+ instance healthy"; `doctor` answers "will the next command even work here".
1796
+
1797
+ ```bash
1798
+ cwcli doctor [OPTIONS]
1799
+ ```
1800
+
1801
+ **Options:**
1802
+
1803
+ | Option | Description |
1804
+ |--------|-------------|
1805
+ | `-v`, `--verbose` | Show each check's stable id (e.g. `[c2]`) alongside its title |
1806
+
1807
+ **Read-only checks:** doctor never starts a container, installs anything, or
1808
+ writes a config file.
1809
+ There is one accepted shared-infrastructure exception before the checks run:
1810
+ building the command tree for `cwcli doctor` or `cwcli axi doctor` imports the
1811
+ pre-existing cache layer, which may idempotently create cwcli's private cache
1812
+ directory and apply mode `0700`.
1813
+ It does not touch project data.
1814
+ Making that initialization lazy is tracked separately as
1815
+ `cwcli-cache-dir-lazy-init`; this caveat should disappear when that lands.
1816
+ When a direct remedy is available, the finding includes the command or action
1817
+ to apply yourself.
1818
+
1819
+ **Checks always run in full** (no tiers, no selection flags), grouped into
1820
+ Docker, cwcli, Storage, Transfer, and Git hosting, plus a cross-instance
1821
+ port-range collision check under Instances. Each check is `pass`, `warn`, or
1822
+ `fail`; an absent *optional* tool (sendme, `gh`, `glab`) is `warn`, never
1823
+ `fail`.
1824
+
1825
+ **Exit codes:** `0` when every check is pass/warn (a warning never blocks -
1826
+ doctor is a chainable preflight gate), `1` when any check fails.
1827
+
1828
+ **Example:**
1829
+
1830
+ ```bash
1831
+ cwcli doctor # grouped glyph checklist
1832
+ cwcli doctor -v # same, with each check's stable id shown
1833
+ ```
1834
+
1835
+ For agents, `cwcli axi doctor` emits the same checks as one TOON document with
1836
+ a machine-readable `status` token per row.
1837
+ The version row also carries `version_verified`, so `status: pass` with
1838
+ `version_verified: false` is explicitly unverified rather than a claim that the
1839
+ installed version is current.
1840
+ See [the `axi` surface](#for-agents-the-cwcli-axi-surface).
1841
+
1842
+ ---
1843
+
1763
1844
  ### `config` - Manage Configuration
1764
1845
 
1765
1846
  Manages the CLI configuration and cache.
@@ -1946,7 +2027,11 @@ cwcli self-update --no-cache
1946
2027
 
1947
2028
  **Passive update notices:**
1948
2029
 
1949
- Every `cwcli` and `cwcli axi` run also does a passive, cache-only check: if a newer release is already known (from the same ≤1-day cache `self-update`/`--check` share) and you're at an interactive terminal, cwcli prints a one-line "a newer cwcli is available" hint - with the right upgrade command for how you installed it - to stderr. It never makes a blocking network call itself: PyPI is actually re-checked at most once/day, via a detached background refresh kicked off whenever the cache is missing or stale, so this never delays a command; until that refresh lands, the hint keeps showing on every run. It never touches stdout, so it's invisible to pipes, scripts, CI, and `cwcli axi`'s TOON output. Set `CWCLI_NO_UPDATE_CHECK=1` to suppress it entirely.
2030
+ Every `cwcli` and `cwcli axi` run except the strictly read-only doctor commands also does a passive, cache-only check: if a newer release is already known (from the same ≤1-day cache `self-update`/`--check` share) and you're at an interactive terminal, cwcli prints a one-line "a newer cwcli is available" hint with the right upgrade command for how you installed it to stderr.
2031
+ `cwcli doctor` and `cwcli axi doctor` skip this check because a stale or missing version cache would otherwise launch a background refresh that writes it.
2032
+ The notice never makes a blocking network call itself: PyPI is actually re-checked at most once/day, via a detached background refresh kicked off whenever the cache is missing or stale, so this never delays a command; until that refresh lands, the hint keeps showing on each eligible run.
2033
+ It never touches stdout, so it is invisible to pipes, scripts, CI, and `cwcli axi`'s TOON output.
2034
+ Set `CWCLI_NO_UPDATE_CHECK=1` to suppress it entirely.
1950
2035
 
1951
2036
  ---
1952
2037
 
@@ -1956,8 +2041,10 @@ A single cwcli project (one Docker Compose instance) can hold more than one benc
1956
2041
 
1957
2042
  **How benches are addressed:**
1958
2043
 
1959
- - **Numeric index** - Every bench discovered by `cwcli inspect` gets an index (`0`, `1`, `2`, ...) from a stable, sorted-by-path order. `cwcli inspect` shows it next to each bench (for example `Bench [1] at /workspace/frappe-bench-2`). An index is positional, so it can shift when a bench is added or removed.
1960
- - **User label** - A durable, human-friendly handle you assign with the [`label`](#label---manage-bench-labels) command or `cwcli inspect -i`. Because it does not move when benches are renumbered, a label is the reliable way to target a bench in scripts. Labels may not be purely numeric (that would collide with an index) and must be unique within a project.
2044
+ - **Numeric index** - Every bench discovered by `cwcli inspect` gets a durable per-project number. `cwcli inspect` shows it next to each bench (for example `Bench [1] at /workspace/frappe-bench-2`). The number stays attached to that bench path when another bench is added or removed, so a stored or scripted `--bench 1` never quietly starts addressing a different bench.
2045
+ Removed numbers are not reused, so the available indices may be sparse, such as `0`, `2`, and `3`.
2046
+ Benches are listed in that numeric order, so a newly added bench appears at the end of the list even when its path sorts earlier.
2047
+ - **User label** - A durable, human-friendly handle you assign with the [`label`](#label---manage-bench-labels) command or `cwcli inspect -i`. Labels may not be purely numeric (that would collide with an index) and must be unique within a project.
1961
2048
 
1962
2049
  **The `--bench` selector:**
1963
2050
 
@@ -2025,6 +2112,9 @@ cwcli inspect frappe-one --json | jq '.bench_instances[0].sites'
2025
2112
  It sits on the same logic core as the human commands but renders differently:
2026
2113
 
2027
2114
  - **Structured output on stdout** in [TOON](https://toonformat.dev) (a token-efficient, agent-readable format); progress and diagnostics go to stderr, so stdout is always clean, parseable data.
2115
+ - **TOON help across the complete command tree.** `cwcli axi --help` and every nested `cwcli axi ... --help` path derive concise usage, commands, arguments, flags, defaults, descriptions, notes, and examples from the live command metadata.
2116
+ Group listings preserve the complete current command summaries, argument names match the usage line, and notes preserve bullet boundaries without leaking source markup.
2117
+ The human `cwcli ... --help` surface remains Rich.
2028
2118
  - **No prompts, ever.** Every operation completes from flags alone; a decision it cannot make (an ambiguous multi-bench project, a stopped instance) is reported as a structured usage error naming the exact flag to pass, not an interactive question. An unrecognized flag or a missing required argument is likewise a structured `error:`+`help:` usage error on stdout - never a leaked stack trace or empty output - with the `help:` line naming the command's valid flags.
2029
2119
  - **Conventional exit codes:** `0` success (including no-ops), `1` error, `2` usage error.
2030
2120
 
@@ -2079,6 +2169,16 @@ cwcli axi status frappe-one --bench 1
2079
2169
  cwcli axi logs frappe-one
2080
2170
  cwcli axi logs frappe-one --process web --lines 50
2081
2171
 
2172
+ # Resolve a bench's host URL and probe it fresh, right now; prints as TOON. This
2173
+ # is the agent verb that reports `--bench 1`'s real address (bench 1 of a
2174
+ # --port 21000 instance is :21001, not :8000) plus whether it answers HTTP now.
2175
+ # `cwcli axi status` also probes fresh once; only human `cwcli status --watch`
2176
+ # suppresses HTTP probing. An unreadable port config is `url: null` /
2177
+ # `reachable: false`, never a guessed :8000; --site sends that Host header
2178
+ # (Frappe routes by Host) instead of the bench's representative site.
2179
+ cwcli axi url frappe-one
2180
+ cwcli axi url frappe-one --bench 1 --site erp.localhost
2181
+
2082
2182
  # Restart ONE supervised process (siblings keep running); the outcome prints as
2083
2183
  # TOON. --process is required; --bench selects a bench on a multi-bench project.
2084
2184
  cwcli axi restart frappe-one --process web
@@ -2101,7 +2201,9 @@ cwcli axi inspect frappe-one --update
2101
2201
 
2102
2202
  # List a project's benches with their indices and labels. This is what answers
2103
2203
  # "pass --bench <index|label>" from any other verb - no other verb can.
2204
+ # Each row carries `state` (present/absent/unverified) - read it before acting.
2104
2205
  cwcli axi benches frappe-one
2206
+ cwcli axi benches frappe-one --no-verify # bare cache read; reports `unverified`
2105
2207
 
2106
2208
  # Set or clear a bench's durable user label. Exactly one of --set/--clear is
2107
2209
  # required; listing is `cwcli axi benches`, not a mode of this verb.
@@ -2151,9 +2253,26 @@ cwcli axi self-update --check
2151
2253
  # auto-inspect state (config, live daemon, boot hook - reported separately),
2152
2254
  # tips, and the config-file/cache-DB locations.
2153
2255
  cwcli axi config
2256
+
2257
+ # System-wide environment preflight as one TOON document: Docker, cwcli's own
2258
+ # footprint, disk space, sendme, gh/glab. Machine-readable status per check;
2259
+ # exits 0 unless a check FAILS (a warning never blocks a chained preflight).
2260
+ cwcli axi doctor
2154
2261
  ```
2155
2262
 
2156
- `cwcli axi ls`, `cwcli axi where`, `cwcli axi backup`, `cwcli axi unlock`, `cwcli axi stop`, `cwcli axi start`, `cwcli axi status`, `cwcli axi restart`, `cwcli axi inspect`, `cwcli axi benches`, and `cwcli axi label` run on the same logic core as their human counterparts; only the output (always TOON, never JSON) and choice-handling differ. `cwcli axi start` never prompts: an ambiguous multi-bench project is a `--bench` usage error (exit 2), and an unresolved port conflict is a `CONFLICT` error naming `--yes` (exit 1). `cwcli axi unlock` follows `cwcli axi backup`'s conventions exactly: an ambiguous multi-bench project is a `--bench` usage error (exit 2) and a stopped instance is a usage error pointing at `cwcli start` (exit 2). `cwcli axi stop` is idempotent for an agent - stopping an already-stopped project is a success, not an error. `cwcli axi where` answers from the cache, which outlives the instances it describes, so every match carries `project_state` (`present`, `absent`, or `unverified`) and the document carries `verified`: an agent must read those before acting on a hit, because an `absent` row describes an instance that no longer exists and an `unverified` row means the liveness check could not run. An unreachable Docker daemon degrades the whole answer to `unverified` rather than vouching for it, and `--no-verify` skips the check (reporting `unverified`) when a caller has already established liveness. `cwcli axi status` always exits 0, leading with the instance `overall` aggregate (`offline`/`online`/`running`/`degraded`). It reports **every** bench, in one document, under `benches[N]` - with no `--bench` it used to refuse a multi-bench project with exit 2 and send you off to `cwcli axi benches` to poll once per bench, reassembling the instance view from documents that never said which bench they described. The shape is uniform, so `supervisor_up`, `web_http_code`, `processes` and `not_cwcli_supervised` live inside `benches[i]` even for a single-bench project (`benches[0].processes`), and there is one parse path rather than a branch on bench count. Each bench also carries `index`, `bench_path`, `label`, its own `overall`, `web_port`/`web_port_verified` (the port that bench's `web_http_code` was actually measured on), and `web_site` (the site it was measured FOR - Frappe routes by `Host`, so the code belongs to that site, and a probe naming no site is answered `404` by a healthy bench). When that port cannot be read it is `null`/`false` and **no probe was made** - cwcli never falls back to `:8000`, because on a multi-bench instance that reports a different bench's web server as this one's. A stopped instance carries `benches: []`. `cwcli axi stop` takes `--bench <index|label>` to stop ONE bench's dev processes, leaving sibling benches and every container running - the inverse of `cwcli axi start --bench`; without it, it stops the whole instance's containers, and either form is idempotent. `cwcli axi restart` requires `--process`; an unknown/ambiguous process is a usage error listing the valid labels (exit 2). `cwcli axi inspect` serves whichever freshness tier answers the request (`served_from` names it) and, unlike every other bench-scoped verb, has deliberately NO `--yes` - a stopped project on the refresh path is a usage error (exit 2) naming `cwcli start`, and a drift escalation that can no longer discover the bench serves the cached data with `degraded: true` and a warning (exit 0). Every site also carries `installed_apps_verified`: only a full read actually re-observes an app's installed version and git ref, so a cache or partial read carries the remembered list forward unverified and, when that list is non-empty, adds a warning naming `--update` - an agent must not treat an unverified `installed_apps` entry as the live git state. `cwcli axi benches` is the discovery verb behind every other verb's `--bench`: when a bench-scoped verb reports "multiple benches; pass `--bench <index|label>`", this is what tells you the valid values, and a project that has never been inspected is a structured error naming `cwcli axi inspect` rather than an empty list. `cwcli axi label` never starts a stopped project (the label marker lives inside the bench), and listing is deliberately `cwcli axi benches` rather than a mode of the mutation verb. `cwcli axi self-update --check` is read-only and **exits 0 whenever the check succeeds, including when an update is available** - the answer is the `is_outdated` field, not the exit code, because on the agent surface a non-zero exit means an error. This deliberately differs from the human `cwcli self-update --check`, which exits 1 when an update is available so shell scripts can gate on it; the mutating `cwcli axi self-update` is deliberately not offered. `cwcli axi config` is the read-only counterpart of `cwcli config show`: one TOON document carrying the search paths, all three auto-inspect state stores, the tips setting, and the file locations. **No config-mutating axi verbs exist** (no `paths add`/`remove`, no `cache clear`, no `auto-inspect enable`/`disable`): an agent rewriting the user's search paths or wiping the cache is a product decision that deserves its own evidence, so mutations stay on the human `cwcli config` surface (whose reads all have `--json`).
2263
+ `cwcli axi ls`, `cwcli axi where`, `cwcli axi backup`, `cwcli axi unlock`, `cwcli axi stop`, `cwcli axi start`, `cwcli axi status`, `cwcli axi restart`, `cwcli axi inspect`, `cwcli axi benches`, and `cwcli axi label` run on the same logic core as their human counterparts; only the output (always TOON, never JSON) and choice-handling differ. `cwcli axi start` never prompts: an ambiguous multi-bench project is a `--bench` usage error (exit 2), and an unresolved port conflict is a `CONFLICT` error naming `--yes` (exit 1). `cwcli axi unlock` follows `cwcli axi backup`'s conventions exactly: an ambiguous multi-bench project is a `--bench` usage error (exit 2) and a stopped instance is a usage error pointing at `cwcli start` (exit 2). `cwcli axi stop` is idempotent for an agent - stopping an already-stopped project is a success, not an error. `cwcli axi where` answers from the cache, which outlives the instances it describes, so every match carries `project_state` (`present`, `absent`, or `unverified`) and the document carries `verified`: an agent must read those before acting on a hit, because an `absent` row describes an instance that no longer exists and an `unverified` row means the liveness check could not run. An unreachable Docker daemon degrades the whole answer to `unverified` rather than vouching for it, and `--no-verify` skips the check (reporting `unverified`) when a caller has already established liveness. `cwcli axi status` always exits 0, leading with the instance `overall` aggregate (`offline`/`online`/`running`/`degraded`). It reports **every** bench, in one document, under `benches[N]` - with no `--bench` it used to refuse a multi-bench project with exit 2 and send you off to `cwcli axi benches` to poll once per bench, reassembling the instance view from documents that never said which bench they described. The shape is uniform, so `supervisor_up`, `web_http_code`, `processes` and `not_cwcli_supervised` live inside `benches[i]` even for a single-bench project (`benches[0].processes`), and there is one parse path rather than a branch on bench count. Each bench also carries `bench_present` (`present`/`absent`/`unverified`) - the bench LIST comes from the cache, and a bench whose directory was deleted has no marker and no supervisord, which is indistinguishable from one that was simply never started, so it used to read as `online`; the token answers that, and `overall` keeps its four tokens. Each bench also carries `index`, `bench_path`, `label`, its own `overall`, `web_port`/`web_port_verified` (the port that bench's `web_http_code` was actually measured on), and `web_site` (the site it was measured FOR - Frappe routes by `Host`, so the code belongs to that site, and a probe naming no site is answered `404` by a healthy bench). When that port cannot be read it is `null`/`false` and **no probe was made** - cwcli never falls back to `:8000`, because on a multi-bench instance that reports a different bench's web server as this one's. A stopped instance carries `benches: []`. `cwcli axi stop` takes `--bench <index|label>` to stop ONE bench's dev processes, leaving sibling benches and every container running - the inverse of `cwcli axi start --bench`; without it, it stops the whole instance's containers, and either form is idempotent. `cwcli axi restart` requires `--process`; an unknown/ambiguous process is a usage error listing the valid labels (exit 2). `cwcli axi inspect` serves whichever freshness tier answers the request (`served_from` names it) and, unlike every other bench-scoped verb, has deliberately NO `--yes` - a stopped project on the refresh path is a usage error (exit 2) naming `cwcli start`, and a drift escalation that can no longer discover the bench serves the cached data with `degraded: true` and a warning (exit 0). Every site also carries `installed_apps_verified`: only a full read actually re-observes an app's installed version and git ref, so a cache or partial read carries the remembered list forward unverified and, when that list is non-empty, adds a warning naming `--update` - an agent must not treat an unverified `installed_apps` entry as the live git state. `cwcli axi benches` is the discovery verb behind every other verb's `--bench`: when a bench-scoped verb reports "multiple benches; pass `--bench <index|label>`", this is what tells you the valid values, and a project that has never been inspected is a structured error naming `cwcli axi inspect` rather than an empty list. Its rows come from the cache, which outlives the benches it describes, so every row carries `state` (`present`, `absent`, or `unverified`) and the document carries `verified` - the same contract `cwcli axi where` uses, cross-checked here against the running container in one exec. An agent must read `state` before acting on a row: `absent` means that bench directory no longer exists, and `unverified` means the check could not run (a stopped project, an unreachable daemon, or `--no-verify`) - never a confirmation. A stale row is reported, never pruned; `cwcli axi inspect <project> --update` is what refreshes it. `cwcli axi label` never starts a stopped project (the label marker lives inside the bench), and listing is deliberately `cwcli axi benches` rather than a mode of the mutation verb. `cwcli axi self-update --check` is read-only and **exits 0 whenever the check succeeds, including when an update is available** - the answer is the `is_outdated` field, not the exit code, because on the agent surface a non-zero exit means an error. This deliberately differs from the human `cwcli self-update --check`, which exits 1 when an update is available so shell scripts can gate on it; the mutating `cwcli axi self-update` is deliberately not offered. `cwcli axi config` is the read-only counterpart of `cwcli config show`: one TOON document carrying the search paths, all three auto-inspect state stores, the tips setting, and the file locations. **No config-mutating axi verbs exist** (no `paths add`/`remove`, no `cache clear`, no `auto-inspect enable`/`disable`): an agent rewriting the user's search paths or wiping the cache is a product decision that deserves its own evidence, so mutations stay on the human `cwcli config` surface (whose reads all have `--json`). `cwcli axi doctor` is the read-only, system-wide counterpart of `cwcli doctor`: one TOON document listing every check with a machine-readable `status` (`pass`/`warn`/`fail`) an agent gates on directly, never by parsing the `detail` text. It exits 0 whenever every check is pass/warn and non-zero only when a check fails, exactly matching the human command, so a fleet can run it before driving any instance and `&&`-chain on the result.
2264
+
2265
+ `cwcli axi url <project>` answers two questions no other `axi` verb does: what HOST URL a bench actually serves on, and whether it is answering HTTP right now.
2266
+ `cwcli axi status`'s `web_http_code` is measured against the bench's CONTAINER-internal port and never states the host address; the host URL is computed only by `cwcli open`'s success banner and the human `cwcli init` banner, neither reachable from `cwcli axi`, so getting either answer meant dropping to a raw `docker inspect`/`curl` against the container.
2267
+ This verb reuses those existing primitives rather than adding a second URL resolver or probe mechanism: the URL comes from the same two-hop resolution `cwcli open` already uses (the bench's own assigned container port, mapped through the container's live published port bindings), and the probe is a fresh `curl` run **every invocation**.
2268
+ `cwcli axi status` also performs a fresh one-shot probe; only the human `cwcli status --watch` path suppresses HTTP probing.
2269
+ Like other single-bench verbs such as `cwcli axi logs`, a stopped project is a usage error naming `cwcli start` (exit 2, no `--yes`) and an ambiguous multi-bench project with no `--bench` is a usage error naming it.
2270
+ `--site` sends that site as the `Host` header (Frappe routes by `Host`); omitted, it falls back to the bench's own representative site (its default, else its only/first cached site), reported so the observation stays attributable, and a bench with no site at all is probed host-less rather than guessing one.
2271
+ An unreadable port config is `url: null` / `reachable: false` with a warning that names the live config file to repair; a missing live host binding names `cwcli axi scale` when the bench lies beyond the published range.
2272
+ Neither case guesses `:8000`.
2273
+ `http_code` is whatever code curl saw, verbatim: a 404 or 500 still counts as `reachable: true`, exactly as `cwcli axi status` already treats "any code is serving".
2274
+ There is no human `cwcli url` yet.
2275
+ Use `cwcli open`'s banner for the host address and `cwcli status` for the HTTP observation.
2157
2276
 
2158
2277
  `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`).
2159
2278
 
@@ -2181,8 +2300,10 @@ Note that the already-installed case is **not** reported as an idempotent exit-`
2181
2300
  The desired state here is "installed from this branch", and cwcli cannot confirm the copy already on the site matches the `--branch` you asked for, so exiting `0` would be asserting something it has not verified.
2182
2301
  Like every other bench-scoped verb it takes `--bench`, has no `--yes`, and reports a stopped project as a usage error naming `cwcli start`.
2183
2302
  Private-repo fetches use the same credential bridge as `apps update`/`apps checkout`, so no token is stored in the container.
2303
+ On an already-serving bench, the operation resynchronises that bench - it restarts every supervised program running the bench's Python (`web`, `schedule`, workers) and reports a `restart-processes` row.
2304
+ It exits non-zero if a restart fails or the named site does not answer Frappe's public ping with HTTP `200`, so `ok: true` means the installed app is visible to the running site.
2184
2305
 
2185
- `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; maintenance mode is enabled first 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.
2306
+ `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.
2186
2307
 
2187
2308
  `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.
2188
2309
 
@@ -2314,7 +2435,7 @@ The CLI uses:
2314
2435
  - **Questionary** - Interactive prompts
2315
2436
  - **Peewee ORM** - SQLite-based caching
2316
2437
 
2317
- **Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and any future GUI are all thin frontends over one implementation.
2438
+ **Logic core:** business logic and I/O live in a UI-pure `core/` package that carries no `rich`/`questionary`/`typer`; it returns a serializable typed envelope (or raises a typed error) so the human CLI, the `cwcli axi` agent surface, and the Console GUI are all thin frontends over one implementation.
2318
2439
  `backup`, `unlock`, `stop`, `label`, `run`, `ls`/`list`, `where`, `start`/`status`/`restart`, `logs`, `inspect`, `apps`, `update`, `open`, `init`, `config`, `restore`, `rm`, and `rm-site` are migrated onto it so far.
2319
2440
 
2320
2441
  **Data Directories:**
@@ -2323,11 +2444,12 @@ The CLI uses:
2323
2444
  - **Cache**: `~/.cwcli/cache/cwc-cache.db` - Project inspection cache
2324
2445
  - **Runtime**: `~/.cwcli/run/` - PID and log files for background services
2325
2446
  - **Archive**: `~/.cwcli/archive/` - Pre-deletion backups and config snapshots from `cwcli rm`, plus dropped-site archives from `cwcli rm-site`
2447
+ - **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
2326
2448
 
2327
2449
  **Relocating cwcli's data (`CWCLI_HOME`):**
2328
2450
 
2329
- Set the `CWCLI_HOME` environment variable to move cwcli's entire on-disk footprint - projects, config, cache, runtime, and archive files - out of `~/.cwcli` and into a directory of your choice.
2330
- When it is set, cwcli uses `$CWCLI_HOME/projects`, `$CWCLI_HOME/config`, `$CWCLI_HOME/cache`, `$CWCLI_HOME/run`, and `$CWCLI_HOME/archive` in place of the `~/.cwcli/*` locations above.
2451
+ Set the `CWCLI_HOME` environment variable to move cwcli's on-disk footprint - projects, config, cache, runtime, archive, and transfers without an explicit temp-directory override - out of `~/.cwcli` and into a directory of your choice.
2452
+ When it is set, cwcli uses the corresponding directories under `$CWCLI_HOME` in place of the `~/.cwcli/*` locations above, except when an explicit temp-directory variable controls transfers.
2331
2453
  When it is unset (or empty), cwcli uses the default `~/.cwcli` locations, so existing installs are unaffected.
2332
2454
 
2333
2455
  Unlike repointing `HOME`, `CWCLI_HOME` redirects only cwcli's own state - it does not change your process `HOME`, so `git`, `ssh`, and other tools that read `HOME` are untouched.