envctl 2.3.1__tar.gz → 2.3.3__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 (162) hide show
  1. envctl-2.3.3/PKG-INFO +397 -0
  2. envctl-2.3.3/README.md +358 -0
  3. {envctl-2.3.1 → envctl-2.3.3}/pyproject.toml +8 -7
  4. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/dotenv.py +32 -4
  5. envctl-2.3.3/src/envctl/adapters/input.py +47 -0
  6. envctl-2.3.3/src/envctl/adapters/process_environment.py +17 -0
  7. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/app.py +7 -2
  8. envctl-2.3.3/src/envctl/cli/callbacks.py +14 -0
  9. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/add/command.py +33 -42
  10. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/check/command.py +22 -8
  11. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/config/app.py +2 -3
  12. envctl-2.3.3/src/envctl/cli/commands/doctor/command.py +37 -0
  13. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/explain/command.py +14 -9
  14. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/export/command.py +2 -4
  15. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/fill/command.py +13 -18
  16. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/init/command.py +12 -15
  17. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/inspect/command.py +10 -8
  18. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/copy.py +8 -7
  19. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/create.py +6 -8
  20. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/list.py +5 -3
  21. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/path.py +6 -3
  22. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/remove.py +12 -12
  23. envctl-2.3.3/src/envctl/cli/commands/project/commands/bind.py +30 -0
  24. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/rebind.py +17 -14
  25. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/repair.py +10 -14
  26. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/unbind.py +6 -9
  27. envctl-2.3.3/src/envctl/cli/commands/remove/command.py +53 -0
  28. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/set/command.py +6 -4
  29. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/status/command.py +5 -4
  30. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/sync/command.py +2 -5
  31. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/unset/command.py +7 -8
  32. envctl-2.3.3/src/envctl/cli/commands/vault/commands/check.py +29 -0
  33. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/edit.py +6 -8
  34. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/path.py +6 -3
  35. envctl-2.3.3/src/envctl/cli/commands/vault/commands/prune.py +69 -0
  36. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/show.py +30 -15
  37. envctl-2.3.3/src/envctl/cli/presenters/__init__.py +89 -0
  38. envctl-2.3.3/src/envctl/cli/presenters/action_presenter.py +242 -0
  39. envctl-2.3.3/src/envctl/cli/presenters/common.py +69 -0
  40. envctl-2.3.3/src/envctl/cli/presenters/doctor_presenter.py +59 -0
  41. envctl-2.3.3/src/envctl/cli/presenters/profile_presenter.py +58 -0
  42. envctl-2.3.3/src/envctl/cli/presenters/project_presenter.py +100 -0
  43. envctl-2.3.3/src/envctl/cli/presenters/resolution_presenter.py +84 -0
  44. envctl-2.3.3/src/envctl/cli/presenters/status_presenter.py +88 -0
  45. envctl-2.3.3/src/envctl/cli/presenters/vault_presenter.py +126 -0
  46. envctl-2.3.3/src/envctl/cli/prompts/__init__.py +17 -0
  47. envctl-2.3.3/src/envctl/cli/prompts/confirmation_prompts.py +45 -0
  48. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/serializers.py +12 -0
  49. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/loader.py +3 -24
  50. envctl-2.3.3/src/envctl/config/profile_resolution.py +54 -0
  51. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/contract.py +15 -1
  52. envctl-2.3.3/src/envctl/domain/expansion.py +101 -0
  53. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/operations.py +4 -0
  54. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/resolution.py +6 -0
  55. envctl-2.3.3/src/envctl/repository/profile_repository.py +140 -0
  56. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/add_service.py +38 -16
  57. envctl-2.3.3/src/envctl/services/doctor_service.py +161 -0
  58. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/fill_service.py +13 -13
  59. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/profile_service.py +14 -40
  60. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/rebind_service.py +6 -8
  61. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/remove_service.py +35 -32
  62. envctl-2.3.3/src/envctl/services/resolution_service.py +506 -0
  63. envctl-2.3.3/src/envctl/services/set_service.py +35 -0
  64. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/status_service.py +6 -2
  65. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/sync_service.py +4 -3
  66. envctl-2.3.3/src/envctl/services/unset_service.py +35 -0
  67. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/vault_service.py +51 -31
  68. envctl-2.3.3/src/envctl/utils/output.py +86 -0
  69. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/project_paths.py +10 -0
  70. envctl-2.3.3/src/envctl.egg-info/PKG-INFO +397 -0
  71. {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/SOURCES.txt +16 -1
  72. {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/requires.txt +2 -1
  73. envctl-2.3.1/PKG-INFO +0 -302
  74. envctl-2.3.1/README.md +0 -264
  75. envctl-2.3.1/src/envctl/cli/callbacks.py +0 -33
  76. envctl-2.3.1/src/envctl/cli/commands/doctor/command.py +0 -55
  77. envctl-2.3.1/src/envctl/cli/commands/project/commands/bind.py +0 -31
  78. envctl-2.3.1/src/envctl/cli/commands/remove/command.py +0 -65
  79. envctl-2.3.1/src/envctl/cli/commands/vault/commands/check.py +0 -43
  80. envctl-2.3.1/src/envctl/cli/commands/vault/commands/prune.py +0 -58
  81. envctl-2.3.1/src/envctl/cli/formatters.py +0 -78
  82. envctl-2.3.1/src/envctl/services/doctor_service.py +0 -177
  83. envctl-2.3.1/src/envctl/services/resolution_service.py +0 -146
  84. envctl-2.3.1/src/envctl/services/set_service.py +0 -35
  85. envctl-2.3.1/src/envctl/services/unset_service.py +0 -35
  86. envctl-2.3.1/src/envctl/utils/output.py +0 -25
  87. envctl-2.3.1/src/envctl.egg-info/PKG-INFO +0 -302
  88. {envctl-2.3.1 → envctl-2.3.3}/LICENSE +0 -0
  89. {envctl-2.3.1 → envctl-2.3.3}/setup.cfg +0 -0
  90. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/__init__.py +0 -0
  91. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/__main__.py +0 -0
  92. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/__init__.py +0 -0
  93. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/editor.py +0 -0
  94. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/adapters/git.py +0 -0
  95. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/__init__.py +0 -0
  96. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/__init__.py +0 -0
  97. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/add/__init__.py +0 -0
  98. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/check/__init__.py +0 -0
  99. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/config/__init__.py +0 -0
  100. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/doctor/__init__.py +0 -0
  101. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/explain/__init__.py +0 -0
  102. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/export/__init__.py +0 -0
  103. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/fill/__init__.py +0 -0
  104. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/init/__init__.py +0 -0
  105. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/inspect/__init__.py +0 -0
  106. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/__init__.py +0 -0
  107. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/app.py +0 -0
  108. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/profile/commands/__init__.py +0 -0
  109. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/__init__.py +0 -0
  110. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/app.py +0 -0
  111. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/project/commands/__init__.py +0 -0
  112. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/remove/__init__.py +0 -0
  113. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/run/__init__.py +0 -0
  114. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/run/command.py +0 -0
  115. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/set/__init__.py +0 -0
  116. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/status/__init__.py +0 -0
  117. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/sync/__init__.py +0 -0
  118. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/unset/__init__.py +0 -0
  119. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/__init__.py +0 -0
  120. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/app.py +0 -0
  121. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/commands/vault/commands/__init__.py +0 -0
  122. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/decorators.py +0 -0
  123. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/cli/runtime.py +0 -0
  124. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/__init__.py +0 -0
  125. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/defaults.py +0 -0
  126. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/config/writer.py +0 -0
  127. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/constants.py +0 -0
  128. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/__init__.py +0 -0
  129. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/app_config.py +0 -0
  130. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/contract_inference.py +0 -0
  131. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/doctor.py +0 -0
  132. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/project.py +0 -0
  133. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/runtime.py +0 -0
  134. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/domain/status.py +0 -0
  135. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/errors.py +0 -0
  136. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/__init__.py +0 -0
  137. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/contract_repository.py +0 -0
  138. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/project_context.py +0 -0
  139. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/repository/state_repository.py +0 -0
  140. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/__init__.py +0 -0
  141. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/bind_service.py +0 -0
  142. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/check_service.py +0 -0
  143. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/config_service.py +0 -0
  144. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/context_service.py +0 -0
  145. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/explain_service.py +0 -0
  146. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/export_service.py +0 -0
  147. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/init_service.py +0 -0
  148. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/inspect_service.py +0 -0
  149. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/repair_service.py +0 -0
  150. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/run_service.py +0 -0
  151. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/services/unbind_service.py +0 -0
  152. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/__init__.py +0 -0
  153. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/atomic.py +0 -0
  154. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/filesystem.py +0 -0
  155. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/masking.py +0 -0
  156. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/project_ids.py +0 -0
  157. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/project_names.py +0 -0
  158. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/shells.py +0 -0
  159. {envctl-2.3.1 → envctl-2.3.3}/src/envctl/utils/tilde.py +0 -0
  160. {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/dependency_links.txt +0 -0
  161. {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/entry_points.txt +0 -0
  162. {envctl-2.3.1 → envctl-2.3.3}/src/envctl.egg-info/top_level.txt +0 -0
envctl-2.3.3/PKG-INFO ADDED
@@ -0,0 +1,397 @@
1
+ Metadata-Version: 2.4
2
+ Name: envctl
3
+ Version: 2.3.3
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
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
+ Provides-Extra: dev
30
+ Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
31
+ Requires-Dist: pytest-cov<8.0.0,>=7.1.0; extra == "dev"
32
+ Requires-Dist: ruff<0.12,>=0.11; extra == "dev"
33
+ Requires-Dist: mypy<2.0,>=1.10; extra == "dev"
34
+ Requires-Dist: types-PyYAML<7.0.0,>=6.0.12; extra == "dev"
35
+ Requires-Dist: build<2.0,>=1.2; extra == "dev"
36
+ Requires-Dist: twine<7.0,>=6.2; extra == "dev"
37
+ Requires-Dist: pkginfo<2.0,>=1.12; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # envctl
41
+
42
+ **Your `.env.local` files are undocumented, unvalidated, and drift between machines. envctl fixes that.**
43
+
44
+ [![CI](https://github.com/labrynx/envctl/actions/workflows/ci.yml/badge.svg)](https://github.com/labrynx/envctl/actions/workflows/ci.yml)
45
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
46
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/labrynx/envctl/blob/main/LICENSE)
47
+
48
+ ---
49
+
50
+ ## What is this?
51
+
52
+ Most projects handle `.env` files like this:
53
+
54
+ * variables are not documented
55
+ * values get copied between machines
56
+ * something works locally… but breaks somewhere else
57
+
58
+ `envctl` gives you a simple structure to fix that.
59
+
60
+ It separates three things that usually get mixed together:
61
+
62
+ * **what the project needs** → defined in `.envctl.schema.yaml` (committed to the repo)
63
+ * **what you have locally** → stored in a private vault (never in git)
64
+ * **what actually runs** → a validated environment, built on demand
65
+
66
+ So you get:
67
+
68
+ * no secrets in git
69
+ * no undocumented variables
70
+ * no copy-pasting `.env` files
71
+
72
+ ---
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ pip install envctl
78
+ ```
79
+
80
+ Or from source:
81
+
82
+ ```bash
83
+ git clone https://github.com/labrynx/envctl
84
+ cd envctl
85
+ pip install -e .
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Quickstart
91
+
92
+ ```bash
93
+ envctl config init # create your local config
94
+ envctl init # initialize this repository
95
+ envctl fill # set missing values (interactive)
96
+ envctl check # validate against the contract
97
+ envctl run -- python app.py # run with env injected
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Why not just `.env.local`?
103
+
104
+ Because it doesn’t scale well.
105
+
106
+ | | `.env.local` | direnv | Doppler / Infisical | **envctl** |
107
+ | ------------------------------ | --------------- | ------------ | ------------------- | -------------------------- |
108
+ | Documents what variables exist | ❌ | ❌ | Partial | ✅ contract |
109
+ | Type validation | ❌ | ❌ | ❌ | ✅ |
110
+ | Values stay off git | ⚠️ easy to slip | ✅ | ✅ cloud | ✅ local vault |
111
+ | Multiple environments | manual files | manual files | ✅ | ✅ profiles |
112
+ | No cloud account required | ✅ | ✅ | ❌ | ✅ |
113
+ | Works in CI without mutation | ❌ | ❌ | ❌ | ✅ `ENVCTL_RUNTIME_MODE=ci` |
114
+
115
+ `envctl` is not a secrets manager.
116
+
117
+ It’s a **local control plane** for your project’s environment:
118
+
119
+ > the contract says what’s needed, your machine provides values, and envctl makes them work together.
120
+
121
+ ---
122
+
123
+ ## How it works
124
+
125
+ There are five pieces, but the idea is simple:
126
+
127
+ * **contract** → defines what variables exist and their rules
128
+ * **vault** → stores your real values locally
129
+ * **profile** → selects a set of values (`local`, `dev`, `staging`, …)
130
+ * **resolution** → combines everything in a deterministic way
131
+ * **projection** → makes it usable (`run`, `sync`, `export`)
132
+
133
+ Think of it like this:
134
+
135
+ > the repo defines the rules, your machine provides the data, and envctl builds the final environment.
136
+
137
+ Resolution now includes placeholder expansion as part of the runtime model, so `check`, `inspect`,
138
+ `run`, `sync`, and `export` all see the same final value.
139
+
140
+ ---
141
+
142
+ ### Example contract
143
+
144
+ ```yaml
145
+ # .envctl.schema.yaml — commit this
146
+ version: 1
147
+ variables:
148
+ DATABASE_URL:
149
+ type: url
150
+ required: true
151
+ sensitive: true
152
+ description: Primary database connection URL
153
+ PORT:
154
+ type: int
155
+ required: true
156
+ default: 3000
157
+ sensitive: false
158
+ DEBUG:
159
+ type: bool
160
+ required: false
161
+ default: false
162
+ sensitive: false
163
+ TEST_JSON:
164
+ type: string
165
+ format: json
166
+ required: false
167
+ sensitive: false
168
+ ```
169
+
170
+ This file describes what exists.
171
+ It never contains real values.
172
+
173
+ ---
174
+
175
+ ## Variable expansion
176
+
177
+ `envctl` supports explicit placeholder expansion with `${VAR}` during resolution.
178
+
179
+ That means the expansion happens before projection, so the effective expanded value is what:
180
+
181
+ * `inspect` shows
182
+ * `check` validates
183
+ * `run` injects
184
+ * `sync` writes
185
+ * `export` prints
186
+
187
+ Example:
188
+
189
+ ```dotenv
190
+ INFRA_NEO4J_USER=neo4j
191
+ INFRA_NEO4J_PASSWORD=super-secret
192
+ INFRA_NEO4J_AUTH=${INFRA_NEO4J_USER}/${INFRA_NEO4J_PASSWORD}
193
+ ```
194
+
195
+ `INFRA_NEO4J_AUTH` resolves to the final runtime value, not the literal expression.
196
+
197
+ Rules:
198
+
199
+ * only `${VAR}` is supported in v1
200
+ * `$VAR` stays literal
201
+ * if `VAR` is a declared envctl key, envctl resolves that key first
202
+ * otherwise envctl falls back to the current process environment
203
+ * `${HOME}` works when `HOME` exists in the current process environment
204
+ * malformed placeholders or unresolved references make resolution invalid
205
+
206
+ Compatibility notes:
207
+
208
+ * before this feature, `${HOME}` stayed literal
209
+ * now `${HOME}` is expanded during resolution
210
+ * `${...}` literal escaping is not supported in v1
211
+
212
+ ---
213
+
214
+ ## Profiles
215
+
216
+ Instead of juggling multiple `.env` files:
217
+
218
+ ```bash
219
+ # set up dev once
220
+ envctl --profile dev fill
221
+
222
+ # validate staging
223
+ envctl --profile staging check
224
+
225
+ # run with staging values
226
+ envctl --profile staging run -- python app.py
227
+ ```
228
+
229
+ Profile selection priority:
230
+
231
+ 1. `--profile`
232
+ 2. `ENVCTL_PROFILE`
233
+ 3. config default
234
+ 4. `local`
235
+
236
+ Each profile is independent. No hidden inheritance.
237
+
238
+ ---
239
+
240
+ ## Team workflow
241
+
242
+ The idea is simple:
243
+
244
+ * the **contract is shared**
245
+ * the **values are local**
246
+
247
+ ```bash
248
+ # developer A
249
+ envctl add API_KEY sk-abc123
250
+ git add .envctl.schema.yaml
251
+ git commit -m "require API_KEY"
252
+
253
+ # developer B
254
+ git pull
255
+ envctl check # shows what's missing
256
+ envctl fill # only asks for missing values
257
+ ```
258
+
259
+ No more guessing what goes into `.env`.
260
+
261
+ ---
262
+
263
+ ## CI workflow
264
+
265
+ ```bash
266
+ ENVCTL_RUNTIME_MODE=ci envctl check
267
+ ```
268
+
269
+ In CI mode:
270
+
271
+ * validation works
272
+ * mutations are blocked (`add`, `set`, `fill`, etc.)
273
+
274
+ You can also combine it with profiles:
275
+
276
+ ```bash
277
+ ENVCTL_PROFILE=ci ENVCTL_RUNTIME_MODE=ci envctl check
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Common commands
283
+
284
+ ```bash
285
+ # validation and visibility
286
+ envctl check
287
+ envctl inspect
288
+ envctl explain DATABASE_URL
289
+ envctl status
290
+ envctl doctor
291
+
292
+ # values
293
+ envctl add DATABASE_URL <value>
294
+ envctl add TEST_JSON '{"key":"value"}' --type string --format json
295
+ envctl set PORT 4000
296
+ envctl unset PORT
297
+ envctl remove PORT
298
+
299
+ # run / output
300
+ envctl run -- <command>
301
+ envctl sync
302
+ envctl export
303
+
304
+ # profiles
305
+ envctl profile list
306
+ envctl profile create staging
307
+ envctl profile copy local staging
308
+ envctl profile remove staging --yes
309
+
310
+ # vault
311
+ envctl vault show
312
+ envctl vault check
313
+ envctl vault path
314
+ envctl vault prune
315
+
316
+ # project identity
317
+ envctl project bind <id>
318
+ envctl project rebind
319
+ envctl project repair
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Machine-readable output
325
+
326
+ All read commands support `--json`:
327
+
328
+ ```bash
329
+ envctl --json check
330
+ envctl --json status
331
+ envctl --json inspect
332
+ envctl --json doctor
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Structured string validation
338
+
339
+ If a variable is a string but carries structured content, declare that semantic format in the contract:
340
+
341
+ ```yaml
342
+ variables:
343
+ TEST_JSON:
344
+ type: string
345
+ format: json
346
+ ```
347
+
348
+ Supported `format` values for `type: string`:
349
+
350
+ * `json`
351
+ * `url`
352
+ * `csv`
353
+
354
+ When `format` is declared, `check`, `inspect`, and runtime resolution validate payload semantics, not only raw string presence.
355
+
356
+ ---
357
+
358
+ ## Design principles
359
+
360
+ * Contract-first: the repo defines requirements
361
+ * Deterministic: same inputs → same result
362
+ * Explicit: nothing happens automatically
363
+ * Local-first: no required cloud
364
+ * Generated files are disposable
365
+ * Profiles are value namespaces, not variants
366
+ * CI mode is policy, not a profile
367
+
368
+ ---
369
+
370
+ ## Security model
371
+
372
+ * The contract contains **no secrets**
373
+ * Secrets stay on your machine
374
+ * `.env.local` is optional and disposable
375
+ * Sensitive values are masked in output
376
+ * Read-only commands never change state
377
+ * Vault files use restrictive permissions (`0600`)
378
+
379
+ Important:
380
+
381
+ > envctl assumes a trusted machine.
382
+ > If your machine is compromised, your secrets are compromised.
383
+
384
+ It’s not a replacement for a team-wide secrets manager.
385
+
386
+ ---
387
+
388
+ ## Documentation
389
+
390
+ * [Quickstart](https://github.com/labrynx/envctl/blob/main/docs/getting-started/quickstart.md)
391
+ * [Mental model](https://github.com/labrynx/envctl/blob/main/docs/getting-started/mental-model.md)
392
+ * [Commands reference](https://github.com/labrynx/envctl/blob/main/docs/reference/commands.md)
393
+ * [Profiles reference](https://github.com/labrynx/envctl/blob/main/docs/reference/profiles.md)
394
+ * [CI workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/ci.md)
395
+ * [Team workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/team.md)
396
+ * [Security](https://github.com/labrynx/envctl/blob/main/docs/reference/security.md)
397
+ * [Internal architecture](https://github.com/labrynx/envctl/blob/main/docs/internals/architecture.md)