splent-cli 1.5.1__tar.gz → 1.7.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 (176) hide show
  1. {splent_cli-1.5.1/src/splent_cli.egg-info → splent_cli-1.7.0}/PKG-INFO +4 -17
  2. splent_cli-1.7.0/README.md +23 -0
  3. {splent_cli-1.5.1 → splent_cli-1.7.0}/pyproject.toml +1 -1
  4. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/cli.py +18 -10
  5. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_clear.py +4 -1
  6. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_outdated.py +1 -1
  7. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_github.py +8 -4
  8. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_infra.py +110 -0
  9. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_product.py +1 -1
  10. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_pypi.py +7 -3
  11. splent_cli-1.5.1/src/splent_cli/commands/clear_cache.py → splent_cli-1.7.0/src/splent_cli/commands/clear/clear_build.py +2 -2
  12. {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/clear}/clear_log.py +1 -1
  13. {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/clear}/clear_uploads.py +1 -1
  14. {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/command}/command_create.py +1 -1
  15. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/coverage.py +1 -1
  16. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_console.py +1 -1
  17. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_dump.py +1 -1
  18. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_migrate.py +1 -1
  19. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_reset.py +5 -1
  20. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_upgrade.py +5 -0
  21. splent_cli-1.7.0/src/splent_cli/commands/doctor.py +142 -0
  22. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_list.py +4 -1
  23. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_show.py +1 -1
  24. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_add.py +20 -1
  25. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_attach.py +22 -1
  26. splent_cli-1.7.0/src/splent_cli/commands/feature/feature_clean.py +365 -0
  27. splent_cli-1.5.1/src/splent_cli/commands/feature/feature_diff.py → splent_cli-1.7.0/src/splent_cli/commands/feature/feature_compat.py +95 -214
  28. {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/feature}/feature_compile.py +2 -1
  29. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_contract.py +31 -0
  30. splent_cli-1.7.0/src/splent_cli/commands/feature/feature_create.py +275 -0
  31. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_delete.py +1 -1
  32. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_detach.py +1 -1
  33. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_discard.py +1 -1
  34. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_drift.py +1 -1
  35. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_env.py +13 -15
  36. splent_cli-1.7.0/src/splent_cli/commands/feature/feature_impact.py +161 -0
  37. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_inject_config.py +1 -1
  38. splent_cli-1.7.0/src/splent_cli/commands/feature/feature_install.py +425 -0
  39. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_list.py +2 -1
  40. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_outdated.py +2 -2
  41. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pin.py +1 -1
  42. splent_cli-1.5.1/src/splent_cli/commands/feature/feature_refinement.py → splent_cli-1.7.0/src/splent_cli/commands/feature/feature_refine.py +6 -6
  43. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_release.py +106 -1
  44. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_remove.py +1 -1
  45. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_rename.py +1 -1
  46. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_status.py +1 -1
  47. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_test.py +48 -0
  48. splent_cli-1.5.1/src/splent_cli/commands/feature/feature_edit.py → splent_cli-1.7.0/src/splent_cli/commands/feature/feature_unlock.py +12 -1
  49. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_upgrade.py +1 -1
  50. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_xray.py +4 -1
  51. splent_cli-1.5.1/src/splent_cli/commands/linter.py → splent_cli-1.7.0/src/splent_cli/commands/lint.py +1 -3
  52. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/locust.py +2 -4
  53. splent_cli-1.5.1/src/splent_cli/commands/product/product_complete.py → splent_cli-1.7.0/src/splent_cli/commands/product/product_auto_require.py +2 -2
  54. splent_cli-1.7.0/src/splent_cli/commands/product/product_build.py +526 -0
  55. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_clean.py +1 -1
  56. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_config.py +1 -1
  57. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_configure.py +68 -5
  58. splent_cli-1.7.0/src/splent_cli/commands/product/product_containers.py +316 -0
  59. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_create.py +1 -1
  60. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_deploy.py +62 -6
  61. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_derive.py +4 -4
  62. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_env.py +33 -0
  63. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_missing.py +1 -1
  64. splent_cli-1.5.1/src/splent_cli/commands/product/product_sync.py → splent_cli-1.7.0/src/splent_cli/commands/product/product_resolve.py +3 -3
  65. splent_cli-1.7.0/src/splent_cli/commands/product/product_restart.py +284 -0
  66. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_select.py +1 -1
  67. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_test.py +14 -0
  68. splent_cli-1.7.0/src/splent_cli/commands/product/product_up.py +269 -0
  69. splent_cli-1.7.0/src/splent_cli/commands/product/product_validate.py +371 -0
  70. splent_cli-1.7.0/src/splent_cli/commands/spl/__init__.py +0 -0
  71. splent_cli-1.5.1/src/splent_cli/commands/spl/spl_fix.py → splent_cli-1.7.0/src/splent_cli/commands/spl/spl_add_constraints.py +3 -3
  72. splent_cli-1.5.1/src/splent_cli/commands/spl/spl_configs.py → splent_cli-1.7.0/src/splent_cli/commands/spl/spl_configurations.py +2 -2
  73. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_features.py +1 -1
  74. splent_cli-1.5.1/src/splent_cli/commands/tokens.py → splent_cli-1.7.0/src/splent_cli/commands/tokens_setup.py +2 -2
  75. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/version.py +4 -1
  76. splent_cli-1.7.0/src/splent_cli/services/__init__.py +0 -0
  77. splent_cli-1.7.0/src/splent_cli/services/preflight.py +157 -0
  78. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/services/release.py +2 -2
  79. splent_cli-1.7.0/src/splent_cli/utils/__init__.py +0 -0
  80. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/integrity.py +2 -2
  81. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/lifecycle.py +3 -3
  82. {splent_cli-1.5.1 → splent_cli-1.7.0/src/splent_cli.egg-info}/PKG-INFO +4 -17
  83. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/SOURCES.txt +22 -16
  84. splent_cli-1.5.1/README.md +0 -36
  85. splent_cli-1.5.1/src/splent_cli/commands/doctor.py +0 -140
  86. splent_cli-1.5.1/src/splent_cli/commands/feature/feature_create.py +0 -218
  87. splent_cli-1.5.1/src/splent_cli/commands/product/product_build.py +0 -270
  88. splent_cli-1.5.1/src/splent_cli/commands/product/product_restart.py +0 -114
  89. splent_cli-1.5.1/src/splent_cli/commands/product/product_status.py +0 -196
  90. splent_cli-1.5.1/src/splent_cli/commands/product/product_up.py +0 -105
  91. splent_cli-1.5.1/src/splent_cli/commands/product/product_validate.py +0 -157
  92. splent_cli-1.5.1/src/splent_cli/services/preflight.py +0 -70
  93. {splent_cli-1.5.1 → splent_cli-1.7.0}/LICENSE +0 -0
  94. {splent_cli-1.5.1 → splent_cli-1.7.0}/setup.cfg +0 -0
  95. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/__init__.py +0 -0
  96. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/__main__.py +0 -0
  97. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/__init__.py +0 -0
  98. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/__init__.py +0 -0
  99. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
  100. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  101. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
  102. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
  103. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  104. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  105. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/__init__.py +0 -0
  106. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_deps.py +0 -0
  107. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_docker.py +0 -0
  108. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_env.py +0 -0
  109. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_features.py +0 -0
  110. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
  111. {splent_cli-1.5.1/src/splent_cli/commands/product → splent_cli-1.7.0/src/splent_cli/commands/clear}/__init__.py +0 -0
  112. {splent_cli-1.5.1/src/splent_cli/commands/release → splent_cli-1.7.0/src/splent_cli/commands/command}/__init__.py +0 -0
  113. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_restore.py +0 -0
  114. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
  115. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_seed.py +0 -0
  116. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/database/db_status.py +0 -0
  117. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/env/env_set.py +0 -0
  118. {splent_cli-1.5.1/src/splent_cli/commands/spl → splent_cli-1.7.0/src/splent_cli/commands/export}/__init__.py +0 -0
  119. {splent_cli-1.5.1/src/splent_cli/commands → splent_cli-1.7.0/src/splent_cli/commands/export}/export_puml.py +0 -0
  120. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
  121. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  122. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
  123. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
  124. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
  125. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
  126. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
  127. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
  128. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  129. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
  130. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
  131. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
  132. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
  133. {splent_cli-1.5.1/src/splent_cli/services → splent_cli-1.7.0/src/splent_cli/commands/product}/__init__.py +0 -0
  134. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_commands.py +0 -0
  135. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_console.py +0 -0
  136. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
  137. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_down.py +0 -0
  138. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_drift.py +0 -0
  139. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_list.py +0 -0
  140. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_logs.py +0 -0
  141. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_port.py +0 -0
  142. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_release.py +0 -0
  143. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_routes.py +0 -0
  144. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_run.py +0 -0
  145. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_shell.py +0 -0
  146. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_signals.py +0 -0
  147. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
  148. {splent_cli-1.5.1/src/splent_cli/utils → splent_cli-1.7.0/src/splent_cli/commands/release}/__init__.py +0 -0
  149. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/release/release_core.py +0 -0
  150. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/selenium.py +0 -0
  151. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
  152. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
  153. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
  154. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
  155. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
  156. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
  157. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
  158. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
  159. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/services/compose.py +0 -0
  160. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/services/context.py +0 -0
  161. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/cache_utils.py +0 -0
  162. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/command_loader.py +0 -0
  163. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/contract_freshness.py +0 -0
  164. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/db_utils.py +0 -0
  165. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/decorators.py +0 -0
  166. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
  167. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/feature_installer.py +0 -0
  168. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/feature_utils.py +0 -0
  169. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/git_url.py +0 -0
  170. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/manifest.py +0 -0
  171. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/path_utils.py +0 -0
  172. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli/utils/template_drift.py +0 -0
  173. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  174. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
  175. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/requires.txt +0 -0
  176. {splent_cli-1.5.1 → splent_cli-1.7.0}/src/splent_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.5.1
