wasm-cli 1.0.1__tar.gz → 1.0.2__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 (179) hide show
  1. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/MANIFEST.in +1 -0
  2. {wasm_cli-1.0.1/src/wasm_cli.egg-info → wasm_cli-1.0.2}/PKG-INFO +1 -1
  3. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/man/wasm.1 +1 -1
  4. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/pyproject.toml +3 -1
  5. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/setup.py +1 -1
  6. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/__init__.py +1 -1
  7. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/interactive.py +72 -70
  8. wasm_cli-1.0.2/src/wasm/cli/prompts.py +182 -0
  9. wasm_cli-1.0.2/src/wasm/completions/README.md +16 -0
  10. wasm_cli-1.0.2/src/wasm/completions/_wasm +40 -0
  11. wasm_cli-1.0.2/src/wasm/completions/wasm.bash +28 -0
  12. wasm_cli-1.0.2/src/wasm/completions/wasm.fish +17 -0
  13. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/env_manager.py +10 -27
  14. {wasm_cli-1.0.1 → wasm_cli-1.0.2/src/wasm_cli.egg-info}/PKG-INFO +1 -1
  15. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/SOURCES.txt +5 -0
  16. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/LICENSE +0 -0
  17. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/README.md +0 -0
  18. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/MONITOR.md +0 -0
  19. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/OBS_SETUP.md +0 -0
  20. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/assets/logo.png +0 -0
  21. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/assets/logo_bg.png +0 -0
  22. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/superpowers/specs/2026-08-12-wasm-panel-design-direction.md +0 -0
  23. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/docs/superpowers/specs/2026-08-12-wasm-v1-refactor-design.md +0 -0
  24. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/setup.cfg +0 -0
  25. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/__main__.py +0 -0
  26. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/__init__.py +0 -0
  27. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/app.py +0 -0
  28. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/__init__.py +0 -0
  29. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/backup.py +0 -0
  30. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/cert.py +0 -0
  31. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/config.py +0 -0
  32. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/db.py +0 -0
  33. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/env.py +0 -0
  34. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/health.py +0 -0
  35. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/monitor.py +0 -0
  36. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/service.py +0 -0
  37. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/setup.py +0 -0
  38. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/site.py +0 -0
  39. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/store.py +0 -0
  40. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/version.py +0 -0
  41. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/web.py +0 -0
  42. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/cli/commands/webapp.py +0 -0
  43. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/__init__.py +0 -0
  44. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/config.py +0 -0
  45. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/dependencies.py +0 -0
  46. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/exceptions.py +0 -0
  47. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/fs.py +0 -0
  48. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/logger.py +0 -0
  49. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/runner.py +0 -0
  50. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/store.py +0 -0
  51. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/update_checker.py +0 -0
  52. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/core/utils.py +0 -0
  53. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/__init__.py +0 -0
  54. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/auto.py +0 -0
  55. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/base.py +0 -0
  56. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/docker_compose.py +0 -0
  57. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/__init__.py +0 -0
  58. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/health.py +0 -0
  59. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/nginx_config.py +0 -0
  60. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/package_manager.py +0 -0
  61. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/path_resolver.py +0 -0
  62. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/preflight.py +0 -0
  63. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/prisma.py +0 -0
  64. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/registration.py +0 -0
  65. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/summary.py +0 -0
  66. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/turbo.py +0 -0
  67. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/helpers/workspace.py +0 -0
  68. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/interface.py +0 -0
  69. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/monorepo.py +0 -0
  70. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/nextjs.py +0 -0
  71. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/nodejs.py +0 -0
  72. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/pipeline.py +0 -0
  73. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/python.py +0 -0
  74. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/registry.py +0 -0
  75. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/static.py +0 -0
  76. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/deployers/vite.py +0 -0
  77. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/main.py +0 -0
  78. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/__init__.py +0 -0
  79. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/apache_manager.py +0 -0
  80. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/backup_manager.py +0 -0
  81. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/backup_scheduler.py +0 -0
  82. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/base_manager.py +0 -0
  83. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/cert_manager.py +0 -0
  84. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/__init__.py +0 -0
  85. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/base.py +0 -0
  86. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/mongodb.py +0 -0
  87. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/mysql.py +0 -0
  88. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/postgres.py +0 -0
  89. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/redis.py +0 -0
  90. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/database/registry.py +0 -0
  91. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/nginx_manager.py +0 -0
  92. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/service_manager.py +0 -0
  93. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/source_manager.py +0 -0
  94. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/managers/webserver.py +0 -0
  95. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/__init__.py +0 -0
  96. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/email_notifier.py +0 -0
  97. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/metrics.py +0 -0
  98. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/models.py +0 -0
  99. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/observation_store.py +0 -0
  100. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/process_monitor.py +0 -0
  101. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/monitor/signals.py +0 -0
  102. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/__init__.py +0 -0
  103. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/apache/proxy.conf.j2 +0 -0
  104. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/apache/static.conf.j2 +0 -0
  105. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/advanced.conf.j2 +0 -0
  106. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/monorepo.conf.j2 +0 -0
  107. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/proxy.conf.j2 +0 -0
  108. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/nginx/static.conf.j2 +0 -0
  109. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/_escape.j2 +0 -0
  110. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/app.service.j2 +0 -0
  111. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/backup-service.j2 +0 -0
  112. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/backup-timer.j2 +0 -0
  113. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/templates/systemd/docker-compose.service.j2 +0 -0
  114. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/__init__.py +0 -0
  115. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/domain.py +0 -0
  116. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/environment.py +0 -0
  117. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/names.py +0 -0
  118. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/port.py +0 -0
  119. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/source.py +0 -0
  120. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/validators/ssh.py +0 -0
  121. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/__init__.py +0 -0
  122. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/__init__.py +0 -0
  123. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/apps.py +0 -0
  124. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/auth.py +0 -0
  125. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/backups.py +0 -0
  126. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/certs.py +0 -0
  127. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/config.py +0 -0
  128. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/databases.py +0 -0
  129. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/deps.py +0 -0
  130. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/jobs.py +0 -0
  131. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/monitor.py +0 -0
  132. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/router.py +0 -0
  133. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/services.py +0 -0
  134. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/sites.py +0 -0
  135. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/api/system.py +0 -0
  136. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/auth.py +0 -0
  137. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/jobs.py +0 -0
  138. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/server.py +0 -0
  139. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/app.css +0 -0
  140. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/logo.png +0 -0
  141. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/logo.webp +0 -0
  142. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/panel.js +0 -0
  143. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/alpine.min.js +0 -0
  144. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  145. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  146. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-condensed-latin-500-normal.woff2 +0 -0
  147. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-condensed-latin-600-normal.woff2 +0 -0
  148. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  149. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  150. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  151. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/htmx.min.js +0 -0
  152. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/uplot.min.css +0 -0
  153. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/uplot.min.js +0 -0
  154. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/xterm.css +0 -0
  155. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/static/vendor/xterm.js +0 -0
  156. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/_macros.html +0 -0
  157. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/base.html +0 -0
  158. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/fragments/machine.html +0 -0
  159. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/login.html +0 -0
  160. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/activity.html +0 -0
  161. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/app.html +0 -0
  162. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/backups.html +0 -0
  163. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/certificates.html +0 -0
  164. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/dashboard.html +0 -0
  165. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/missing.html +0 -0
  166. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/resources.html +0 -0
  167. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/templates/pages/settings.html +0 -0
  168. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/__init__.py +0 -0
  169. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/context.py +0 -0
  170. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/machine.py +0 -0
  171. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/rendering.py +0 -0
  172. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/resources.py +0 -0
  173. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/views/router.py +0 -0
  174. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/websockets/__init__.py +0 -0
  175. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm/web/websockets/router.py +0 -0
  176. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
  177. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/entry_points.txt +0 -0
  178. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/requires.txt +0 -0
  179. {wasm_cli-1.0.1 → wasm_cli-1.0.2}/src/wasm_cli.egg-info/top_level.txt +0 -0
