envctl 2.3.3__tar.gz → 2.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. envctl-2.4.0/PKG-INFO +359 -0
  2. envctl-2.4.0/README.md +317 -0
  3. {envctl-2.3.3 → envctl-2.4.0}/pyproject.toml +20 -12
  4. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/adapters/git.py +42 -6
  5. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/app.py +26 -5
  6. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/callbacks.py +1 -1
  7. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/check/command.py +5 -2
  8. envctl-2.4.0/src/envctl/cli/commands/export/command.py +44 -0
  9. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/inspect/command.py +5 -2
  10. envctl-2.4.0/src/envctl/cli/commands/run/command.py +31 -0
  11. envctl-2.4.0/src/envctl/cli/commands/sync/command.py +35 -0
  12. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/app.py +6 -0
  13. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/commands/__init__.py +6 -0
  14. envctl-2.4.0/src/envctl/cli/commands/vault/commands/audit.py +27 -0
  15. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/commands/check.py +8 -1
  16. envctl-2.4.0/src/envctl/cli/commands/vault/commands/decrypt.py +26 -0
  17. envctl-2.4.0/src/envctl/cli/commands/vault/commands/encrypt.py +26 -0
  18. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/commands/show.py +2 -0
  19. envctl-2.4.0/src/envctl/cli/decorators.py +133 -0
  20. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/__init__.py +32 -0
  21. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/common.py +0 -10
  22. envctl-2.4.0/src/envctl/cli/presenters/config_error_presenter.py +46 -0
  23. envctl-2.4.0/src/envctl/cli/presenters/contract_error_presenter.py +34 -0
  24. envctl-2.4.0/src/envctl/cli/presenters/projection_error_presenter.py +33 -0
  25. envctl-2.4.0/src/envctl/cli/presenters/repository_error_presenter.py +71 -0
  26. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/resolution_presenter.py +38 -28
  27. envctl-2.4.0/src/envctl/cli/presenters/run_presenter.py +11 -0
  28. envctl-2.4.0/src/envctl/cli/presenters/state_error_presenter.py +26 -0
  29. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/vault_presenter.py +80 -10
  30. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/runtime.py +8 -1
  31. envctl-2.4.0/src/envctl/cli/serializers/__init__.py +37 -0
  32. envctl-2.4.0/src/envctl/cli/serializers/common.py +20 -0
  33. envctl-2.4.0/src/envctl/cli/serializers/context.py +26 -0
  34. envctl-2.4.0/src/envctl/cli/serializers/diagnostics.py +124 -0
  35. envctl-2.4.0/src/envctl/cli/serializers/doctor.py +26 -0
  36. envctl-2.4.0/src/envctl/cli/serializers/errors.py +28 -0
  37. envctl-2.4.0/src/envctl/cli/serializers/resolution.py +47 -0
  38. envctl-2.4.0/src/envctl/cli/serializers/status.py +23 -0
  39. envctl-2.4.0/src/envctl/config/loader.py +305 -0
  40. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/config/writer.py +10 -1
  41. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/constants.py +3 -4
  42. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/app_config.py +2 -0
  43. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/contract.py +5 -1
  44. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/expansion.py +9 -0
  45. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/operations.py +52 -0
  46. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/project.py +6 -1
  47. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/errors.py +17 -4
  48. envctl-2.4.0/src/envctl/repository/contract_repository.py +231 -0
  49. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/repository/profile_repository.py +71 -5
  50. envctl-2.4.0/src/envctl/repository/project_context/__init__.py +15 -0
  51. envctl-2.4.0/src/envctl/repository/project_context/bindings.py +67 -0
  52. envctl-2.4.0/src/envctl/repository/project_context/builders.py +43 -0
  53. envctl-2.4.0/src/envctl/repository/project_context/context.py +146 -0
  54. envctl-2.4.0/src/envctl/repository/project_context/discovery.py +34 -0
  55. envctl-2.4.0/src/envctl/repository/project_context/identity.py +13 -0
  56. envctl-2.4.0/src/envctl/repository/project_context/recovery.py +107 -0
  57. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/repository/state_repository.py +82 -11
  58. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/add_service.py +4 -2
  59. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/check_service.py +4 -1
  60. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/context_service.py +27 -0
  61. envctl-2.4.0/src/envctl/services/error_diagnostics.py +139 -0
  62. envctl-2.4.0/src/envctl/services/export_service.py +49 -0
  63. envctl-2.4.0/src/envctl/services/group_selection_service.py +65 -0
  64. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/inspect_service.py +4 -1
  65. envctl-2.4.0/src/envctl/services/projection_validation.py +73 -0
  66. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/rebind_service.py +38 -3
  67. envctl-2.4.0/src/envctl/services/resolution_service/__init__.py +11 -0
  68. envctl-2.4.0/src/envctl/services/resolution_service/builders.py +41 -0
  69. envctl-2.4.0/src/envctl/services/resolution_service/expansion.py +256 -0
  70. envctl-2.4.0/src/envctl/services/resolution_service/resolution.py +74 -0
  71. envctl-2.4.0/src/envctl/services/resolution_service/selection.py +40 -0
  72. envctl-2.4.0/src/envctl/services/resolution_service/types.py +13 -0
  73. envctl-2.4.0/src/envctl/services/resolution_service/validation.py +113 -0
  74. envctl-2.4.0/src/envctl/services/run_service.py +120 -0
  75. envctl-2.4.0/src/envctl/services/sync_service.py +50 -0
  76. envctl-2.4.0/src/envctl/services/vault_service.py +551 -0
  77. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/output.py +0 -6
  78. envctl-2.4.0/src/envctl/utils/projection_rendering.py +77 -0
  79. envctl-2.4.0/src/envctl/vault_crypto.py +241 -0
  80. envctl-2.4.0/src/envctl.egg-info/PKG-INFO +359 -0
  81. {envctl-2.3.3 → envctl-2.4.0}/src/envctl.egg-info/SOURCES.txt +36 -4
  82. envctl-2.4.0/src/envctl.egg-info/requires.txt +16 -0
  83. envctl-2.3.3/PKG-INFO +0 -397
  84. envctl-2.3.3/README.md +0 -358
  85. envctl-2.3.3/src/envctl/adapters/process_environment.py +0 -17
  86. envctl-2.3.3/src/envctl/cli/commands/export/command.py +0 -16
  87. envctl-2.3.3/src/envctl/cli/commands/run/command.py +0 -22
  88. envctl-2.3.3/src/envctl/cli/commands/sync/command.py +0 -16
  89. envctl-2.3.3/src/envctl/cli/decorators.py +0 -74
  90. envctl-2.3.3/src/envctl/cli/serializers.py +0 -132
  91. envctl-2.3.3/src/envctl/config/loader.py +0 -130
  92. envctl-2.3.3/src/envctl/repository/contract_repository.py +0 -120
  93. envctl-2.3.3/src/envctl/repository/project_context.py +0 -327
  94. envctl-2.3.3/src/envctl/services/export_service.py +0 -35
  95. envctl-2.3.3/src/envctl/services/resolution_service.py +0 -506
  96. envctl-2.3.3/src/envctl/services/run_service.py +0 -56
  97. envctl-2.3.3/src/envctl/services/sync_service.py +0 -41
  98. envctl-2.3.3/src/envctl/services/vault_service.py +0 -201
  99. envctl-2.3.3/src/envctl.egg-info/PKG-INFO +0 -397
  100. envctl-2.3.3/src/envctl.egg-info/requires.txt +0 -13
  101. {envctl-2.3.3 → envctl-2.4.0}/LICENSE +0 -0
  102. {envctl-2.3.3 → envctl-2.4.0}/setup.cfg +0 -0
  103. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/__init__.py +0 -0
  104. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/__main__.py +0 -0
  105. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/adapters/__init__.py +0 -0
  106. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/adapters/dotenv.py +0 -0
  107. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/adapters/editor.py +0 -0
  108. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/adapters/input.py +0 -0
  109. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/__init__.py +0 -0
  110. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/__init__.py +0 -0
  111. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/add/__init__.py +0 -0
  112. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/add/command.py +0 -0
  113. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/check/__init__.py +0 -0
  114. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/config/__init__.py +0 -0
  115. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/config/app.py +0 -0
  116. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/doctor/__init__.py +0 -0
  117. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/doctor/command.py +0 -0
  118. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/explain/__init__.py +0 -0
  119. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/explain/command.py +0 -0
  120. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/export/__init__.py +0 -0
  121. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/fill/__init__.py +0 -0
  122. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/fill/command.py +0 -0
  123. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/init/__init__.py +0 -0
  124. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/init/command.py +0 -0
  125. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/inspect/__init__.py +0 -0
  126. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/__init__.py +0 -0
  127. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/app.py +0 -0
  128. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/commands/__init__.py +0 -0
  129. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/commands/copy.py +0 -0
  130. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/commands/create.py +0 -0
  131. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/commands/list.py +0 -0
  132. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/commands/path.py +0 -0
  133. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/profile/commands/remove.py +0 -0
  134. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/__init__.py +0 -0
  135. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/app.py +0 -0
  136. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/commands/__init__.py +0 -0
  137. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/commands/bind.py +0 -0
  138. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/commands/rebind.py +0 -0
  139. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/commands/repair.py +0 -0
  140. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/project/commands/unbind.py +0 -0
  141. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/remove/__init__.py +0 -0
  142. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/remove/command.py +0 -0
  143. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/run/__init__.py +0 -0
  144. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/set/__init__.py +0 -0
  145. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/set/command.py +0 -0
  146. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/status/__init__.py +0 -0
  147. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/status/command.py +0 -0
  148. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/sync/__init__.py +0 -0
  149. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/unset/__init__.py +0 -0
  150. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/unset/command.py +0 -0
  151. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/__init__.py +0 -0
  152. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/commands/edit.py +0 -0
  153. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/commands/path.py +0 -0
  154. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/commands/vault/commands/prune.py +0 -0
  155. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/action_presenter.py +0 -0
  156. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/doctor_presenter.py +0 -0
  157. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/profile_presenter.py +0 -0
  158. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/project_presenter.py +0 -0
  159. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/presenters/status_presenter.py +0 -0
  160. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/prompts/__init__.py +0 -0
  161. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/cli/prompts/confirmation_prompts.py +0 -0
  162. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/config/__init__.py +0 -0
  163. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/config/defaults.py +0 -0
  164. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/config/profile_resolution.py +0 -0
  165. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/__init__.py +0 -0
  166. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/contract_inference.py +0 -0
  167. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/doctor.py +0 -0
  168. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/resolution.py +0 -0
  169. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/runtime.py +0 -0
  170. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/domain/status.py +0 -0
  171. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/repository/__init__.py +0 -0
  172. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/__init__.py +0 -0
  173. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/bind_service.py +0 -0
  174. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/config_service.py +0 -0
  175. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/doctor_service.py +0 -0
  176. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/explain_service.py +0 -0
  177. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/fill_service.py +0 -0
  178. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/init_service.py +0 -0
  179. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/profile_service.py +0 -0
  180. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/remove_service.py +0 -0
  181. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/repair_service.py +0 -0
  182. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/set_service.py +0 -0
  183. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/status_service.py +0 -0
  184. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/unbind_service.py +0 -0
  185. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/services/unset_service.py +0 -0
  186. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/__init__.py +0 -0
  187. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/atomic.py +0 -0
  188. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/filesystem.py +0 -0
  189. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/masking.py +0 -0
  190. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/project_ids.py +0 -0
  191. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/project_names.py +0 -0
  192. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/project_paths.py +0 -0
  193. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/shells.py +0 -0
  194. {envctl-2.3.3 → envctl-2.4.0}/src/envctl/utils/tilde.py +0 -0
  195. {envctl-2.3.3 → envctl-2.4.0}/src/envctl.egg-info/dependency_links.txt +0 -0
  196. {envctl-2.3.3 → envctl-2.4.0}/src/envctl.egg-info/entry_points.txt +0 -0
  197. {envctl-2.3.3 → envctl-2.4.0}/src/envctl.egg-info/top_level.txt +0 -0