3
+ Version: 1.7.0
4
4
  Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
@@ -47,23 +47,10 @@ Command-line tool for managing SPLENT products, features, databases, and environ
47
47
  ## Quick start
48
48
 
49
49
  ```bash
50
- make setup # Prepare .env, start Docker, enter CLI container
51
- splent --help # See all available commands
50
+ make setup # Prepare .env, start Docker, enter CLI container
51
+ splent --help # See all available commands
52
52
  ```
53
53
 
54
- ## Key commands
55
-
56
- | Command | Description |
57
- |---------|-------------|
58
- | `product:create` | Create a new product |
59
- | `product:derive --dev` | Full SPL derivation pipeline |
60
- | `feature:add` / `feature:attach` | Add features to a product |
61
- | `feature:status` | Show feature lifecycle states |
62
- | `feature:release` | Release a feature (tag + PyPI + GitHub) |
63
- | `db:migrate` / `db:upgrade` | Manage per-feature migrations |
64
- | `export:puml` | Generate PlantUML diagrams |
65
- | `doctor` | System health check |
66
-
67
54
  ## Requirements
68
55
 
69
56
  - Docker + Docker Compose
@@ -71,7 +58,7 @@ splent --help # See all available commands
71
58
 
72
59
  ## Documentation
73
60
 
74
- Full documentation at **[docs.splent.io](https://docs.splent.io)**
61
+ Full docs, tutorials, and command reference at **[docs.splent.io](https://docs.splent.io)**
75
62
 
76
63
  ## License
77
64
 
@@ -0,0 +1,23 @@
1
+ # SPLENT CLI
2
+
3
+ Command-line tool for managing SPLENT products, features, databases, and environments.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ make setup # Prepare .env, start Docker, enter CLI container
9
+ splent --help # See all available commands
10
+ ```
11
+
12
+ ## Requirements
13
+
14
+ - Docker + Docker Compose
15
+ - Python 3.13+
16
+
17
+ ## Documentation
18
+
19
+ Full docs, tutorials, and command reference at **[docs.splent.io](https://docs.splent.io)**
20
+
21
+ ## License
22
+
23
+ Creative Commons CC BY 4.0 - SPLENT - Diverso Lab
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.5.1"
7
+ version = "1.7.0"
8
8
  description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -44,9 +44,13 @@ class SPLENTCLI(click.Group):
44
44
  with app.app_context():
45
45
  registry = app.extensions.get("splent_feature_commands", {})
46
46
  for feature_short, commands in registry.items():
47
+ cmd_names = ", ".join(c.name for c in commands if c.name)
47
48
  group = click.Group(
48
49
  name=f"feature:{feature_short}",
49
50
  help=f"Commands contributed by splent_feature_{feature_short}.",
51
+ short_help=f"Subcommands: {cmd_names}"
52
+ if cmd_names
53
+ else f"Commands from splent_feature_{feature_short}.",
50
54
  )
51
55
  group.requires_app = True # type: ignore[attr-defined]
52
56
  for cmd in commands:
@@ -92,12 +96,12 @@ class SPLENTCLI(click.Group):
92
96
  def format_commands(self, ctx, formatter):
93
97
  """Group SPLENT commands by category in the CLI help output."""
94
98
  all_cmds = self.list_commands(ctx)
99
+ feat_cmds = self._load_feature_commands()
95
100
  groups = {
96
101
  "🌿 Feature Management": [
97
102
  cmd
98
103
  for cmd in all_cmds
99
- if cmd.startswith("feature:")
100
- and cmd not in self._load_feature_commands()
104
+ if cmd.startswith("feature:") and cmd not in feat_cmds
101
105
  ],
102
106
  "🏗️ Product Management": [
103
107
  cmd for cmd in all_cmds if cmd.startswith("product:")
@@ -107,27 +111,28 @@ class SPLENTCLI(click.Group):
107
111
  ],
108
112
  "🧱 Database": [cmd for cmd in all_cmds if cmd.startswith("db:")],
109
113
  "💾 Cache": [cmd for cmd in all_cmds if cmd.startswith("cache:")],
114
+ "🔍 Checks": [cmd for cmd in all_cmds if cmd.startswith("check:")],
115
+ "📦 Export": [cmd for cmd in all_cmds if cmd.startswith("export:")],
116
+ "🚀 Release": [cmd for cmd in all_cmds if cmd.startswith("release:")],
110
117
  "🧰 Utilities": [
111
118
  cmd
112
119
  for cmd in all_cmds
113
- if cmd.startswith(
114
- ("clear:", "env", "select", "info", "version", "doctor", "tokens")
115
- )
120
+ if cmd.startswith(("clear:", "command:", "env:", "env"))
121
+ or cmd in ("doctor", "tokens:setup", "version")
116
122
  ],
117
123
  "🐍 Development & QA": [
118
124
  cmd
119
125
  for cmd in all_cmds
120
- if cmd.startswith(("linter", "test", "coverage", "locust"))
121
- ],
122
- "🔌 Feature Commands": [
123
- cmd for cmd in all_cmds if cmd in self._load_feature_commands()
126
+ if cmd in ("lint", "coverage", "selenium", "locust", "locust:stop")
124
127
  ],
128
+ "🔌 Feature Commands": [cmd for cmd in all_cmds if cmd in feat_cmds],
125
129
  }
130
+ total = 0
126
131
  for title, cmds in groups.items():
127
132
  if not cmds:
128
133
  continue
129
134
 
130
- with formatter.section(title):
135
+ with formatter.section(f"{title} ({len(cmds)})"):
131
136
  rows = []
132
137
  for cmd_name in sorted(cmds):
133
138
  cmd = self.get_command(ctx, cmd_name)
@@ -136,6 +141,9 @@ class SPLENTCLI(click.Group):
136
141
  rows.append((cmd_name, cmd.get_short_help_str()))
137
142
  if rows:
138
143
  formatter.write_dl(rows)
144
+ total += len(cmds)
145
+
146
+ formatter.write(f"\n {total} commands available.\n")
139
147
 
140
148
 
141
149
  @click.group(cls=SPLENTCLI)
@@ -4,7 +4,10 @@ import click
4
4
  from pathlib import Path
5
5
 
6
6
 
7
- @click.command("cache:clear", short_help="Clear the feature cache (total or partial).")
7
+ @click.command(
8
+ "cache:clear",
9
+ short_help="Clear the feature version cache (.splent_cache), totally or partially.",
10
+ )
8
11
  @click.option(
9
12
  "--namespace",
10
13
  default=None,
@@ -68,7 +68,7 @@ def _get_product_features(workspace: Path) -> dict:
68
68
 
69
69
  @click.command(
70
70
  "cache:outdated",
71
- short_help="Show products using older versions than what's in cache.",
71
+ short_help="Show products using older feature versions than what is in cache.",
72
72
  )
73
73
  def cache_outdated():
74
74
  """
@@ -18,13 +18,17 @@ def check_github():
18
18
  # --- presence checks ---
19
19
  if not user:
20
20
  click.echo(click.style("[✖] ", fg="red") + "GITHUB_USER not set in .env")
21
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
21
+ click.secho(
22
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
23
+ )
22
24
  raise SystemExit(1)
23
25
  click.echo(click.style("[✔] ", fg="green") + f"GITHUB_USER = {user}")
24
26
 
25
27
  if not token:
26
28
  click.echo(click.style("[✖] ", fg="red") + "GITHUB_TOKEN not set in .env")
27
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
29
+ click.secho(
30
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
31
+ )
28
32
  raise SystemExit(1)
29
33
 
30
34
  masked = token[:4] + "*" * (len(token) - 8) + token[-4:]
@@ -54,7 +58,7 @@ def check_github():
54
58
  + "Token invalid or expired (401 Unauthorized)"
55
59
  )
56
60
  click.secho(
57
- " Run 'splent tokens' for instructions on obtaining a valid token.",
61
+ " Run 'splent tokens:setup' for instructions on obtaining a valid token.",
58
62
  fg="bright_black",
59
63
  )
60
64
  elif e.code == 403:
@@ -63,7 +67,7 @@ def check_github():
63
67
  + "Token lacks required permissions (403 Forbidden)"
64
68
  )
65
69
  click.secho(
66
- " Run 'splent tokens' — ensure the token has 'repo' scope.",
70
+ " Run 'splent tokens:setup' — ensure the token has 'repo' scope.",
67
71
  fg="bright_black",
68
72
  )
69
73
  else:
@@ -220,6 +220,116 @@ def check_infra():
220
220
  else:
221
221
  _ok("No external networks required")
222
222
 
223
+ # --- Check 4: Dockerfile build contexts ---
224
+ click.echo()
225
+ click.echo(click.style(" Build contexts", bold=True))
226
+ build_count = 0
227
+ for label, cf in compose_files:
228
+ docker_dir = os.path.dirname(cf)
229
+ result = subprocess.run(
230
+ ["docker", "compose", "-f", cf, "config", "--format", "json"],
231
+ capture_output=True,
232
+ text=True,
233
+ )
234
+ if result.returncode != 0:
235
+ continue
236
+ try:
237
+ config = json.loads(result.stdout)
238
+ except json.JSONDecodeError:
239
+ continue
240
+ for svc_name, svc_def in config.get("services", {}).items():
241
+ build_cfg = svc_def.get("build")
242
+ if not build_cfg:
243
+ continue
244
+ build_count += 1
245
+ if isinstance(build_cfg, dict):
246
+ ctx = build_cfg.get("context", ".")
247
+ dockerfile = build_cfg.get("dockerfile", "Dockerfile")
248
+ df_path = (
249
+ os.path.join(ctx, dockerfile)
250
+ if os.path.isabs(ctx)
251
+ else os.path.join(docker_dir, ctx, dockerfile)
252
+ )
253
+ else:
254
+ df_path = os.path.join(docker_dir, build_cfg, "Dockerfile")
255
+
256
+ if os.path.isfile(df_path):
257
+ _ok(f"{label}/{svc_name}: Dockerfile found")
258
+ else:
259
+ _fail(f"{label}/{svc_name}: Dockerfile not found at {df_path}")
260
+
261
+ if build_count == 0:
262
+ _ok("No custom builds (all services use pre-built images)")
263
+
264
+ # --- Check 5: Healthcheck coverage ---
265
+ click.echo()
266
+ click.echo(click.style(" Health checks", bold=True))
267
+ services_with_hc: set[str] = set()
268
+ services_depended_on: dict[str, str] = {} # depended_svc -> by_svc
269
+
270
+ for label, cf in compose_files:
271
+ result = subprocess.run(
272
+ ["docker", "compose", "-f", cf, "config", "--format", "json"],
273
+ capture_output=True,
274
+ text=True,
275
+ )
276
+ if result.returncode != 0:
277
+ continue
278
+ try:
279
+ config = json.loads(result.stdout)
280
+ except json.JSONDecodeError:
281
+ continue
282
+ for svc_name, svc_def in config.get("services", {}).items():
283
+ if "healthcheck" in svc_def:
284
+ services_with_hc.add(svc_name)
285
+ for dep_svc, dep_cfg in (svc_def.get("depends_on") or {}).items():
286
+ if (
287
+ isinstance(dep_cfg, dict)
288
+ and dep_cfg.get("condition") == "service_healthy"
289
+ ):
290
+ services_depended_on[dep_svc] = svc_name
291
+
292
+ missing_hc = {
293
+ svc: by
294
+ for svc, by in services_depended_on.items()
295
+ if svc not in services_with_hc
296
+ }
297
+ if missing_hc:
298
+ for svc, by in sorted(missing_hc.items()):
299
+ _fail(
300
+ f"'{by}' depends on '{svc}' being healthy, but '{svc}' has no healthcheck"
301
+ )
302
+ elif services_with_hc:
303
+ _ok(f"{len(services_with_hc)} service(s) with health checks")
304
+ else:
305
+ _ok("No health checks declared (none required)")
306
+
307
+ # --- Check 6: Volume name conflicts ---
308
+ click.echo()
309
+ click.echo(click.style(" Volumes", bold=True))
310
+ all_volumes: dict[str, list[str]] = {} # vol_name -> [labels]
311
+ for label, cf in compose_files:
312
+ result = subprocess.run(
313
+ ["docker", "compose", "-f", cf, "config", "--format", "json"],
314
+ capture_output=True,
315
+ text=True,
316
+ )
317
+ if result.returncode != 0:
318
+ continue
319
+ try:
320
+ config = json.loads(result.stdout)
321
+ except json.JSONDecodeError:
322
+ continue
323
+ for vol_name in config.get("volumes", {}):
324
+ all_volumes.setdefault(vol_name, []).append(label)
325
+
326
+ vol_conflicts = {v: srcs for v, srcs in all_volumes.items() if len(srcs) > 1}
327
+ if vol_conflicts:
328
+ for vol, sources in sorted(vol_conflicts.items()):
329
+ _warn(f"Volume '{vol}' declared by multiple features: {', '.join(sources)}")
330
+ else:
331
+ _ok(f"No volume name conflicts ({len(all_volumes)} volumes)")
332
+
223
333
  # --- Summary ---
224
334
  click.echo()
225
335
  if fail:
@@ -291,7 +291,7 @@ def _check_blueprints(counters):
291
291
 
292
292
  @click.command(
293
293
  "check:product",
294
- short_help="Validate product health: env vars, symlinks, config.",
294
+ short_help="Quick health check: env vars, symlinks, and config files.",
295
295
  )
296
296
  @context.requires_product
297
297
  def check_product():
@@ -26,7 +26,9 @@ def check_pypi(test: bool):
26
26
  # --- presence checks ---
27
27
  if not username:
28
28
  click.echo(click.style("[✖] ", fg="red") + "TWINE_USERNAME not set in .env")
29
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
29
+ click.secho(
30
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
31
+ )
30
32
  raise SystemExit(1)
31
33
  click.echo(click.style("[✔] ", fg="green") + f"TWINE_USERNAME = {username}")
32
34
 
@@ -35,7 +37,9 @@ def check_pypi(test: bool):
35
37
  click.style("[✖] ", fg="red")
36
38
  + "TWINE_PASSWORD (or PYPI_PASSWORD) not set in .env"
37
39
  )
38
- click.secho(" Run 'splent tokens' for setup instructions.", fg="bright_black")
40
+ click.secho(
41
+ " Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
42
+ )
39
43
  raise SystemExit(1)
40
44
 
41
45
  masked = (
@@ -94,7 +98,7 @@ def check_pypi(test: bool):
94
98
  + f"Credentials rejected by {registry} (HTTP {e.code}) — token invalid or expired"
95
99
  )
96
100
  click.secho(
97
- " Run 'splent tokens' for instructions on obtaining a valid token.",
101
+ " Run 'splent tokens:setup' for instructions on obtaining a valid token.",
98
102
  fg="bright_black",
99
103
  )
100
104
  raise SystemExit(1)
@@ -52,8 +52,8 @@ def clean_build_artifacts(target_path: str | Path, *, quiet: bool = False):
52
52
 
53
53
 
54
54
  @click.command(
55
- "clear:pycache",
56
- short_help="Clear __pycache__, .pytest_cache and build artifacts from the workspace.",
55
+ "clear:build",
56
+ short_help="Remove __pycache__, .pytest_cache, dist/, build/, and egg-info.",
57
57
  )
58
58
  def clear_cache():
59
59
  if not click.confirm("Are you sure you want to clear caches and build artifacts?"):
@@ -5,7 +5,7 @@ from splent_cli.utils.path_utils import PathUtils
5
5
  from splent_cli.services import context
6
6
 
7
7
 
8
- @click.command("clear:log", short_help="Clears the 'app.log' file.")
8
+ @click.command("clear:log", short_help="Clear the app.log file.")
9
9
  @context.requires_product
10
10
  def clear_log():
11
11
  log_file_path = PathUtils.get_app_log_dir()
@@ -8,7 +8,7 @@ from splent_cli.services import context
8
8
 
9
9
  @click.command(
10
10
  "clear:uploads",
11
- short_help="Clears the contents of the 'uploads' directory without removing the folder.",
11
+ short_help="Clear the contents of the uploads directory.",
12
12
  )
13
13
  @context.requires_product
14
14
  def clear_uploads():
@@ -5,7 +5,7 @@ import click
5
5
  from splent_cli.utils.path_utils import PathUtils
6
6
 
7
7
 
8
- @click.command("command:create", help="Creates a new CLI command skeleton.")
8
+ @click.command("command:create", short_help="Create a new CLI command skeleton.")
9
9
  @click.argument("name")
10
10
  def command_create(name):
11
11
  commands_dir = PathUtils.get_commands_path()
@@ -8,7 +8,7 @@ from splent_cli.services import context
8
8
 
9
9
  @click.command(
10
10
  "coverage",
11
- short_help="Runs pytest coverage on the selected feature",
11
+ short_help="Run pytest with coverage reporting for a feature.",
12
12
  )
13
13
  @click.argument("module_name", required=False)
14
14
  @click.option("--html", is_flag=True, help="Generates an HTML coverage report.")
@@ -7,7 +7,7 @@ from splent_cli.services import context
7
7
 
8
8
 
9
9
  @click.command(
10
- "db:console", short_help="Opens a MariaDB console with credentials from .env."
10
+ "db:console", short_help="Open a MariaDB console with credentials from .env."
11
11
  )
12
12
  @context.requires_product
13
13
  def db_console():
@@ -9,7 +9,7 @@ from splent_cli.services import context
9
9
 
10
10
  @click.command(
11
11
  "db:dump",
12
- short_help="Creates a dump of the MariaDB database with credentials from .env.",
12
+ short_help="Create a SQL dump of the MariaDB database.",
13
13
  )
14
14
  @click.argument("filename", required=False)
15
15
  @context.requires_product
@@ -48,7 +48,7 @@ def _is_empty_migration(path: str) -> bool:
48
48
  @requires_db
49
49
  @click.command(
50
50
  "db:migrate",
51
- short_help="Generate and apply migrations (all features or a single one).",
51
+ short_help="Generate a new migration from model changes and apply it.",
52
52
  )
53
53
  @click.argument("feature", required=False, default=None)
54
54
  @click.option(
@@ -15,7 +15,7 @@ from splent_framework.managers.migration_manager import (
15
15
  )
16
16
  from splent_framework.utils.feature_utils import get_features_from_pyproject
17
17
  from splent_framework.utils.path_utils import PathUtils
18
- from splent_cli.commands.clear_uploads import clear_uploads
18
+ from splent_cli.commands.clear.clear_uploads import clear_uploads
19
19
 
20
20
 
21
21
  @requires_db
@@ -123,6 +123,10 @@ def db_reset(yes):
123
123
  name=name,
124
124
  version=version,
125
125
  )
126
+ except ImportError as e:
127
+ if "models" in str(e):
128
+ continue
129
+ click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
126
130
  except Exception as e:
127
131
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
128
132
 
@@ -84,6 +84,11 @@ def db_upgrade(feature):
84
84
  name=name,
85
85
  version=version,
86
86
  )
87
+ except ImportError as e:
88
+ if "models" in str(e):
89
+ # Feature has migrations/ dir but no models module — skip silently
90
+ continue
91
+ click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
87
92
  except Exception as e:
88
93
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
89
94
 
@@ -0,0 +1,142 @@
1
+ """
2
+ splent doctor — Run all diagnostic checks on the workspace.
3
+
4
+ Organized in sections: environment, product, services, and credentials.
5
+ """
6
+
7
+ import click
8
+
9
+ from splent_cli.commands.check.check_env import check_env
10
+ from splent_cli.commands.check.check_pyproject import check_pyproject
11
+ from splent_cli.commands.check.check_features import check_features
12
+ from splent_cli.commands.check.check_docker import check_docker
13
+ from splent_cli.commands.check.check_github import check_github
14
+ from splent_cli.commands.check.check_pypi import check_pypi
15
+ from splent_cli.commands.check.check_infra import check_infra
16
+ from splent_cli.commands.check.check_product import check_product
17
+ from splent_cli.commands.feature.feature_status import feature_status
18
+ from splent_cli.commands.database.db_status import db_status
19
+ from splent_cli.commands.product.product_containers import product_docker
20
+
21
+
22
+ SECTIONS = [
23
+ (
24
+ "Environment",
25
+ [
26
+ ("check:env", check_env, False, False),
27
+ ("check:pyproject", check_pyproject, False, False),
28
+ ("check:docker", check_docker, False, False),
29
+ ("check:infra", check_infra, False, False),
30
+ ],
31
+ ),
32
+ (
33
+ "Product",
34
+ [
35
+ ("check:product", check_product, False, False),
36
+ ("check:features", check_features, False, False),
37
+ ("feature:status", feature_status, False, False),
38
+ ],
39
+ ),
40
+ (
41
+ "Services",
42
+ [
43
+ ("product:containers", product_docker, False, False),
44
+ ("db:status", db_status, True, False),
45
+ ],
46
+ ),
47
+ (
48
+ "Credentials",
49
+ [
50
+ ("check:github", check_github, False, True),
51
+ ("check:pypi", check_pypi, False, True),
52
+ ],
53
+ ),
54
+ ]
55
+
56
+
57
+ @click.command(
58
+ "doctor", short_help="Run all diagnostic checks sequentially and report a summary."
59
+ )
60
+ @click.option("--fast", is_flag=True, help="Skip slow checks (network, database).")
61
+ def doctor(fast):
62
+ """
63
+ Run all diagnostic checks on the workspace and report a summary.
64
+
65
+ \b
66
+ Sections:
67
+ Environment — Python, env vars, pyproject, Docker, infrastructure
68
+ Product — product health, feature cache/symlinks, feature status
69
+ Services — containers, database migrations
70
+ Credentials — GitHub and PyPI tokens (skipped with --fast)
71
+
72
+ \b
73
+ Use --fast to skip network checks (GitHub, PyPI) and database checks.
74
+
75
+ \b
76
+ For full product validation (UVL + contracts + imports), run:
77
+ splent product:validate
78
+ """
79
+ click.echo(click.style("\n SPLENT Doctor\n", fg="cyan", bold=True))
80
+
81
+ passed = 0
82
+ failed = 0
83
+ skipped = 0
84
+
85
+ for section_name, checks in SECTIONS:
86
+ click.echo(click.style(f" ── {section_name} ──", fg="cyan", bold=True))
87
+ click.echo()
88
+
89
+ for name, cmd, requires_db, network_only in checks:
90
+ if fast and (network_only or requires_db):
91
+ click.echo(
92
+ click.style(f" {name}", fg="bright_black")
93
+ + click.style(" (skipped)", fg="bright_black")
94
+ )
95
+ skipped += 1
96
+ click.echo()
97
+ continue
98
+
99
+ click.echo(click.style(f" {name}", bold=True))
100
+
101
+ try:
102
+ ctx = click.Context(
103
+ cmd, info_name=name, parent=click.get_current_context()
104
+ )
105
+ if requires_db:
106
+ from splent_cli.utils.dynamic_imports import get_app
107
+
108
+ app = get_app()
109
+ with app.app_context():
110
+ ctx.invoke(cmd)
111
+ else:
112
+ ctx.invoke(cmd)
113
+ passed += 1
114
+ except SystemExit as e:
115
+ if e.code and e.code != 0:
116
+ failed += 1
117
+ else:
118
+ passed += 1
119
+ except Exception as e:
120
+ click.secho(f" Unexpected error: {e}", fg="red")
121
+ failed += 1
122
+
123
+ click.echo()
124
+
125
+ # Summary
126
+ click.echo(click.style(" ── Summary ──", fg="cyan", bold=True))
127
+ click.echo(f" Passed: {passed}")
128
+ if skipped:
129
+ click.echo(f" Skipped: {skipped}")
130
+ if failed:
131
+ click.echo(f" Failed: {failed}")
132
+ click.echo()
133
+ click.secho(" Some checks failed. Review the output above.", fg="red")
134
+ raise SystemExit(1)
135
+ else:
136
+ click.echo()
137
+ click.secho(" All checks passed.", fg="green")
138
+
139
+ click.echo()
140
+
141
+
142
+ cli_command = doctor
@@ -51,7 +51,10 @@ def _read_env_file(path: Path) -> dict[str, str]:
51
51
  return result
52
52
 
53
53
 
54
- @click.command("env:list", short_help="List variables in the active .env file.")
54
+ @click.command(
55
+ "env:list",
56
+ short_help="List all variables in the workspace .env file (grouped, with masking).",
57
+ )
55
58
  @click.argument("filter", required=False, metavar="FILTER")
56
59
  @click.option(
57
60
  "--keys-only", is_flag=True, help="Print only variable names, one per line."
@@ -12,7 +12,7 @@ def _mask(value: str, key: str) -> str:
12
12
 
13
13
  @click.command(
14
14
  "env:show",
15
- short_help="Display .env variables and check which ones are currently loaded in the shell.",
15
+ short_help="Compare .env file values against what is loaded in the shell.",
16
16
  )
17
17
  def env_show():
18
18
  """Check which variables from .env are actually available in the current Bash shell."""