@@ -10,6 +10,7 @@ include pyproject.toml
10
10
  # Include source code (handled by setuptools, but explicit is better)
11
11
  recursive-include src *.py
12
12
  recursive-include src/wasm/templates *.j2
13
+ recursive-include src/wasm/completions *
13
14
  recursive-include src/wasm/web/static *
14
15
  recursive-include src/wasm/web/templates *.html
15
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wasm-cli
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Web App System Management - Deploy and manage web applications on Linux servers
5
5
  Author: Yago López Prado
6
6
  Author-email: Yago López Prado <yago.lopez.adeje@gmail.com>
@@ -1,4 +1,4 @@
1
- .TH WASM 1 "2026-08-12" "WASM 1.0.0" "Web App System Management"
1
+ .TH WASM 1 "2026-08-12" "WASM 1.0.1" "Web App System Management"
2
2
  .\" Generated by scripts/generate_man.py. Do not edit by hand.
3
3
  .SH NAME
4
4
  wasm \- deploy and manage web applications on a Linux server
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wasm-cli"
7
- version = "1.0.1"
7
+ version = "1.0.2"
8
8
  description = "Web App System Management - Deploy and manage web applications on Linux servers"
9
9
  readme = "README.md"
10
10
  license = { text = "WASM-NCSAL" }