envctl-2.4.0/PKG-INFO ADDED
@@ -0,0 +1,359 @@
1
+ Metadata-Version: 2.4
2
+ Name: envctl
3
+ Version: 2.4.0
4
+ Summary: Local environment control plane for contract-driven development workflows
5
+ Author-email: Alessandro Barbagallo <alessbarb@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/labrynx/envctl
8
+ Project-URL: Repository, https://github.com/labrynx/envctl
9
+ Project-URL: Issues, https://github.com/labrynx/envctl/issues
10
+ Project-URL: Changelog, https://github.com/labrynx/envctl/releases
11
+ Keywords: env,environment,dotenv,cli,developer-tools,configuration,secrets,contract-first,encryption
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Environment :: Console
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Build Tools
21
+ Classifier: Topic :: Utilities
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typer<1.0,>=0.12
27
+ Requires-Dist: PyYAML<7.0,>=6.0
28
+ Requires-Dist: pydantic<3.0,>=2.7
29
+ Requires-Dist: cryptography<45,>=41
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest==8.2.2; extra == "dev"
32
+ Requires-Dist: pytest-cov==7.1.0; extra == "dev"
33
+ Requires-Dist: ruff==0.11.8; extra == "dev"
34
+ Requires-Dist: mypy==1.11.2; extra == "dev"
35
+ Requires-Dist: types-PyYAML==6.0.12.20240311; extra == "dev"
36
+ Requires-Dist: types-cryptography; extra == "dev"
37
+ Requires-Dist: build==1.2.2; extra == "dev"
38
+ Requires-Dist: twine==6.2.0; extra == "dev"
39
+ Requires-Dist: pkginfo==1.12.0; extra == "dev"
40
+ Requires-Dist: vulture==2.16; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # envctl
44
+
45
+ **Your `.env.local` files drift between machines, hide missing variables, and break when you least expect it. envctl fixes that.**
46
+
47
+ [![CI](https://github.com/labrynx/envctl/actions/workflows/ci.yml/badge.svg)](https://github.com/labrynx/envctl/actions/workflows/ci.yml)
48
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
49
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/labrynx/envctl/blob/main/LICENSE)
50
+
51
+ ---
52
+
53
+ ## Why this exists
54
+
55
+ Most projects handle environment variables in a messy way:
56
+
57
+ - `.env.local` files are undocumented
58
+ - values get copied between machines
59
+ - something works locally until it suddenly does not
60
+ - CI and local setups behave differently
61
+ - nobody is fully sure which variables are required
62
+
63
+ It works — until it breaks.
64
+
65
+ `envctl` brings structure to this without turning environment setup into a second project.
66
+
67
+ ---
68
+
69
+ ## What is envctl?
70
+
71
+ `envctl` is a local-first environment control plane built around a **contract-first model**.
72
+
73
+ It separates three things that usually get mixed together:
74
+
75
+ - **what the project needs** → defined in `.envctl.schema.yaml` and committed to the repository
76
+ - **what you have locally** → stored in a private local vault, outside git
77
+ - **what actually runs** → a validated environment resolved when needed
78
+
79
+ That gives you:
80
+
81
+ - clear, documented variables
82
+ - no secrets in git
83
+ - fewer setup mistakes
84
+ - more predictable local and team workflows
85
+ - explicit validation before execution
86
+
87
+ ---
88
+
89
+ ## Install
90
+
91
+ ```bash
92
+ pip install envctl
93
+ ````
94
+
95
+ Or from source:
96
+
97
+ ```bash
98
+ git clone https://github.com/labrynx/envctl
99
+ cd envctl
100
+ pip install -e .
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Quickstart
106
+
107
+ ```bash
108
+ envctl config init
109
+ envctl init
110
+ envctl fill
111
+ envctl check
112
+ envctl run -- python app.py
113
+ ```
114
+
115
+ What this does:
116
+
117
+ * `config init` creates your local envctl config
118
+ * `init` connects the current repository to envctl
119
+ * `fill` asks only for missing values
120
+ * `check` validates the environment before you run anything
121
+ * `run` injects a clean resolved environment into the child process
122
+
123
+ ---
124
+
125
+ ## Why not just `.env.local`?
126
+
127
+ Because it does not scale cleanly.
128
+
129
+ | | `.env.local` | direnv | Doppler / Infisical | **envctl** |
130
+ | ------------------------------ | ------------ | ------------ | ------------------- | ---------- |
131
+ | Documents variables | ❌ | ❌ | Partial | ✅ |
132
+ | Validates values | ❌ | ❌ | ❌ | ✅ |
133
+ | Keeps secrets out of git | ⚠️ | ✅ | ✅ cloud | ✅ local |
134
+ | Supports multiple environments | manual files | manual files | ✅ | ✅ profiles |
135
+ | Works without cloud | ✅ | ✅ | ❌ | ✅ |
136
+
137
+ `envctl` is **not** a cloud secrets manager.
138
+
139
+ It is a way to make environment handling explicit, predictable, and local-first.
140
+
141
+ > your repository defines what is needed, your machine provides the values, and envctl resolves the final environment.
142
+
143
+ ---
144
+
145
+ ## A typical workflow
146
+
147
+ ```bash
148
+ # one developer adds a new requirement
149
+ envctl add API_KEY sk-example
150
+ git add .envctl.schema.yaml
151
+ git commit -m "require API_KEY"
152
+
153
+ # another developer pulls the change
154
+ envctl check
155
+ envctl fill
156
+ envctl run -- python app.py
157
+ ```
158
+
159
+ The contract is shared in git.
160
+ The values stay local.
161
+ The runtime environment is rebuilt consistently when needed.
162
+
163
+ ---
164
+
165
+ ## How it works
166
+
167
+ At a high level:
168
+
169
+ * **contract** → defines which variables exist and how they should look
170
+ * **vault** → stores the real local values
171
+ * **profile** → selects which local value set to use (`local`, `dev`, `staging`, ...)
172
+ * **resolution** → builds the final validated environment
173
+ * **projection** → applies it through `run`, `sync`, or `export`
174
+
175
+ Think of it like this:
176
+
177
+ > the repository defines the rules, your machine provides the values, and envctl builds the environment you actually run.
178
+
179
+ ---
180
+
181
+ ## Profiles
182
+
183
+ Instead of juggling multiple `.env` files:
184
+
185
+ ```bash
186
+ envctl --profile dev fill
187
+ envctl --profile staging check
188
+ envctl --profile staging run -- python app.py
189
+ ```
190
+
191
+ Each profile is explicit and independent.
192
+ No hidden inheritance, no magic fallback between profiles.
193
+
194
+ ---
195
+
196
+ ## Docker note
197
+
198
+ ```bash
199
+ envctl run -- docker run ...
200
+ ```
201
+
202
+ `envctl` injects variables into the **Docker client process**.
203
+
204
+ To pass them into the container, you still need one of these:
205
+
206
+ * `-e`
207
+ * `--env`
208
+ * `--env-file`
209
+
210
+ A common pattern is:
211
+
212
+ ```bash
213
+ docker run --env-file <(envctl export --format dotenv) ...
214
+ ```
215
+
216
+ ---
217
+
218
+ ## CI mode
219
+
220
+ ```bash
221
+ ENVCTL_RUNTIME_MODE=ci envctl check
222
+ ```
223
+
224
+ In CI:
225
+
226
+ * validation still works
227
+ * mutating commands are blocked
228
+
229
+ That keeps automation predictable and avoids accidental local-style writes in CI environments.
230
+
231
+ ---
232
+
233
+ ## Common commands
234
+
235
+ ```bash
236
+ envctl check
237
+ envctl inspect
238
+ envctl explain DATABASE_URL
239
+ envctl status
240
+ envctl doctor
241
+
242
+ envctl add DATABASE_URL <value>
243
+ envctl set PORT 4000
244
+ envctl unset PORT
245
+
246
+ envctl run -- <command>
247
+ envctl sync
248
+ envctl export
249
+
250
+ envctl profile list
251
+ envctl profile create staging
252
+
253
+ envctl vault check
254
+ envctl vault show
255
+ envctl vault encrypt
256
+ envctl vault decrypt
257
+ ```
258
+
259
+ ---
260
+
261
+ ## When envctl is a good fit
262
+
263
+ envctl is a strong fit if:
264
+
265
+ * `.env.local` files drift between machines
266
+ * onboarding is fragile
267
+ * CI and local environments do not behave the same way
268
+ * you work with multiple environments
269
+ * you want a local-first workflow without depending on a hosted service
270
+
271
+ ---
272
+
273
+ ## When envctl is not the right tool
274
+
275
+ envctl may be unnecessary if:
276
+
277
+ * you only have one static `.env` file
278
+ * the project is very small and has no real setup complexity
279
+ * you already rely fully on a centralized secrets platform and do not want local-first handling
280
+
281
+ ---
282
+
283
+ ## Security model
284
+
285
+ * the contract contains **no secrets**
286
+ * secrets stay on your machine
287
+ * sensitive values are masked in normal output
288
+ * vault files use restrictive permissions
289
+ * optional encryption at rest is available for vault files
290
+
291
+ ### Vault encryption at rest
292
+
293
+ If you enable encryption, envctl stores vault files in an encrypted, self-identifying format instead of plaintext.
294
+
295
+ Enable it in your config:
296
+
297
+ ```json
298
+ {
299
+ "encryption": {
300
+ "enabled": true
301
+ }
302
+ }
303
+ ```
304
+
305
+ Then migrate existing vault files once:
306
+
307
+ ```bash
308
+ envctl vault encrypt
309
+ ```
310
+
311
+ This creates a local key file at:
312
+
313
+ ```text
314
+ ~/.envctl/vault/master.key
315
+ ```
316
+
317
+ That key is stored with restrictive permissions.
318
+
319
+ After encryption is enabled:
320
+
321
+ * `vault edit` works transparently
322
+ * `vault check` reports whether the file is plaintext, encrypted, using the wrong key, or corrupted
323
+ * decrypt failures are explicit instead of looking like generic parse errors
324
+
325
+ To migrate back to plaintext:
326
+
327
+ ```bash
328
+ envctl vault decrypt
329
+ ```
330
+
331
+ Then disable encryption in config.
332
+
333
+ ### Important limitation
334
+
335
+ Encryption at rest helps protect vault files on disk.
336
+
337
+ It does **not** protect against a fully compromised machine or a compromised user session.
338
+
339
+ > envctl assumes a trusted machine.
340
+ > If your machine is compromised, your secrets are compromised too.
341
+
342
+ Back up your `master.key` carefully.
343
+ If you lose it, encrypted vault data cannot be recovered.
344
+
345
+ ---
346
+
347
+ ## Documentation
348
+
349
+ * [Quickstart](https://github.com/labrynx/envctl/blob/main/docs/getting-started/quickstart.md)
350
+ * [Mental model](https://github.com/labrynx/envctl/blob/main/docs/getting-started/mental-model.md)
351
+ * [Commands reference](https://github.com/labrynx/envctl/blob/main/docs/reference/commands.md)
352
+ * [Profiles reference](https://github.com/labrynx/envctl/blob/main/docs/reference/profiles.md)
353
+ * [Vault reference](https://github.com/labrynx/envctl/blob/main/docs/reference/vault.md)
354
+ * [Encryption reference](https://github.com/labrynx/envctl/blob/main/docs/reference/encryption.md)
355
+ * [Config reference](https://github.com/labrynx/envctl/blob/main/docs/reference/config.md)
356
+ * [CI workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/ci.md)
357
+ * [Team workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/team.md)
358
+ * [Security](https://github.com/labrynx/envctl/blob/main/docs/reference/security.md)
359
+ * [Internal architecture](https://github.com/labrynx/envctl/blob/main/docs/internals/architecture.md)
envctl-2.4.0/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # envctl
2
+
3
+ **Your `.env.local` files drift between machines, hide missing variables, and break when you least expect it. envctl fixes that.**
4
+
5
+ [![CI](https://github.com/labrynx/envctl/actions/workflows/ci.yml/badge.svg)](https://github.com/labrynx/envctl/actions/workflows/ci.yml)
6
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/labrynx/envctl/blob/main/LICENSE)
8
+
9
+ ---
10
+
11
+ ## Why this exists
12
+
13
+ Most projects handle environment variables in a messy way:
14
+
15
+ - `.env.local` files are undocumented
16
+ - values get copied between machines
17
+ - something works locally until it suddenly does not
18
+ - CI and local setups behave differently
19
+ - nobody is fully sure which variables are required
20
+
21
+ It works — until it breaks.
22
+
23
+ `envctl` brings structure to this without turning environment setup into a second project.
24
+
25
+ ---
26
+
27
+ ## What is envctl?
28
+
29
+ `envctl` is a local-first environment control plane built around a **contract-first model**.
30
+
31
+ It separates three things that usually get mixed together:
32
+
33
+ - **what the project needs** → defined in `.envctl.schema.yaml` and committed to the repository
34
+ - **what you have locally** → stored in a private local vault, outside git
35
+ - **what actually runs** → a validated environment resolved when needed
36
+
37
+ That gives you:
38
+
39
+ - clear, documented variables
40
+ - no secrets in git
41
+ - fewer setup mistakes
42
+ - more predictable local and team workflows
43
+ - explicit validation before execution
44
+
45
+ ---
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ pip install envctl
51
+ ````
52
+
53
+ Or from source:
54
+
55
+ ```bash
56
+ git clone https://github.com/labrynx/envctl
57
+ cd envctl
58
+ pip install -e .
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Quickstart
64
+
65
+ ```bash
66
+ envctl config init
67
+ envctl init
68
+ envctl fill
69
+ envctl check
70
+ envctl run -- python app.py
71
+ ```
72
+
73
+ What this does:
74
+
75
+ * `config init` creates your local envctl config
76
+ * `init` connects the current repository to envctl
77
+ * `fill` asks only for missing values
78
+ * `check` validates the environment before you run anything
79
+ * `run` injects a clean resolved environment into the child process
80
+
81
+ ---
82
+
83
+ ## Why not just `.env.local`?
84
+
85
+ Because it does not scale cleanly.
86
+
87
+ | | `.env.local` | direnv | Doppler / Infisical | **envctl** |
88
+ | ------------------------------ | ------------ | ------------ | ------------------- | ---------- |
89
+ | Documents variables | ❌ | ❌ | Partial | ✅ |
90
+ | Validates values | ❌ | ❌ | ❌ | ✅ |
91
+ | Keeps secrets out of git | ⚠️ | ✅ | ✅ cloud | ✅ local |
92
+ | Supports multiple environments | manual files | manual files | ✅ | ✅ profiles |
93
+ | Works without cloud | ✅ | ✅ | ❌ | ✅ |
94
+
95
+ `envctl` is **not** a cloud secrets manager.
96
+
97
+ It is a way to make environment handling explicit, predictable, and local-first.
98
+
99
+ > your repository defines what is needed, your machine provides the values, and envctl resolves the final environment.
100
+
101
+ ---
102
+
103
+ ## A typical workflow
104
+
105
+ ```bash
106
+ # one developer adds a new requirement
107
+ envctl add API_KEY sk-example
108
+ git add .envctl.schema.yaml
109
+ git commit -m "require API_KEY"
110
+
111
+ # another developer pulls the change
112
+ envctl check
113
+ envctl fill
114
+ envctl run -- python app.py
115
+ ```
116
+
117
+ The contract is shared in git.
118
+ The values stay local.
119
+ The runtime environment is rebuilt consistently when needed.
120
+
121
+ ---
122
+
123
+ ## How it works
124
+
125
+ At a high level:
126
+
127
+ * **contract** → defines which variables exist and how they should look
128
+ * **vault** → stores the real local values
129
+ * **profile** → selects which local value set to use (`local`, `dev`, `staging`, ...)
130
+ * **resolution** → builds the final validated environment
131
+ * **projection** → applies it through `run`, `sync`, or `export`
132
+
133
+ Think of it like this:
134
+
135
+ > the repository defines the rules, your machine provides the values, and envctl builds the environment you actually run.
136
+
137
+ ---
138
+
139
+ ## Profiles
140
+
141
+ Instead of juggling multiple `.env` files:
142
+
143
+ ```bash
144
+ envctl --profile dev fill
145
+ envctl --profile staging check
146
+ envctl --profile staging run -- python app.py
147
+ ```
148
+
149
+ Each profile is explicit and independent.
150
+ No hidden inheritance, no magic fallback between profiles.
151
+
152
+ ---
153
+
154
+ ## Docker note
155
+
156
+ ```bash
157
+ envctl run -- docker run ...
158
+ ```
159
+
160
+ `envctl` injects variables into the **Docker client process**.
161
+
162
+ To pass them into the container, you still need one of these:
163
+
164
+ * `-e`
165
+ * `--env`
166
+ * `--env-file`
167
+
168
+ A common pattern is:
169
+
170
+ ```bash
171
+ docker run --env-file <(envctl export --format dotenv) ...
172
+ ```
173
+
174
+ ---
175
+
176
+ ## CI mode
177
+
178
+ ```bash
179
+ ENVCTL_RUNTIME_MODE=ci envctl check
180
+ ```
181
+
182
+ In CI:
183
+
184
+ * validation still works
185
+ * mutating commands are blocked
186
+
187
+ That keeps automation predictable and avoids accidental local-style writes in CI environments.
188
+
189
+ ---
190
+
191
+ ## Common commands
192
+
193
+ ```bash
194
+ envctl check
195
+ envctl inspect
196
+ envctl explain DATABASE_URL
197
+ envctl status
198
+ envctl doctor
199
+
200
+ envctl add DATABASE_URL <value>
201
+ envctl set PORT 4000
202
+ envctl unset PORT
203
+
204
+ envctl run -- <command>
205
+ envctl sync
206
+ envctl export
207
+
208
+ envctl profile list
209
+ envctl profile create staging
210
+
211
+ envctl vault check
212
+ envctl vault show
213
+ envctl vault encrypt
214
+ envctl vault decrypt
215
+ ```
216
+
217
+ ---
218
+
219
+ ## When envctl is a good fit
220
+
221
+ envctl is a strong fit if:
222
+
223
+ * `.env.local` files drift between machines
224
+ * onboarding is fragile
225
+ * CI and local environments do not behave the same way
226
+ * you work with multiple environments
227
+ * you want a local-first workflow without depending on a hosted service
228
+
229
+ ---
230
+
231
+ ## When envctl is not the right tool
232
+
233
+ envctl may be unnecessary if:
234
+
235
+ * you only have one static `.env` file
236
+ * the project is very small and has no real setup complexity
237
+ * you already rely fully on a centralized secrets platform and do not want local-first handling
238
+
239
+ ---
240
+
241
+ ## Security model
242
+
243
+ * the contract contains **no secrets**
244
+ * secrets stay on your machine
245
+ * sensitive values are masked in normal output
246
+ * vault files use restrictive permissions
247
+ * optional encryption at rest is available for vault files
248
+
249
+ ### Vault encryption at rest
250
+
251
+ If you enable encryption, envctl stores vault files in an encrypted, self-identifying format instead of plaintext.
252
+
253
+ Enable it in your config:
254
+
255
+ ```json
256
+ {
257
+ "encryption": {
258
+ "enabled": true
259
+ }
260
+ }
261
+ ```
262
+
263
+ Then migrate existing vault files once:
264
+
265
+ ```bash
266
+ envctl vault encrypt
267
+ ```
268
+
269
+ This creates a local key file at:
270
+
271
+ ```text
272
+ ~/.envctl/vault/master.key
273
+ ```
274
+
275
+ That key is stored with restrictive permissions.
276
+
277
+ After encryption is enabled:
278
+
279
+ * `vault edit` works transparently
280
+ * `vault check` reports whether the file is plaintext, encrypted, using the wrong key, or corrupted
281
+ * decrypt failures are explicit instead of looking like generic parse errors
282
+
283
+ To migrate back to plaintext:
284
+
285
+ ```bash
286
+ envctl vault decrypt
287
+ ```
288
+
289
+ Then disable encryption in config.
290
+
291
+ ### Important limitation
292
+
293
+ Encryption at rest helps protect vault files on disk.
294
+
295
+ It does **not** protect against a fully compromised machine or a compromised user session.
296
+
297
+ > envctl assumes a trusted machine.
298
+ > If your machine is compromised, your secrets are compromised too.
299
+
300
+ Back up your `master.key` carefully.
301
+ If you lose it, encrypted vault data cannot be recovered.
302
+
303
+ ---
304
+
305
+ ## Documentation
306
+
307
+ * [Quickstart](https://github.com/labrynx/envctl/blob/main/docs/getting-started/quickstart.md)
308
+ * [Mental model](https://github.com/labrynx/envctl/blob/main/docs/getting-started/mental-model.md)
309
+ * [Commands reference](https://github.com/labrynx/envctl/blob/main/docs/reference/commands.md)
310
+ * [Profiles reference](https://github.com/labrynx/envctl/blob/main/docs/reference/profiles.md)
311
+ * [Vault reference](https://github.com/labrynx/envctl/blob/main/docs/reference/vault.md)
312
+ * [Encryption reference](https://github.com/labrynx/envctl/blob/main/docs/reference/encryption.md)
313
+ * [Config reference](https://github.com/labrynx/envctl/blob/main/docs/reference/config.md)
314
+ * [CI workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/ci.md)
315
+ * [Team workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/team.md)
316
+ * [Security](https://github.com/labrynx/envctl/blob/main/docs/reference/security.md)
317
+ * [Internal architecture](https://github.com/labrynx/envctl/blob/main/docs/internals/architecture.md)