envctl 2.3.1__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 (132) hide show
  1. envctl-2.3.1/LICENSE +21 -0
  2. envctl-2.3.1/PKG-INFO +302 -0
  3. envctl-2.3.1/README.md +264 -0
  4. envctl-2.3.1/pyproject.toml +128 -0
  5. envctl-2.3.1/setup.cfg +4 -0
  6. envctl-2.3.1/src/envctl/__init__.py +10 -0
  7. envctl-2.3.1/src/envctl/__main__.py +6 -0
  8. envctl-2.3.1/src/envctl/adapters/__init__.py +0 -0
  9. envctl-2.3.1/src/envctl/adapters/dotenv.py +76 -0
  10. envctl-2.3.1/src/envctl/adapters/editor.py +39 -0
  11. envctl-2.3.1/src/envctl/adapters/git.py +76 -0
  12. envctl-2.3.1/src/envctl/cli/__init__.py +1 -0
  13. envctl-2.3.1/src/envctl/cli/app.py +92 -0
  14. envctl-2.3.1/src/envctl/cli/callbacks.py +33 -0
  15. envctl-2.3.1/src/envctl/cli/commands/__init__.py +1 -0
  16. envctl-2.3.1/src/envctl/cli/commands/add/__init__.py +5 -0
  17. envctl-2.3.1/src/envctl/cli/commands/add/command.py +237 -0
  18. envctl-2.3.1/src/envctl/cli/commands/check/__init__.py +5 -0
  19. envctl-2.3.1/src/envctl/cli/commands/check/command.py +49 -0
  20. envctl-2.3.1/src/envctl/cli/commands/config/__init__.py +5 -0
  21. envctl-2.3.1/src/envctl/cli/commands/config/app.py +22 -0
  22. envctl-2.3.1/src/envctl/cli/commands/doctor/__init__.py +5 -0
  23. envctl-2.3.1/src/envctl/cli/commands/doctor/command.py +55 -0
  24. envctl-2.3.1/src/envctl/cli/commands/explain/__init__.py +5 -0
  25. envctl-2.3.1/src/envctl/cli/commands/explain/command.py +40 -0
  26. envctl-2.3.1/src/envctl/cli/commands/export/__init__.py +5 -0
  27. envctl-2.3.1/src/envctl/cli/commands/export/command.py +18 -0
  28. envctl-2.3.1/src/envctl/cli/commands/fill/__init__.py +5 -0
  29. envctl-2.3.1/src/envctl/cli/commands/fill/command.py +49 -0
  30. envctl-2.3.1/src/envctl/cli/commands/init/__init__.py +5 -0
  31. envctl-2.3.1/src/envctl/cli/commands/init/command.py +50 -0
  32. envctl-2.3.1/src/envctl/cli/commands/inspect/__init__.py +5 -0
  33. envctl-2.3.1/src/envctl/cli/commands/inspect/command.py +36 -0
  34. envctl-2.3.1/src/envctl/cli/commands/profile/__init__.py +5 -0
  35. envctl-2.3.1/src/envctl/cli/commands/profile/app.py +19 -0
  36. envctl-2.3.1/src/envctl/cli/commands/profile/commands/__init__.py +1 -0
  37. envctl-2.3.1/src/envctl/cli/commands/profile/commands/copy.py +34 -0
  38. envctl-2.3.1/src/envctl/cli/commands/profile/commands/create.py +33 -0
  39. envctl-2.3.1/src/envctl/cli/commands/profile/commands/list.py +18 -0
  40. envctl-2.3.1/src/envctl/cli/commands/profile/commands/path.py +26 -0
  41. envctl-2.3.1/src/envctl/cli/commands/profile/commands/remove.py +49 -0
  42. envctl-2.3.1/src/envctl/cli/commands/project/__init__.py +5 -0
  43. envctl-2.3.1/src/envctl/cli/commands/project/app.py +19 -0
  44. envctl-2.3.1/src/envctl/cli/commands/project/commands/__init__.py +13 -0
  45. envctl-2.3.1/src/envctl/cli/commands/project/commands/bind.py +31 -0
  46. envctl-2.3.1/src/envctl/cli/commands/project/commands/rebind.py +47 -0
  47. envctl-2.3.1/src/envctl/cli/commands/project/commands/repair.py +45 -0
  48. envctl-2.3.1/src/envctl/cli/commands/project/commands/unbind.py +24 -0
  49. envctl-2.3.1/src/envctl/cli/commands/remove/__init__.py +5 -0
  50. envctl-2.3.1/src/envctl/cli/commands/remove/command.py +65 -0
  51. envctl-2.3.1/src/envctl/cli/commands/run/__init__.py +5 -0
  52. envctl-2.3.1/src/envctl/cli/commands/run/command.py +22 -0
  53. envctl-2.3.1/src/envctl/cli/commands/set/__init__.py +5 -0
  54. envctl-2.3.1/src/envctl/cli/commands/set/command.py +28 -0
  55. envctl-2.3.1/src/envctl/cli/commands/status/__init__.py +5 -0
  56. envctl-2.3.1/src/envctl/cli/commands/status/command.py +31 -0
  57. envctl-2.3.1/src/envctl/cli/commands/sync/__init__.py +5 -0
  58. envctl-2.3.1/src/envctl/cli/commands/sync/command.py +19 -0
  59. envctl-2.3.1/src/envctl/cli/commands/unset/__init__.py +5 -0
  60. envctl-2.3.1/src/envctl/cli/commands/unset/command.py +30 -0
  61. envctl-2.3.1/src/envctl/cli/commands/vault/__init__.py +5 -0
  62. envctl-2.3.1/src/envctl/cli/commands/vault/app.py +21 -0
  63. envctl-2.3.1/src/envctl/cli/commands/vault/commands/__init__.py +15 -0
  64. envctl-2.3.1/src/envctl/cli/commands/vault/commands/check.py +43 -0
  65. envctl-2.3.1/src/envctl/cli/commands/vault/commands/edit.py +34 -0
  66. envctl-2.3.1/src/envctl/cli/commands/vault/commands/path.py +17 -0
  67. envctl-2.3.1/src/envctl/cli/commands/vault/commands/prune.py +58 -0
  68. envctl-2.3.1/src/envctl/cli/commands/vault/commands/show.py +82 -0
  69. envctl-2.3.1/src/envctl/cli/decorators.py +74 -0
  70. envctl-2.3.1/src/envctl/cli/formatters.py +78 -0
  71. envctl-2.3.1/src/envctl/cli/runtime.py +75 -0
  72. envctl-2.3.1/src/envctl/cli/serializers.py +120 -0
  73. envctl-2.3.1/src/envctl/config/__init__.py +1 -0
  74. envctl-2.3.1/src/envctl/config/defaults.py +46 -0
  75. envctl-2.3.1/src/envctl/config/loader.py +151 -0
  76. envctl-2.3.1/src/envctl/config/writer.py +39 -0
  77. envctl-2.3.1/src/envctl/constants.py +30 -0
  78. envctl-2.3.1/src/envctl/domain/__init__.py +1 -0
  79. envctl-2.3.1/src/envctl/domain/app_config.py +26 -0
  80. envctl-2.3.1/src/envctl/domain/contract.py +235 -0
  81. envctl-2.3.1/src/envctl/domain/contract_inference.py +236 -0
  82. envctl-2.3.1/src/envctl/domain/doctor.py +14 -0
  83. envctl-2.3.1/src/envctl/domain/operations.py +191 -0
  84. envctl-2.3.1/src/envctl/domain/project.py +35 -0
  85. envctl-2.3.1/src/envctl/domain/resolution.py +51 -0
  86. envctl-2.3.1/src/envctl/domain/runtime.py +19 -0
  87. envctl-2.3.1/src/envctl/domain/status.py +21 -0
  88. envctl-2.3.1/src/envctl/errors.py +33 -0
  89. envctl-2.3.1/src/envctl/repository/__init__.py +1 -0
  90. envctl-2.3.1/src/envctl/repository/contract_repository.py +120 -0
  91. envctl-2.3.1/src/envctl/repository/project_context.py +327 -0
  92. envctl-2.3.1/src/envctl/repository/state_repository.py +186 -0
  93. envctl-2.3.1/src/envctl/services/__init__.py +1 -0
  94. envctl-2.3.1/src/envctl/services/add_service.py +157 -0
  95. envctl-2.3.1/src/envctl/services/bind_service.py +46 -0
  96. envctl-2.3.1/src/envctl/services/check_service.py +20 -0
  97. envctl-2.3.1/src/envctl/services/config_service.py +12 -0
  98. envctl-2.3.1/src/envctl/services/context_service.py +29 -0
  99. envctl-2.3.1/src/envctl/services/doctor_service.py +177 -0
  100. envctl-2.3.1/src/envctl/services/explain_service.py +28 -0
  101. envctl-2.3.1/src/envctl/services/export_service.py +35 -0
  102. envctl-2.3.1/src/envctl/services/fill_service.py +72 -0
  103. envctl-2.3.1/src/envctl/services/init_service.py +210 -0
  104. envctl-2.3.1/src/envctl/services/inspect_service.py +20 -0
  105. envctl-2.3.1/src/envctl/services/profile_service.py +162 -0
  106. envctl-2.3.1/src/envctl/services/rebind_service.py +71 -0
  107. envctl-2.3.1/src/envctl/services/remove_service.py +98 -0
  108. envctl-2.3.1/src/envctl/services/repair_service.py +123 -0
  109. envctl-2.3.1/src/envctl/services/resolution_service.py +146 -0
  110. envctl-2.3.1/src/envctl/services/run_service.py +56 -0
  111. envctl-2.3.1/src/envctl/services/set_service.py +35 -0
  112. envctl-2.3.1/src/envctl/services/status_service.py +97 -0
  113. envctl-2.3.1/src/envctl/services/sync_service.py +40 -0
  114. envctl-2.3.1/src/envctl/services/unbind_service.py +32 -0
  115. envctl-2.3.1/src/envctl/services/unset_service.py +35 -0
  116. envctl-2.3.1/src/envctl/services/vault_service.py +181 -0
  117. envctl-2.3.1/src/envctl/utils/__init__.py +1 -0
  118. envctl-2.3.1/src/envctl/utils/atomic.py +57 -0
  119. envctl-2.3.1/src/envctl/utils/filesystem.py +40 -0
  120. envctl-2.3.1/src/envctl/utils/masking.py +12 -0
  121. envctl-2.3.1/src/envctl/utils/output.py +25 -0
  122. envctl-2.3.1/src/envctl/utils/project_ids.py +20 -0
  123. envctl-2.3.1/src/envctl/utils/project_names.py +22 -0
  124. envctl-2.3.1/src/envctl/utils/project_paths.py +46 -0
  125. envctl-2.3.1/src/envctl/utils/shells.py +18 -0
  126. envctl-2.3.1/src/envctl/utils/tilde.py +16 -0
  127. envctl-2.3.1/src/envctl.egg-info/PKG-INFO +302 -0
  128. envctl-2.3.1/src/envctl.egg-info/SOURCES.txt +130 -0
  129. envctl-2.3.1/src/envctl.egg-info/dependency_links.txt +1 -0
  130. envctl-2.3.1/src/envctl.egg-info/entry_points.txt +2 -0
  131. envctl-2.3.1/src/envctl.egg-info/requires.txt +12 -0
  132. envctl-2.3.1/src/envctl.egg-info/top_level.txt +1 -0
envctl-2.3.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alessandro Barbagallo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
envctl-2.3.1/PKG-INFO ADDED
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.4
2
+ Name: envctl
3
+ Version: 2.3.1
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/alessbarb/envctl
8
+ Project-URL: Repository, https://github.com/alessbarb/envctl
9
+ Project-URL: Issues, https://github.com/alessbarb/envctl/issues
10
+ Project-URL: Changelog, https://github.com/alessbarb/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<6.0,>=5.0; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # envctl
40
+
41
+ **Your `.env.local` files are undocumented, unvalidated, and drift between machines. envctl fixes that.**
42
+
43
+ [![CI](https://github.com/alessbarb/envctl/actions/workflows/ci.yml/badge.svg)](https://github.com/alessbarb/envctl/actions/workflows/ci.yml)
44
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/alessbarb/envctl/blob/main/LICENSE)
46
+
47
+ ---
48
+
49
+ `envctl` separates three things that most workflows conflate:
50
+
51
+ - **what the project needs** → a contract in `.envctl.schema.yaml`, committed to the repo
52
+ - **what you have locally** → values in a private vault, outside version control
53
+ - **what actually runs** → a validated, resolved environment, injected on demand
54
+
55
+ No secrets in git. No undocumented variables. No copy-pasting `.env` files between machines.
56
+
57
+ ---
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ pip install envctl
63
+ ```
64
+
65
+ Or from source:
66
+
67
+ ```bash
68
+ git clone https://github.com/alessbarb/envctl
69
+ cd envctl
70
+ pip install -e .
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Quickstart
76
+
77
+ ```bash
78
+ envctl config init # create your local user config
79
+ envctl init # initialize this repository
80
+ envctl fill # interactively set missing required values
81
+ envctl check # validate your environment against the contract
82
+ envctl run -- python app.py # run with environment injected
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Why not just `.env.local`?
88
+
89
+ | | `.env.local` | direnv | Doppler / Infisical | **envctl** |
90
+ |---|---|---|---|---|
91
+ | Documents what variables exist | ❌ | ❌ | Partial | ✅ contract |
92
+ | Type validation | ❌ | ❌ | ❌ | ✅ |
93
+ | Values stay off git | ⚠️ easy to slip | ✅ | ✅ cloud | ✅ local vault |
94
+ | Multiple environments | manual files | manual files | ✅ | ✅ profiles |
95
+ | No cloud account required | ✅ | ✅ | ❌ | ✅ |
96
+ | Works in CI without mutation | ❌ | ❌ | ❌ | ✅ `ENVCTL_RUNTIME_MODE=ci` |
97
+
98
+ `envctl` is not a secrets manager. It is a local control plane for the environment contract your project declares. Think of it as the layer between "this project needs these variables" and "here is how I run it today."
99
+
100
+ ---
101
+
102
+ ## How it works
103
+
104
+ `envctl` has five parts:
105
+
106
+ - **contract** → `.envctl.schema.yaml` declares which variables exist, their types, and which are required. Committed to the repo. Contains no secrets.
107
+ - **vault** → your machine-local store for real values, kept outside the repository
108
+ - **profile** → one value namespace (`local`, `dev`, `staging`, …) within the vault
109
+ - **resolution** → combines system env, active profile values, and contract defaults deterministically
110
+ - **projection** → makes the resolved environment usable (`run`, `sync`, `export`)
111
+
112
+ The contract defines what exists. The vault stores what you have. The profile selects which values to use. The resolver decides what runs.
113
+
114
+ ### Example contract
115
+
116
+ ```yaml
117
+ # .envctl.schema.yaml — commit this
118
+ version: 1
119
+ variables:
120
+ DATABASE_URL:
121
+ type: url
122
+ required: true
123
+ sensitive: true
124
+ description: Primary database connection URL
125
+ PORT:
126
+ type: int
127
+ required: true
128
+ default: 3000
129
+ sensitive: false
130
+ DEBUG:
131
+ type: bool
132
+ required: false
133
+ default: false
134
+ sensitive: false
135
+ ```
136
+
137
+ Values live in your local vault — never in this file.
138
+
139
+ ---
140
+
141
+ ## Profiles
142
+
143
+ Work with multiple environments locally without managing multiple files:
144
+
145
+ ```bash
146
+ # set up dev values once
147
+ envctl --profile dev fill
148
+
149
+ # check what staging would look like
150
+ envctl --profile staging check
151
+
152
+ # run against staging values
153
+ envctl --profile staging run -- python app.py
154
+ ```
155
+
156
+ Profile selection precedence:
157
+
158
+ 1. `--profile` flag
159
+ 2. `ENVCTL_PROFILE` environment variable
160
+ 3. `default_profile` in config
161
+ 4. `local` (default)
162
+
163
+ Profiles are independent value namespaces. They share the same contract. There is no hidden inheritance between profiles.
164
+
165
+ ---
166
+
167
+ ## Team workflow
168
+
169
+ The contract is shared. Values are local.
170
+
171
+ ```bash
172
+ # developer A: add a new variable
173
+ envctl add API_KEY sk-abc123
174
+ git add .envctl.schema.yaml
175
+ git commit -m "require API_KEY"
176
+
177
+ # developer B: pull and fill
178
+ git pull
179
+ envctl check # → shows API_KEY is missing
180
+ envctl fill # → prompts for API_KEY only
181
+ ```
182
+
183
+ ---
184
+
185
+ ## CI workflow
186
+
187
+ ```bash
188
+ # validate only — no mutation allowed
189
+ ENVCTL_RUNTIME_MODE=ci envctl check
190
+
191
+ # or with a ci profile
192
+ ENVCTL_PROFILE=ci ENVCTL_RUNTIME_MODE=ci envctl check
193
+ ```
194
+
195
+ In `ci` runtime mode, all mutation commands (`add`, `set`, `fill`, `sync`, etc.) are blocked. Read-only commands work normally.
196
+
197
+ ---
198
+
199
+ ## Common commands
200
+
201
+ ```bash
202
+ # validation and visibility
203
+ envctl check # validate against contract
204
+ envctl inspect # show resolved values (sensitive masked)
205
+ envctl explain DATABASE_URL # trace where a value comes from
206
+ envctl status # project readiness summary
207
+ envctl doctor # local environment diagnostics
208
+
209
+ # value management
210
+ envctl add DATABASE_URL <value> # add to contract + active profile
211
+ envctl set PORT 4000 # update active profile value only
212
+ envctl unset PORT # remove from active profile only
213
+ envctl remove PORT # remove from contract + all profiles
214
+
215
+ # projection
216
+ envctl run -- <command> # inject environment into subprocess
217
+ envctl sync # write .env.local as generated artifact
218
+ envctl export # emit shell export lines
219
+
220
+ # profiles
221
+ envctl profile list
222
+ envctl profile create staging
223
+ envctl profile copy local staging
224
+ envctl profile remove staging --yes
225
+
226
+ # vault inspection
227
+ envctl vault show
228
+ envctl vault check
229
+ envctl vault path
230
+ envctl vault prune # remove keys not in contract
231
+
232
+ # project identity
233
+ envctl project bind <id>
234
+ envctl project rebind
235
+ envctl project repair
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Machine-readable output
241
+
242
+ All read commands support `--json`:
243
+
244
+ ```bash
245
+ envctl --json check
246
+ envctl --json status
247
+ envctl --json inspect
248
+ envctl --json doctor
249
+ ```
250
+
251
+ ```json
252
+ {
253
+ "ok": true,
254
+ "command": "check",
255
+ "data": {
256
+ "active_profile": "local",
257
+ "report": {
258
+ "is_valid": true,
259
+ "missing_required": [],
260
+ "values": { "DATABASE_URL": { "source": "vault", "masked": true, ... } }
261
+ }
262
+ }
263
+ }
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Design principles
269
+
270
+ - Contract-first: the repo declares requirements; machines provide values
271
+ - Deterministic resolution: same inputs always produce the same environment
272
+ - Explicit over implicit: nothing happens without a command
273
+ - Local-first: no required cloud dependency
274
+ - Generated files are artifacts, not sources of truth
275
+ - Profiles are value namespaces, not contract variants
276
+ - CI mode is policy, not a profile
277
+
278
+ ---
279
+
280
+ ## Security model
281
+
282
+ - The contract contains **no secrets** — it is safe to commit
283
+ - Secrets never leave your machine unless you explicitly project them
284
+ - `.env.local` is optional and disposable — regenerate it any time
285
+ - Sensitive values are masked in all normal output
286
+ - Read-only commands never mutate state
287
+ - Local vault files are stored with `0600` permissions by default
288
+
289
+ Important: `envctl` is a local tool. It assumes a trusted machine. If your account or machine is compromised, your local vault is compromised. envctl is not a replacement for a team secrets manager when shared access control is required.
290
+
291
+ ---
292
+
293
+ ## Documentation
294
+
295
+ - [Quickstart](https://github.com/alessbarb/envctl/blob/main/docs/getting-started/quickstart.md)
296
+ - [Mental model](https://github.com/alessbarb/envctl/blob/main/docs/getting-started/mental-model.md)
297
+ - [Commands reference](https://github.com/alessbarb/envctl/blob/main/docs/reference/commands.md)
298
+ - [Profiles reference](https://github.com/alessbarb/envctl/blob/main/docs/reference/profiles.md)
299
+ - [CI workflow](https://github.com/alessbarb/envctl/blob/main/docs/workflows/ci.md)
300
+ - [Team workflow](https://github.com/alessbarb/envctl/blob/main/docs/workflows/team.md)
301
+ - [Security](https://github.com/alessbarb/envctl/blob/main/docs/reference/security.md)
302
+ - [Internal architecture](https://github.com/alessbarb/envctl/blob/main/docs/internals/architecture.md)
envctl-2.3.1/README.md ADDED
@@ -0,0 +1,264 @@
1
+ # envctl
2
+
3
+ **Your `.env.local` files are undocumented, unvalidated, and drift between machines. envctl fixes that.**
4
+
5
+ [![CI](https://github.com/alessbarb/envctl/actions/workflows/ci.yml/badge.svg)](https://github.com/alessbarb/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/alessbarb/envctl/blob/main/LICENSE)
8
+
9
+ ---
10
+
11
+ `envctl` separates three things that most workflows conflate:
12
+
13
+ - **what the project needs** → a contract in `.envctl.schema.yaml`, committed to the repo
14
+ - **what you have locally** → values in a private vault, outside version control
15
+ - **what actually runs** → a validated, resolved environment, injected on demand
16
+
17
+ No secrets in git. No undocumented variables. No copy-pasting `.env` files between machines.
18
+
19
+ ---
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install envctl
25
+ ```
26
+
27
+ Or from source:
28
+
29
+ ```bash
30
+ git clone https://github.com/alessbarb/envctl
31
+ cd envctl
32
+ pip install -e .
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Quickstart
38
+
39
+ ```bash
40
+ envctl config init # create your local user config
41
+ envctl init # initialize this repository
42
+ envctl fill # interactively set missing required values
43
+ envctl check # validate your environment against the contract
44
+ envctl run -- python app.py # run with environment injected
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Why not just `.env.local`?
50
+
51
+ | | `.env.local` | direnv | Doppler / Infisical | **envctl** |
52
+ |---|---|---|---|---|
53
+ | Documents what variables exist | ❌ | ❌ | Partial | ✅ contract |
54
+ | Type validation | ❌ | ❌ | ❌ | ✅ |
55
+ | Values stay off git | ⚠️ easy to slip | ✅ | ✅ cloud | ✅ local vault |
56
+ | Multiple environments | manual files | manual files | ✅ | ✅ profiles |
57
+ | No cloud account required | ✅ | ✅ | ❌ | ✅ |
58
+ | Works in CI without mutation | ❌ | ❌ | ❌ | ✅ `ENVCTL_RUNTIME_MODE=ci` |
59
+
60
+ `envctl` is not a secrets manager. It is a local control plane for the environment contract your project declares. Think of it as the layer between "this project needs these variables" and "here is how I run it today."
61
+
62
+ ---
63
+
64
+ ## How it works
65
+
66
+ `envctl` has five parts:
67
+
68
+ - **contract** → `.envctl.schema.yaml` declares which variables exist, their types, and which are required. Committed to the repo. Contains no secrets.
69
+ - **vault** → your machine-local store for real values, kept outside the repository
70
+ - **profile** → one value namespace (`local`, `dev`, `staging`, …) within the vault
71
+ - **resolution** → combines system env, active profile values, and contract defaults deterministically
72
+ - **projection** → makes the resolved environment usable (`run`, `sync`, `export`)
73
+
74
+ The contract defines what exists. The vault stores what you have. The profile selects which values to use. The resolver decides what runs.
75
+
76
+ ### Example contract
77
+
78
+ ```yaml
79
+ # .envctl.schema.yaml — commit this
80
+ version: 1
81
+ variables:
82
+ DATABASE_URL:
83
+ type: url
84
+ required: true
85
+ sensitive: true
86
+ description: Primary database connection URL
87
+ PORT:
88
+ type: int
89
+ required: true
90
+ default: 3000
91
+ sensitive: false
92
+ DEBUG:
93
+ type: bool
94
+ required: false
95
+ default: false
96
+ sensitive: false
97
+ ```
98
+
99
+ Values live in your local vault — never in this file.
100
+
101
+ ---
102
+
103
+ ## Profiles
104
+
105
+ Work with multiple environments locally without managing multiple files:
106
+
107
+ ```bash
108
+ # set up dev values once
109
+ envctl --profile dev fill
110
+
111
+ # check what staging would look like
112
+ envctl --profile staging check
113
+
114
+ # run against staging values
115
+ envctl --profile staging run -- python app.py
116
+ ```
117
+
118
+ Profile selection precedence:
119
+
120
+ 1. `--profile` flag
121
+ 2. `ENVCTL_PROFILE` environment variable
122
+ 3. `default_profile` in config
123
+ 4. `local` (default)
124
+
125
+ Profiles are independent value namespaces. They share the same contract. There is no hidden inheritance between profiles.
126
+
127
+ ---
128
+
129
+ ## Team workflow
130
+
131
+ The contract is shared. Values are local.
132
+
133
+ ```bash
134
+ # developer A: add a new variable
135
+ envctl add API_KEY sk-abc123
136
+ git add .envctl.schema.yaml
137
+ git commit -m "require API_KEY"
138
+
139
+ # developer B: pull and fill
140
+ git pull
141
+ envctl check # → shows API_KEY is missing
142
+ envctl fill # → prompts for API_KEY only
143
+ ```
144
+
145
+ ---
146
+
147
+ ## CI workflow
148
+
149
+ ```bash
150
+ # validate only — no mutation allowed
151
+ ENVCTL_RUNTIME_MODE=ci envctl check
152
+
153
+ # or with a ci profile
154
+ ENVCTL_PROFILE=ci ENVCTL_RUNTIME_MODE=ci envctl check
155
+ ```
156
+
157
+ In `ci` runtime mode, all mutation commands (`add`, `set`, `fill`, `sync`, etc.) are blocked. Read-only commands work normally.
158
+
159
+ ---
160
+
161
+ ## Common commands
162
+
163
+ ```bash
164
+ # validation and visibility
165
+ envctl check # validate against contract
166
+ envctl inspect # show resolved values (sensitive masked)
167
+ envctl explain DATABASE_URL # trace where a value comes from
168
+ envctl status # project readiness summary
169
+ envctl doctor # local environment diagnostics
170
+
171
+ # value management
172
+ envctl add DATABASE_URL <value> # add to contract + active profile
173
+ envctl set PORT 4000 # update active profile value only
174
+ envctl unset PORT # remove from active profile only
175
+ envctl remove PORT # remove from contract + all profiles
176
+
177
+ # projection
178
+ envctl run -- <command> # inject environment into subprocess
179
+ envctl sync # write .env.local as generated artifact
180
+ envctl export # emit shell export lines
181
+
182
+ # profiles
183
+ envctl profile list
184
+ envctl profile create staging
185
+ envctl profile copy local staging
186
+ envctl profile remove staging --yes
187
+
188
+ # vault inspection
189
+ envctl vault show
190
+ envctl vault check
191
+ envctl vault path
192
+ envctl vault prune # remove keys not in contract
193
+
194
+ # project identity
195
+ envctl project bind <id>
196
+ envctl project rebind
197
+ envctl project repair
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Machine-readable output
203
+
204
+ All read commands support `--json`:
205
+
206
+ ```bash
207
+ envctl --json check
208
+ envctl --json status
209
+ envctl --json inspect
210
+ envctl --json doctor
211
+ ```
212
+
213
+ ```json
214
+ {
215
+ "ok": true,
216
+ "command": "check",
217
+ "data": {
218
+ "active_profile": "local",
219
+ "report": {
220
+ "is_valid": true,
221
+ "missing_required": [],
222
+ "values": { "DATABASE_URL": { "source": "vault", "masked": true, ... } }
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Design principles
231
+
232
+ - Contract-first: the repo declares requirements; machines provide values
233
+ - Deterministic resolution: same inputs always produce the same environment
234
+ - Explicit over implicit: nothing happens without a command
235
+ - Local-first: no required cloud dependency
236
+ - Generated files are artifacts, not sources of truth
237
+ - Profiles are value namespaces, not contract variants
238
+ - CI mode is policy, not a profile
239
+
240
+ ---
241
+
242
+ ## Security model
243
+
244
+ - The contract contains **no secrets** — it is safe to commit
245
+ - Secrets never leave your machine unless you explicitly project them
246
+ - `.env.local` is optional and disposable — regenerate it any time
247
+ - Sensitive values are masked in all normal output
248
+ - Read-only commands never mutate state
249
+ - Local vault files are stored with `0600` permissions by default
250
+
251
+ Important: `envctl` is a local tool. It assumes a trusted machine. If your account or machine is compromised, your local vault is compromised. envctl is not a replacement for a team secrets manager when shared access control is required.
252
+
253
+ ---
254
+
255
+ ## Documentation
256
+
257
+ - [Quickstart](https://github.com/alessbarb/envctl/blob/main/docs/getting-started/quickstart.md)
258
+ - [Mental model](https://github.com/alessbarb/envctl/blob/main/docs/getting-started/mental-model.md)
259
+ - [Commands reference](https://github.com/alessbarb/envctl/blob/main/docs/reference/commands.md)
260
+ - [Profiles reference](https://github.com/alessbarb/envctl/blob/main/docs/reference/profiles.md)
261
+ - [CI workflow](https://github.com/alessbarb/envctl/blob/main/docs/workflows/ci.md)
262
+ - [Team workflow](https://github.com/alessbarb/envctl/blob/main/docs/workflows/team.md)
263
+ - [Security](https://github.com/alessbarb/envctl/blob/main/docs/reference/security.md)
264
+ - [Internal architecture](https://github.com/alessbarb/envctl/blob/main/docs/internals/architecture.md)