@@ -108,6 +108,8 @@ include = ["wasm*"]
108
108
  wasm = [
109
109
  # nginx, apache and systemd unit templates.
110
110
  "templates/**/*.j2",
111
+ # Committed, not generated: see src/wasm/completions/README.md.
112
+ "completions/*",
111
113
  # The panel: its pages, its stylesheet and every third-party asset it
112
114
  # serves. All of it has to ship, because the panel renders server-side and
113
115
  # fetches nothing from the network.
@@ -9,7 +9,7 @@ from setuptools import setup, find_packages
9
9
 
10
10
  setup(
11
11
  name="wasm-cli",
12
- version="1.0.1",
12
+ version="1.0.2",
13
13
  description="Web App System Management - Deploy and manage web applications on Linux servers",
14
14
  author="Yago López Prado",
15
15
  author_email="yago.lopez.adeje@gmail.com",
@@ -15,7 +15,7 @@ from importlib.metadata import version as _installed_version
15
15
  #: as an OBS build directory. The single source of truth is the ``version``
16
16
  #: field of pyproject.toml; ``scripts/release.py`` keeps this literal and the
17
17
  #: distribution packaging files in step with it.
18
- _FALLBACK_VERSION = "1.0.1"
18
+ _FALLBACK_VERSION = "1.0.2"
19
19
 
20
20
  try:
21
21
  __version__ = _installed_version("wasm-cli")
@@ -1,15 +1,13 @@
1
1
  """
2
- Interactive mode for WASM using inquirer.
3
- """
4
-
5
- try:
6
- import inquirer
7
- from inquirer.themes import GreenPassion
2
+ Interactive mode.
8
3
 
9
- HAS_INQUIRER = True
10
- except ImportError:
11
- HAS_INQUIRER = False
4
+ Prompts come from :mod:`wasm.cli.prompts`, which is built on questionary. The
5
+ previous implementation used inquirer, which is not packaged in Debian or
6
+ Ubuntu, so interactive mode never worked on the distributions most WASM users
7
+ run.
8
+ """
12
9
 
10
+ from wasm.cli import prompts
13
11
  from wasm.core.exceptions import WASMError
14
12
  from wasm.core.logger import Logger
15
13
  from wasm.validators.domain import check_domain
@@ -19,7 +17,7 @@ from wasm.validators.source import is_valid_source
19
17
 
20
18
  class InteractiveMode:
21
19
  """
22
- Interactive mode handler using inquirer prompts.
20
+ Interactive mode handler.
23
21
  """
24
22
 
25
23
  def __init__(self, verbose: bool = False):
@@ -32,10 +30,14 @@ class InteractiveMode:
32
30
  self.verbose = verbose
33
31
  self.logger = Logger(verbose=verbose)
34
32
 
35
- if not HAS_INQUIRER:
33
+ if not prompts.AVAILABLE:
36
34
  raise WASMError(
37
- "Interactive mode requires 'inquirer' package.",
38
- "Install with: pip install wasm-cli[interactive]\nOr: pip install inquirer",
35
+ "Interactive mode needs questionary, which is missing",
36
+ details=(
37
+ "It is a hard dependency, so this means a broken install.\n"
38
+ " pip install --force-reinstall wasm-cli\n"
39
+ " or, on a distribution package: apt install python3-questionary"
40
+ ),
39
41
  )
40
42
 
41
43
  def run(self) -> int:
@@ -73,7 +75,7 @@ class InteractiveMode:
73
75
  def _prompt_action_type(self) -> str:
74
76
  """Prompt for action type."""
75
77
  questions = [
76
- inquirer.List(
78
+ prompts.List(
77
79
  "action_type",
78
80
  message="What would you like to do?",
79
81
  choices=[
@@ -85,13 +87,13 @@ class InteractiveMode:
85
87
  ),
86
88
  ]
87
89
 
88
- answers = inquirer.prompt(questions, theme=GreenPassion())
90
+ answers = prompts.prompt(questions)
89
91
  return answers["action_type"] if answers else None
90
92
 
91
93
  def _webapp_flow(self) -> int:
92
94
  """Handle webapp interactive flow."""
93
95
  questions = [
94
- inquirer.List(
96
+ prompts.List(
95
97
  "action",
96
98
  message="What action would you like to perform?",
97
99
  choices=[
@@ -108,7 +110,7 @@ class InteractiveMode:
108
110
  ),
109
111
  ]
110
112
 
111
- answers = inquirer.prompt(questions, theme=GreenPassion())
113
+ answers = prompts.prompt(questions)
112
114
  if not answers:
113
115
  return 0
114
116
 
@@ -155,7 +157,7 @@ class InteractiveMode:
155
157
  def _webapp_create(self) -> int:
156
158
  """Handle webapp create flow."""
157
159
  questions = [
158
- inquirer.List(
160
+ prompts.List(
159
161
  "type",
160
162
  message="Select application type",
161
163
  choices=[
@@ -167,23 +169,23 @@ class InteractiveMode:
167
169
  ("🔍 Auto-detect", "auto"),
168
170
  ],
169
171
  ),
170
- inquirer.Text(
172
+ prompts.Text(
171
173
  "domain",
172
174
  message="Enter target domain",
173
175
  validate=lambda _, x: check_domain(x) or "Invalid domain name",
174
176
  ),
175
- inquirer.Text(
177
+ prompts.Text(
176
178
  "source",
177
179
  message="Enter source (Git URL or path)",
178
180
  validate=lambda _, x: is_valid_source(x) or "Invalid source",
179
181
  ),
180
- inquirer.Text(
182
+ prompts.Text(
181
183
  "port",
182
184
  message="Application port (leave empty for auto)",
183
185
  default="",
184
186
  validate=lambda _, x: x == "" or check_port(x) or "Invalid port",
185
187
  ),
186
- inquirer.List(
188
+ prompts.List(
187
189
  "webserver",
188
190
  message="Select web server",
189
191
  choices=[
@@ -192,24 +194,24 @@ class InteractiveMode:
192
194
  ],
193
195
  default="nginx",
194
196
  ),
195
- inquirer.Text(
197
+ prompts.Text(
196
198
  "branch",
197
199
  message="Git branch (leave empty for default)",
198
200
  default="",
199
201
  ),
200
- inquirer.Confirm(
202
+ prompts.Confirm(
201
203
  "ssl",
202
204
  message="Configure SSL certificate?",
203
205
  default=True,
204
206
  ),
205
- inquirer.Text(
207
+ prompts.Text(
206
208
  "env_file",
207
209
  message="Path to environment file (leave empty to skip)",
208
210
  default="",
209
211
  ),
210
212
  ]
211
213
 
212
- answers = inquirer.prompt(questions, theme=GreenPassion())
214
+ answers = prompts.prompt(questions)
213
215
  if not answers:
214
216
  return 0
215
217
 
@@ -219,13 +221,13 @@ class InteractiveMode:
219
221
  include_www = False
220
222
  if answers["ssl"] and should_include_www(answers["domain"]):
221
223
  www_questions = [
222
- inquirer.Confirm(
224
+ prompts.Confirm(
223
225
  "include_www",
224
226
  message=f"Include www.{answers['domain']} in certificate and web server config?",
225
227
  default=True,
226
228
  ),
227
229
  ]
228
- www_answers = inquirer.prompt(www_questions, theme=GreenPassion())
230
+ www_answers = prompts.prompt(www_questions)
229
231
  include_www = www_answers.get("include_www", True) if www_answers else False
230
232
 
231
233
  # Build arguments
@@ -252,20 +254,20 @@ class InteractiveMode:
252
254
  def _webapp_logs(self, domain: str) -> int:
253
255
  """Handle webapp logs flow with interactive prompts."""
254
256
  questions = [
255
- inquirer.Text(
257
+ prompts.Text(
256
258
  "lines",
257
259
  message="Number of log lines to show",
258
260
  default="50",
259
261
  validate=lambda _, x: (x.isdigit() and int(x) > 0) or "Must be a positive number",
260
262
  ),
261
- inquirer.Confirm(
263
+ prompts.Confirm(
262
264
  "follow",
263
265
  message="Follow log output in real time?",
264
266
  default=False,
265
267
  ),
266
268
  ]
267
269
 
268
- answers = inquirer.prompt(questions, theme=GreenPassion())
270
+ answers = prompts.prompt(questions)
269
271
  if not answers:
270
272
  return 0
271
273
 
@@ -279,14 +281,14 @@ class InteractiveMode:
279
281
  def _webapp_update(self, domain: str) -> int:
280
282
  """Handle webapp update flow with interactive prompts."""
281
283
  questions = [
282
- inquirer.Text(
284
+ prompts.Text(
283
285
  "branch",
284
286
  message="Git branch to update from (leave empty for default)",
285
287
  default="",
286
288
  ),
287
289
  ]
288
290
 
289
- answers = inquirer.prompt(questions, theme=GreenPassion())
291
+ answers = prompts.prompt(questions)
290
292
  if not answers:
291
293
  return 0
292
294
 
@@ -299,7 +301,7 @@ class InteractiveMode:
299
301
  def _site_flow(self) -> int:
300
302
  """Handle site interactive flow."""
301
303
  questions = [
302
- inquirer.List(
304
+ prompts.List(
303
305
  "action",
304
306
  message="What action would you like to perform?",
305
307
  choices=[
@@ -313,7 +315,7 @@ class InteractiveMode:
313
315
  ),
314
316
  ]
315
317
 
316
- answers = inquirer.prompt(questions, theme=GreenPassion())
318
+ answers = prompts.prompt(questions)
317
319
  if not answers:
318
320
  return 0
319
321
 
@@ -334,12 +336,12 @@ class InteractiveMode:
334
336
  def _site_create(self) -> int:
335
337
  """Handle site create flow."""
336
338
  questions = [
337
- inquirer.Text(
339
+ prompts.Text(
338
340
  "domain",
339
341
  message="Enter domain name",
340
342
  validate=lambda _, x: check_domain(x) or "Invalid domain name",
341
343
  ),
342
- inquirer.List(
344
+ prompts.List(
343
345
  "webserver",
344
346
  message="Select web server",
345
347
  choices=[
@@ -347,7 +349,7 @@ class InteractiveMode:
347
349
  ("Apache", "apache"),
348
350
  ],
349
351
  ),
350
- inquirer.List(
352
+ prompts.List(
351
353
  "template",
352
354
  message="Select configuration template",
353
355
  choices=[
@@ -355,20 +357,20 @@ class InteractiveMode:
355
357
  ("Static Site", "static"),
356
358
  ],
357
359
  ),
358
- inquirer.Text(
360
+ prompts.Text(
359
361
  "port",
360
362
  message="Backend port (for proxy)",
361
363
  default="3000",
362
364
  validate=lambda _, x: check_port(x) or "Invalid port",
363
365
  ),
364
- inquirer.Confirm(
366
+ prompts.Confirm(
365
367
  "ssl",
366
368
  message="Configure SSL certificate?",
367
369
  default=True,
368
370
  ),
369
371
  ]
370
372
 
371
- answers = inquirer.prompt(questions, theme=GreenPassion())
373
+ answers = prompts.prompt(questions)
372
374
  if not answers:
373
375
  return 0
374
376
 
@@ -378,13 +380,13 @@ class InteractiveMode:
378
380
  include_www = False
379
381
  if answers["ssl"] and should_include_www(answers["domain"]):
380
382
  www_questions = [
381
- inquirer.Confirm(
383
+ prompts.Confirm(
382
384
  "include_www",
383
385
  message=f"Include www.{answers['domain']} in certificate and web server config?",
384
386
  default=True,
385
387
  ),
386
388
  ]
387
- www_answers = inquirer.prompt(www_questions, theme=GreenPassion())
389
+ www_answers = prompts.prompt(www_questions)
388
390
  include_www = www_answers.get("include_www", True) if www_answers else False
389
391
 
390
392
  from argparse import Namespace
@@ -407,7 +409,7 @@ class InteractiveMode:
407
409
  def _service_flow(self) -> int:
408
410
  """Handle service interactive flow."""
409
411
  questions = [
410
- inquirer.List(
412
+ prompts.List(
411
413
  "action",
412
414
  message="What action would you like to perform?",
413
415
  choices=[
@@ -423,7 +425,7 @@ class InteractiveMode:
423
425
  ),
424
426
  ]
425
427
 
426
- answers = inquirer.prompt(questions, theme=GreenPassion())
428
+ answers = prompts.prompt(questions)
427
429
  if not answers:
428
430
  return 0
429
431
 
@@ -447,34 +449,34 @@ class InteractiveMode:
447
449
  def _service_create(self) -> int:
448
450
  """Handle service create flow."""
449
451
  questions = [
450
- inquirer.Text(
452
+ prompts.Text(
451
453
  "name",
452
454
  message="Enter service name",
453
455
  validate=lambda _, x: len(x) > 0 or "Name required",
454
456
  ),
455
- inquirer.Text(
457
+ prompts.Text(
456
458
  "command",
457
459
  message="Enter command to run",
458
460
  validate=lambda _, x: len(x) > 0 or "Command required",
459
461
  ),
460
- inquirer.Text(
462
+ prompts.Text(
461
463
  "directory",
462
464
  message="Enter working directory",
463
465
  validate=lambda _, x: len(x) > 0 or "Directory required",
464
466
  ),
465
- inquirer.Text(
467
+ prompts.Text(
466
468
  "user",
467
469
  message="User to run as",
468
470
  default="www-data",
469
471
  ),
470
- inquirer.Text(
472
+ prompts.Text(
471
473
  "description",
472
474
  message="Service description",
473
475
  default="",
474
476
  ),
475
477
  ]
476
478
 
477
- answers = inquirer.prompt(questions, theme=GreenPassion())
479
+ answers = prompts.prompt(questions)
478
480
  if not answers:
479
481
  return 0
480
482
 
@@ -497,20 +499,20 @@ class InteractiveMode:
497
499
  def _service_logs(self, name: str) -> int:
498
500
  """Handle service logs flow with interactive prompts."""
499
501
  questions = [
500
- inquirer.Text(
502
+ prompts.Text(
501
503
  "lines",
502
504
  message="Number of log lines to show",
503
505
  default="50",
504
506
  validate=lambda _, x: (x.isdigit() and int(x) > 0) or "Must be a positive number",
505
507
  ),
506
- inquirer.Confirm(
508
+ prompts.Confirm(
507
509
  "follow",
508
510
  message="Follow log output in real time?",
509
511
  default=False,
510
512
  ),
511
513
  ]
512
514
 
513
- answers = inquirer.prompt(questions, theme=GreenPassion())
515
+ answers = prompts.prompt(questions)
514
516
  if not answers:
515
517
  return 0
516
518
 
@@ -525,7 +527,7 @@ class InteractiveMode:
525
527
  def _cert_flow(self) -> int:
526
528
  """Handle cert interactive flow."""
527
529
  questions = [
528
- inquirer.List(
530
+ prompts.List(
529
531
  "action",
530
532
  message="What action would you like to perform?",
531
533
  choices=[
@@ -539,7 +541,7 @@ class InteractiveMode:
539
541
  ),
540
542
  ]
541
543
 
542
- answers = inquirer.prompt(questions, theme=GreenPassion())
544
+ answers = prompts.prompt(questions)
543
545
  if not answers:
544
546
  return 0
545
547
 
@@ -564,22 +566,22 @@ class InteractiveMode:
564
566
  def _cert_create(self) -> int:
565
567
  """Handle cert create flow."""
566
568
  questions = [
567
- inquirer.Text(
569
+ prompts.Text(
568
570
  "domain",
569
571
  message="Enter primary domain",
570
572
  validate=lambda _, x: check_domain(x) or "Invalid domain",
571
573
  ),
572
- inquirer.Text(
574
+ prompts.Text(
573
575
  "additional",
574
576
  message="Additional domains (comma separated, or leave empty)",
575
577
  default="",
576
578
  ),
577
- inquirer.Text(
579
+ prompts.Text(
578
580
  "email",
579
581
  message="Email for registration (leave empty for default)",
580
582
  default="",
581
583
  ),
582
- inquirer.List(
584
+ prompts.List(
583
585
  "method",
584
586
  message="Certificate obtention method",
585
587
  choices=[
@@ -589,14 +591,14 @@ class InteractiveMode:
589
591
  ("Webroot", "webroot"),
590
592
  ],
591
593
  ),
592
- inquirer.Confirm(
594
+ prompts.Confirm(
593
595
  "dry_run",
594
596
  message="Dry run (test without obtaining)?",
595
597
  default=False,
596
598
  ),
597
599
  ]
598
600
 
599
- answers = inquirer.prompt(questions, theme=GreenPassion())
601
+ answers = prompts.prompt(questions)
600
602
  if not answers:
601
603
  return 0
602
604
 
@@ -631,7 +633,7 @@ class InteractiveMode:
631
633
  def _cert_renew(self) -> int:
632
634
  """Handle cert renew flow."""
633
635
  questions = [
634
- inquirer.List(
636
+ prompts.List(
635
637
  "scope",
636
638
  message="What to renew?",
637
639
  choices=[
@@ -639,19 +641,19 @@ class InteractiveMode:
639
641
  ("Specific certificate", "specific"),
640
642
  ],
641
643
  ),
642
- inquirer.Confirm(
644
+ prompts.Confirm(
643
645
  "force",
644
646
  message="Force renewal?",
645
647
  default=False,
646
648
  ),
647
- inquirer.Confirm(
649
+ prompts.Confirm(
648
650
  "dry_run",
649
651
  message="Dry run?",
650
652
  default=False,
651
653
  ),
652
654
  ]
653
655
 
654
- answers = inquirer.prompt(questions, theme=GreenPassion())
656
+ answers = prompts.prompt(questions)
655
657
  if not answers:
656
658
  return 0
657
659
 
@@ -676,25 +678,25 @@ class InteractiveMode:
676
678
  def _prompt_domain(self, message: str) -> str:
677
679
  """Prompt for a domain name."""
678
680
  questions = [
679
- inquirer.Text(
681
+ prompts.Text(
680
682
  "domain",
681
683
  message=message,
682
684
  validate=lambda _, x: check_domain(x) or "Invalid domain",
683
685
  ),
684
686
  ]
685
- answers = inquirer.prompt(questions, theme=GreenPassion())
687
+ answers = prompts.prompt(questions)
686
688
  return answers["domain"] if answers else None
687
689
 
688
690
  def _prompt_text(self, message: str, default: str = "") -> str:
689
691
  """Prompt for text input."""
690
692
  questions = [
691
- inquirer.Text(
693
+ prompts.Text(
692
694
  "value",
693
695
  message=message,
694
696
  default=default,
695
697
  ),
696
698
  ]
697
- answers = inquirer.prompt(questions, theme=GreenPassion())
699
+ answers = prompts.prompt(questions)
698
700
  return answers["value"] if answers else default
699
701
 
700
702
  def _run_command(self, resource: str, action: str, target: str | None = None, **kwargs) -> int: