vaulter 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/dist/bin/minienv-mcp.cjs +17 -0
- package/dist/bin/minienv.cjs +146 -0
- package/dist/cli/commands/delete.d.ts +22 -0
- package/dist/cli/commands/delete.d.ts.map +1 -0
- package/dist/cli/commands/delete.js +85 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/export.d.ts +21 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +126 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/get.d.ts +21 -0
- package/dist/cli/commands/get.d.ts.map +1 -0
- package/dist/cli/commands/get.js +62 -0
- package/dist/cli/commands/get.js.map +1 -0
- package/dist/cli/commands/init.d.ts +20 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +98 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/integrations/helm.d.ts +21 -0
- package/dist/cli/commands/integrations/helm.d.ts.map +1 -0
- package/dist/cli/commands/integrations/helm.js +113 -0
- package/dist/cli/commands/integrations/helm.js.map +1 -0
- package/dist/cli/commands/integrations/kubernetes.d.ts +25 -0
- package/dist/cli/commands/integrations/kubernetes.d.ts.map +1 -0
- package/dist/cli/commands/integrations/kubernetes.js +199 -0
- package/dist/cli/commands/integrations/kubernetes.js.map +1 -0
- package/dist/cli/commands/integrations/terraform.d.ts +25 -0
- package/dist/cli/commands/integrations/terraform.d.ts.map +1 -0
- package/dist/cli/commands/integrations/terraform.js +131 -0
- package/dist/cli/commands/integrations/terraform.js.map +1 -0
- package/dist/cli/commands/key.d.ts +19 -0
- package/dist/cli/commands/key.d.ts.map +1 -0
- package/dist/cli/commands/key.js +247 -0
- package/dist/cli/commands/key.js.map +1 -0
- package/dist/cli/commands/list.d.ts +21 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +94 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +22 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +142 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/push.d.ts +22 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +181 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/services.d.ts +18 -0
- package/dist/cli/commands/services.d.ts.map +1 -0
- package/dist/cli/commands/services.js +92 -0
- package/dist/cli/commands/services.js.map +1 -0
- package/dist/cli/commands/set.d.ts +22 -0
- package/dist/cli/commands/set.d.ts.map +1 -0
- package/dist/cli/commands/set.js +93 -0
- package/dist/cli/commands/set.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +23 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +362 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +266 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lib/create-client.d.ts +21 -0
- package/dist/cli/lib/create-client.d.ts.map +1 -0
- package/dist/cli/lib/create-client.js +68 -0
- package/dist/cli/lib/create-client.js.map +1 -0
- package/dist/cli/lib/hooks.d.ts +5 -0
- package/dist/cli/lib/hooks.d.ts.map +1 -0
- package/dist/cli/lib/hooks.js +17 -0
- package/dist/cli/lib/hooks.js.map +1 -0
- package/dist/cli/preload.d.ts +6 -0
- package/dist/cli/preload.d.ts.map +1 -0
- package/dist/cli/preload.js +7 -0
- package/dist/cli/preload.js.map +1 -0
- package/dist/client.d.ts +89 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +350 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/batch-runner.d.ts +39 -0
- package/dist/lib/batch-runner.d.ts.map +1 -0
- package/dist/lib/batch-runner.js +117 -0
- package/dist/lib/batch-runner.js.map +1 -0
- package/dist/lib/config-loader.d.ts +45 -0
- package/dist/lib/config-loader.d.ts.map +1 -0
- package/dist/lib/config-loader.js +357 -0
- package/dist/lib/config-loader.js.map +1 -0
- package/dist/lib/env-parser.d.ts +38 -0
- package/dist/lib/env-parser.d.ts.map +1 -0
- package/dist/lib/env-parser.js +281 -0
- package/dist/lib/env-parser.js.map +1 -0
- package/dist/lib/monorepo.d.ts +39 -0
- package/dist/lib/monorepo.d.ts.map +1 -0
- package/dist/lib/monorepo.js +181 -0
- package/dist/lib/monorepo.js.map +1 -0
- package/dist/lib/pattern-matcher.d.ts +5 -0
- package/dist/lib/pattern-matcher.d.ts.map +1 -0
- package/dist/lib/pattern-matcher.js +18 -0
- package/dist/lib/pattern-matcher.js.map +1 -0
- package/dist/lib/s3-key-loader.d.ts +33 -0
- package/dist/lib/s3-key-loader.d.ts.map +1 -0
- package/dist/lib/s3-key-loader.js +83 -0
- package/dist/lib/s3-key-loader.js.map +1 -0
- package/dist/lib/secret-patterns.d.ts +10 -0
- package/dist/lib/secret-patterns.d.ts.map +1 -0
- package/dist/lib/secret-patterns.js +27 -0
- package/dist/lib/secret-patterns.js.map +1 -0
- package/dist/load.d.ts +14 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +15 -0
- package/dist/load.js.map +1 -0
- package/dist/loader.d.ts +63 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +50 -0
- package/dist/loader.js.map +1 -0
- package/dist/mcp/index.d.ts +8 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +14 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/preload.d.ts +6 -0
- package/dist/mcp/preload.d.ts.map +1 -0
- package/dist/mcp/preload.js +7 -0
- package/dist/mcp/preload.js.map +1 -0
- package/dist/mcp/resources.d.ts +23 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +112 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +20 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +405 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +37 -0
- package/dist/types.js.map +1 -0
- package/package.json +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Forattini
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🔐 vaulter
|
|
4
|
+
|
|
5
|
+
### Multi-Backend Environment & Secrets Manager
|
|
6
|
+
|
|
7
|
+
**One CLI to manage all your environment variables.**
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g vaulter
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Initialize project
|
|
19
|
+
vaulter init
|
|
20
|
+
|
|
21
|
+
# Set some secrets
|
|
22
|
+
vaulter set DATABASE_URL "postgres://localhost/mydb" -e dev
|
|
23
|
+
vaulter set API_KEY "sk-secret-key" -e dev
|
|
24
|
+
|
|
25
|
+
# Export to shell
|
|
26
|
+
eval $(vaulter export -e dev)
|
|
27
|
+
|
|
28
|
+
# Deploy to Kubernetes
|
|
29
|
+
vaulter k8s:secret -e prd | kubectl apply -f -
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
<div align="center">
|
|
35
|
+
|
|
36
|
+
[](https://www.npmjs.com/package/vaulter)
|
|
37
|
+
[](https://www.typescriptlang.org/)
|
|
38
|
+
[](https://nodejs.org/)
|
|
39
|
+
[](https://github.com/forattini-dev/vaulter/blob/main/LICENSE)
|
|
40
|
+
|
|
41
|
+
Store secrets anywhere: AWS S3, MinIO, R2, Spaces, B2, or local filesystem.
|
|
42
|
+
<br>
|
|
43
|
+
AES-256-GCM encryption. Native K8s, Helm & Terraform integration.
|
|
44
|
+
<br>
|
|
45
|
+
MCP server for Claude AI. Zero config for dev, production-ready.
|
|
46
|
+
|
|
47
|
+
[📖 Documentation](#configuration) · [🔧 CLI](#commands) · [🚀 Highlights](#highlights)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## What's Inside
|
|
54
|
+
|
|
55
|
+
| Category | Features |
|
|
56
|
+
|:---------|:---------|
|
|
57
|
+
| **Backends** | AWS S3, MinIO, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, FileSystem, Memory |
|
|
58
|
+
| **Encryption** | AES-256-GCM via s3db.js, field-level encryption, key rotation |
|
|
59
|
+
| **Environments** | dev, stg, prd, sbx, dr (fully customizable) |
|
|
60
|
+
| **Integrations** | Kubernetes Secret/ConfigMap, Helm values.yaml, Terraform tfvars |
|
|
61
|
+
| **Monorepo** | Service discovery, batch operations, config inheritance |
|
|
62
|
+
| **MCP Server** | Claude AI integration via Model Context Protocol |
|
|
63
|
+
| **Unix Pipes** | Full stdin/stdout support for scripting |
|
|
64
|
+
| **Dotenv** | Drop-in compatible: `import 'vaulter/load'` |
|
|
65
|
+
|
|
66
|
+
## Highlights
|
|
67
|
+
|
|
68
|
+
### Multi-Backend with Fallback
|
|
69
|
+
|
|
70
|
+
Configure multiple backends - vaulter tries each until one succeeds:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
backend:
|
|
74
|
+
urls:
|
|
75
|
+
- s3://bucket/envs?region=us-east-1 # Primary (CI/CD)
|
|
76
|
+
- file:///home/user/.vaulter-store # Fallback (local dev)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Native Integrations
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Kubernetes - deploy secrets directly
|
|
83
|
+
vaulter k8s:secret -e prd | kubectl apply -f -
|
|
84
|
+
|
|
85
|
+
# Helm - generate values file
|
|
86
|
+
vaulter helm:values -e prd | helm upgrade myapp ./chart -f -
|
|
87
|
+
|
|
88
|
+
# Terraform - export as tfvars
|
|
89
|
+
vaulter tf:vars -e prd > terraform.tfvars
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Unix Pipes
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Import from Vault
|
|
96
|
+
vault kv get -format=json secret/app | \
|
|
97
|
+
jq -r '.data.data | to_entries | .[] | "\(.key)=\(.value)"' | \
|
|
98
|
+
vaulter sync -e prd
|
|
99
|
+
|
|
100
|
+
# Export to kubectl
|
|
101
|
+
vaulter export -e prd --format=env | \
|
|
102
|
+
kubectl create secret generic myapp --from-env-file=/dev/stdin
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### MCP Server for Claude
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Start MCP server
|
|
109
|
+
vaulter mcp
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"mcpServers": {
|
|
115
|
+
"vaulter": {
|
|
116
|
+
"command": "npx",
|
|
117
|
+
"args": ["vaulter", "mcp"]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Dotenv Compatible
|
|
124
|
+
|
|
125
|
+
Drop-in replacement for dotenv - works with your existing setup:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Auto-load .env into process.env
|
|
129
|
+
import 'vaulter/load'
|
|
130
|
+
|
|
131
|
+
// Or programmatically with options
|
|
132
|
+
import { loader } from 'vaulter'
|
|
133
|
+
loader({ path: '.env.local', override: true })
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
### Core
|
|
139
|
+
|
|
140
|
+
| Command | Description | Example |
|
|
141
|
+
|:--------|:------------|:--------|
|
|
142
|
+
| `init` | Initialize project | `vaulter init` |
|
|
143
|
+
| `get <key>` | Get a variable | `vaulter get DATABASE_URL -e prd` |
|
|
144
|
+
| `set <key> <value>` | Set a variable | `vaulter set API_KEY "sk-..." -e prd` |
|
|
145
|
+
| `delete <key>` | Delete a variable | `vaulter delete OLD_KEY -e dev` |
|
|
146
|
+
| `list` | List all variables | `vaulter list -e prd` |
|
|
147
|
+
| `export` | Export for shell | `eval $(vaulter export -e dev)` |
|
|
148
|
+
|
|
149
|
+
### Sync
|
|
150
|
+
|
|
151
|
+
| Command | Description | Example |
|
|
152
|
+
|:--------|:------------|:--------|
|
|
153
|
+
| `sync` | Merge local .env and backend | `vaulter sync -f .env.local -e dev` |
|
|
154
|
+
| `pull` | Download to .env | `vaulter pull -e prd -o .env.prd` |
|
|
155
|
+
| `push` | Upload from .env | `vaulter push -f .env.local -e dev` |
|
|
156
|
+
|
|
157
|
+
### Integrations
|
|
158
|
+
|
|
159
|
+
| Command | Description | Example |
|
|
160
|
+
|:--------|:------------|:--------|
|
|
161
|
+
| `k8s:secret` | Kubernetes Secret | `vaulter k8s:secret -e prd \| kubectl apply -f -` |
|
|
162
|
+
| `k8s:configmap` | Kubernetes ConfigMap | `vaulter k8s:configmap -e prd` |
|
|
163
|
+
| `helm:values` | Helm values.yaml | `vaulter helm:values -e prd` |
|
|
164
|
+
| `tf:vars` | Terraform .tfvars | `vaulter tf:vars -e prd > terraform.tfvars` |
|
|
165
|
+
| `tf:json` | Terraform JSON | `vaulter tf:json -e prd` |
|
|
166
|
+
|
|
167
|
+
### Utilities
|
|
168
|
+
|
|
169
|
+
| Command | Description | Example |
|
|
170
|
+
|:--------|:------------|:--------|
|
|
171
|
+
| `key generate` | Generate encryption key | `vaulter key generate` |
|
|
172
|
+
| `services` | List monorepo services | `vaulter services` |
|
|
173
|
+
| `mcp` | Start MCP server | `vaulter mcp` |
|
|
174
|
+
|
|
175
|
+
## Global Options
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
-p, --project <name> Project name
|
|
179
|
+
-s, --service <name> Service name (monorepos)
|
|
180
|
+
-e, --env <env> Environment: dev, stg, prd, sbx, dr
|
|
181
|
+
-b, --backend <url> Backend URL override
|
|
182
|
+
-k, --key <path|value> Encryption key file path or raw key
|
|
183
|
+
-v, --verbose Verbose output
|
|
184
|
+
--all All services (monorepo batch)
|
|
185
|
+
--dry-run Preview without applying
|
|
186
|
+
--json JSON output
|
|
187
|
+
--force Skip confirmations
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
### Basic Config
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
# .vaulter/config.yaml
|
|
196
|
+
version: "1"
|
|
197
|
+
|
|
198
|
+
project: my-project
|
|
199
|
+
service: api # optional
|
|
200
|
+
|
|
201
|
+
backend:
|
|
202
|
+
# Single URL
|
|
203
|
+
url: s3://bucket/envs?region=us-east-1
|
|
204
|
+
|
|
205
|
+
# Or multiple with fallback
|
|
206
|
+
urls:
|
|
207
|
+
- s3://bucket/envs?region=us-east-1
|
|
208
|
+
- file:///home/user/.vaulter-store
|
|
209
|
+
|
|
210
|
+
encryption:
|
|
211
|
+
key_source:
|
|
212
|
+
- env: VAULTER_KEY # 1. Environment variable
|
|
213
|
+
- file: .vaulter/.key # 2. Local file
|
|
214
|
+
- s3: s3://keys/vaulter.key # 3. Remote S3
|
|
215
|
+
|
|
216
|
+
environments:
|
|
217
|
+
- dev
|
|
218
|
+
- stg
|
|
219
|
+
- prd
|
|
220
|
+
|
|
221
|
+
default_environment: dev
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Sync Settings
|
|
225
|
+
|
|
226
|
+
Sync merges local and remote variables. Conflicts are resolved by `sync.conflict`.
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
sync:
|
|
230
|
+
conflict: local # local | remote | prompt | error
|
|
231
|
+
ignore:
|
|
232
|
+
- "PUBLIC_*"
|
|
233
|
+
required:
|
|
234
|
+
dev:
|
|
235
|
+
- DATABASE_URL
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Notes:
|
|
239
|
+
- `prompt` and `error` will stop the sync if conflicts are detected.
|
|
240
|
+
- When reading from stdin, sync only updates the backend (local file is not changed).
|
|
241
|
+
|
|
242
|
+
### Hooks
|
|
243
|
+
|
|
244
|
+
```yaml
|
|
245
|
+
hooks:
|
|
246
|
+
pre_sync: "echo pre sync"
|
|
247
|
+
post_sync: "echo post sync"
|
|
248
|
+
pre_pull: "echo pre pull"
|
|
249
|
+
post_pull: "echo post pull"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Environment Variable Expansion
|
|
253
|
+
|
|
254
|
+
Config values support `${VAR}`, `${VAR:-default}`, and `$VAR`:
|
|
255
|
+
|
|
256
|
+
```yaml
|
|
257
|
+
backend:
|
|
258
|
+
url: s3://${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}@bucket/envs
|
|
259
|
+
# Or
|
|
260
|
+
url: ${VAULTER_BACKEND_URL}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Local Override (config.local.yaml)
|
|
264
|
+
|
|
265
|
+
For credentials that should **never** be committed:
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
# .vaulter/config.local.yaml (gitignored)
|
|
269
|
+
backend:
|
|
270
|
+
url: s3://real-key:real-secret@bucket/envs?region=us-east-1
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Backend URLs
|
|
274
|
+
|
|
275
|
+
| Provider | URL Format |
|
|
276
|
+
|:---------|:-----------|
|
|
277
|
+
| **AWS S3** | `s3://bucket/path?region=us-east-1` |
|
|
278
|
+
| **AWS S3 + Profile** | `s3://bucket/path?region=us-east-1&profile=myprofile` |
|
|
279
|
+
| **AWS S3 + Credentials** | `s3://${KEY}:${SECRET}@bucket/path` |
|
|
280
|
+
| **MinIO** | `http://${KEY}:${SECRET}@localhost:9000/bucket` |
|
|
281
|
+
| **Cloudflare R2** | `https://${KEY}:${SECRET}@${ACCOUNT}.r2.cloudflarestorage.com/bucket` |
|
|
282
|
+
| **DigitalOcean Spaces** | `https://${KEY}:${SECRET}@nyc3.digitaloceanspaces.com/bucket` |
|
|
283
|
+
| **Backblaze B2** | `https://${KEY}:${SECRET}@s3.us-west-002.backblazeb2.com/bucket` |
|
|
284
|
+
| **FileSystem** | `file:///path/to/storage` |
|
|
285
|
+
| **Memory** | `memory://bucket-name` |
|
|
286
|
+
|
|
287
|
+
## Encryption
|
|
288
|
+
|
|
289
|
+
All secrets are encrypted with **AES-256-GCM** before storage.
|
|
290
|
+
|
|
291
|
+
### Key Sources
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# 1. Environment variable (CI/CD)
|
|
295
|
+
export VAULTER_KEY="base64-encoded-32-byte-key"
|
|
296
|
+
vaulter export -e prd
|
|
297
|
+
|
|
298
|
+
# 2. Local file (development)
|
|
299
|
+
vaulter key generate -o .vaulter/.key
|
|
300
|
+
|
|
301
|
+
# 3. Remote S3 (production)
|
|
302
|
+
# Configured in config.yaml
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
You can also pass a key directly:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
vaulter list -e prd --key .vaulter/.key
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Security Settings
|
|
312
|
+
|
|
313
|
+
```yaml
|
|
314
|
+
security:
|
|
315
|
+
paranoid: true # Fail if no encryption key is found
|
|
316
|
+
auto_encrypt:
|
|
317
|
+
patterns:
|
|
318
|
+
- "*_KEY"
|
|
319
|
+
- "*_SECRET"
|
|
320
|
+
- "DATABASE_URL"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`auto_encrypt.patterns` is used to classify secrets for integrations (K8s/Helm).
|
|
324
|
+
|
|
325
|
+
## Monorepo Support
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
my-monorepo/
|
|
329
|
+
├── .vaulter/
|
|
330
|
+
│ ├── config.yaml # Root config
|
|
331
|
+
│ └── environments/
|
|
332
|
+
├── services/
|
|
333
|
+
│ ├── api/
|
|
334
|
+
│ │ └── .vaulter/
|
|
335
|
+
│ │ ├── config.yaml # extends: ../../../.vaulter/config.yaml
|
|
336
|
+
│ │ └── environments/
|
|
337
|
+
│ └── worker/
|
|
338
|
+
│ └── .vaulter/
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# List services
|
|
343
|
+
vaulter services
|
|
344
|
+
|
|
345
|
+
# Sync all services
|
|
346
|
+
vaulter sync -e dev --all
|
|
347
|
+
|
|
348
|
+
# Sync specific services
|
|
349
|
+
vaulter sync -e dev -s api,worker
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## MCP Tools
|
|
353
|
+
|
|
354
|
+
| Tool | Description |
|
|
355
|
+
|:-----|:------------|
|
|
356
|
+
| `vaulter_get` | Get a single variable |
|
|
357
|
+
| `vaulter_set` | Set a variable |
|
|
358
|
+
| `vaulter_delete` | Delete a variable |
|
|
359
|
+
| `vaulter_list` | List all variables |
|
|
360
|
+
| `vaulter_export` | Export in various formats |
|
|
361
|
+
| `vaulter_sync` | Sync with .env file |
|
|
362
|
+
|
|
363
|
+
## CI/CD
|
|
364
|
+
|
|
365
|
+
### GitHub Actions
|
|
366
|
+
|
|
367
|
+
```yaml
|
|
368
|
+
jobs:
|
|
369
|
+
deploy:
|
|
370
|
+
runs-on: ubuntu-latest
|
|
371
|
+
steps:
|
|
372
|
+
- uses: actions/checkout@v4
|
|
373
|
+
- name: Deploy secrets
|
|
374
|
+
env:
|
|
375
|
+
VAULTER_KEY: ${{ secrets.VAULTER_KEY }}
|
|
376
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
377
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
378
|
+
run: |
|
|
379
|
+
npx vaulter k8s:secret -e prd | kubectl apply -f -
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### GitLab CI
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
deploy:
|
|
386
|
+
script:
|
|
387
|
+
- npx vaulter k8s:secret -e prd | kubectl apply -f -
|
|
388
|
+
variables:
|
|
389
|
+
VAULTER_KEY: $VAULTER_KEY
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Security Best Practices
|
|
393
|
+
|
|
394
|
+
| Practice | How |
|
|
395
|
+
|:---------|:----|
|
|
396
|
+
| Never commit credentials | Use `config.local.yaml` or env vars |
|
|
397
|
+
| Never commit encryption keys | Add `.vaulter/.key` to `.gitignore` |
|
|
398
|
+
| Use env var expansion | `${AWS_ACCESS_KEY_ID}` instead of hardcoding |
|
|
399
|
+
| Use AWS credential chain | No credentials in URL, use IAM roles |
|
|
400
|
+
| Separate keys per environment | Different keys for dev/stg/prd |
|
|
401
|
+
| Restrict S3 bucket access | IAM policies to limit readers |
|
|
402
|
+
|
|
403
|
+
### Files to .gitignore
|
|
404
|
+
|
|
405
|
+
```gitignore
|
|
406
|
+
.vaulter/.key
|
|
407
|
+
.vaulter/config.local.yaml
|
|
408
|
+
**/config.local.yaml
|
|
409
|
+
.env
|
|
410
|
+
.env.*
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## API Usage
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { VaulterClient, loadConfig } from 'vaulter'
|
|
417
|
+
|
|
418
|
+
const config = loadConfig()
|
|
419
|
+
const client = new VaulterClient({ config })
|
|
420
|
+
|
|
421
|
+
await client.connect()
|
|
422
|
+
|
|
423
|
+
// Get
|
|
424
|
+
const value = await client.get('DATABASE_URL', 'my-project', 'prd')
|
|
425
|
+
|
|
426
|
+
// Set
|
|
427
|
+
await client.set({
|
|
428
|
+
key: 'API_KEY',
|
|
429
|
+
value: 'sk-secret',
|
|
430
|
+
project: 'my-project',
|
|
431
|
+
environment: 'prd'
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
// List
|
|
435
|
+
const vars = await client.list({
|
|
436
|
+
project: 'my-project',
|
|
437
|
+
environment: 'prd'
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
// Export
|
|
441
|
+
const envVars = await client.export('my-project', 'prd')
|
|
442
|
+
|
|
443
|
+
await client.disconnect()
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Comparison
|
|
447
|
+
|
|
448
|
+
| Feature | vaulter | dotenv | doppler | vault |
|
|
449
|
+
|:--------|:-------:|:------:|:-------:|:-----:|
|
|
450
|
+
| Multi-backend | ✅ | ❌ | ❌ | ❌ |
|
|
451
|
+
| Encryption | AES-256-GCM | ❌ | ✅ | ✅ |
|
|
452
|
+
| K8s integration | Native | ❌ | Plugin | Plugin |
|
|
453
|
+
| Self-hosted | ✅ | N/A | ❌ | ✅ |
|
|
454
|
+
| Monorepo | Native | ❌ | Limited | ❌ |
|
|
455
|
+
| MCP/AI | ✅ | ❌ | ❌ | ❌ |
|
|
456
|
+
| Complexity | Low | Low | Medium | High |
|
|
457
|
+
|
|
458
|
+
## Numbers
|
|
459
|
+
|
|
460
|
+
| Metric | Value |
|
|
461
|
+
|:-------|:------|
|
|
462
|
+
| Backends | 7 (S3, MinIO, R2, Spaces, B2, FileSystem, Memory) |
|
|
463
|
+
| Environments | 5 (dev, stg, prd, sbx, dr) |
|
|
464
|
+
| Export Formats | 5 (shell, json, yaml, env, tfvars) |
|
|
465
|
+
| MCP Tools | 6 |
|
|
466
|
+
| Integrations | 5 (K8s Secret, K8s ConfigMap, Helm, Terraform, tfvars) |
|
|
467
|
+
|
|
468
|
+
## Pre-built Binaries
|
|
469
|
+
|
|
470
|
+
Download from [Releases](https://github.com/forattini-dev/vaulter/releases):
|
|
471
|
+
|
|
472
|
+
| Platform | Binary |
|
|
473
|
+
|:---------|:-------|
|
|
474
|
+
| Linux x64 | `vaulter-linux` |
|
|
475
|
+
| Linux ARM64 | `vaulter-linux-arm64` |
|
|
476
|
+
| macOS x64 | `vaulter-macos` |
|
|
477
|
+
| macOS ARM64 | `vaulter-macos-arm64` |
|
|
478
|
+
| Windows | `vaulter-win.exe` |
|
|
479
|
+
|
|
480
|
+
## License
|
|
481
|
+
|
|
482
|
+
MIT © [Forattini](https://github.com/forattini-dev)
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
<div align="center">
|
|
487
|
+
|
|
488
|
+
**[Documentation](#configuration)** · **[Issues](https://github.com/forattini-dev/vaulter/issues)** · **[Releases](https://github.com/forattini-dev/vaulter/releases)**
|
|
489
|
+
|
|
490
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var Q=Object.create;var _=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var ee=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty;var te=(n,e,r,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of J(e))!ne.call(n,i)&&i!==r&&_(n,i,{get:()=>e[i],enumerable:!(t=X(e,i))||t.enumerable});return n};var E=(n,e,r)=>(r=n!=null?Q(ee(n)):{},te(e||!n||!n.__esModule?_(r,"default",{value:n,enumerable:!0}):r,n));process.setMaxListeners(50);var G=require("@modelcontextprotocol/sdk/server/index.js"),Z=require("@modelcontextprotocol/sdk/server/stdio.js"),p=require("@modelcontextprotocol/sdk/types.js");var I=require("s3db.js"),re=`file://${process.env.HOME||"/tmp"}/.minienv/store`,ie="minienv-default-dev-key",g=class{db=null;resource=null;connectionString;passphrase;initialized=!1;constructor(e={}){this.connectionString=e.connectionString||re,this.passphrase=e.passphrase||ie}async connect(){this.initialized||(this.db=new I.S3db({connectionString:this.connectionString,passphrase:this.passphrase}),await this.db.connect(),this.resource=await this.db.createResource({name:"environment-variables",attributes:{key:"string|required",value:"secret|required",project:"string|required",service:"string|optional",environment:"enum:dev,stg,prd,sbx,dr|required",tags:"array|items:string|optional",metadata:{description:"string|optional",owner:"string|optional",rotateAfter:"date|optional",source:"enum:manual,sync,import|optional"}},partitions:{byProject:{fields:{project:"string"}},byProjectEnv:{fields:{project:"string",environment:"string"}},byProjectServiceEnv:{fields:{project:"string",service:"string",environment:"string"}}},behavior:"body-overflow",timestamps:!0,asyncPartitions:!0}),this.initialized=!0)}ensureConnected(){if(!this.initialized||!this.resource)throw new Error("MiniEnvClient not initialized. Call connect() first.")}async get(e,r,t,i){this.ensureConnected();let s=i?"byProjectServiceEnv":"byProjectEnv",o=i?{project:r,service:i,environment:t}:{project:r,environment:t};return(await this.resource.list({partition:s,partitionValues:o})).find(l=>l.key===e)||null}async set(e){this.ensureConnected();let r=await this.get(e.key,e.project,e.environment,e.service);return r?await this.resource.update(r.id,{value:e.value,tags:e.tags,metadata:{...r.metadata,...e.metadata,source:e.metadata?.source||"manual"}}):await this.resource.insert({...e,metadata:{...e.metadata,source:e.metadata?.source||"manual"}})}async delete(e,r,t,i){this.ensureConnected();let s=await this.get(e,r,t,i);return s?(await this.resource.delete(s.id),!0):!1}async list(e={}){this.ensureConnected();let{project:r,service:t,environment:i,limit:s,offset:o}=e,a,c;r&&t&&i?(a="byProjectServiceEnv",c={project:r,service:t,environment:i}):r&&i?(a="byProjectEnv",c={project:r,environment:i}):r&&(a="byProject",c={project:r});let l={};return a&&c&&(l.partition=a,l.partitionValues=c),s&&(l.limit=s),o&&(l.offset=o),await this.resource.list(l)}async insertMany(e){this.ensureConnected();let r=[];for(let t of e){let i=await this.set(t);r.push(i)}return r}async deleteAll(e,r,t){this.ensureConnected();let i=await this.list({project:e,environment:r,service:t}),s=0;for(let o of i)await this.resource.delete(o.id),s++;return s}async export(e,r,t){let i=await this.list({project:e,environment:r,service:t}),s={};for(let o of i)s[o.key]=o.value;return s}async sync(e,r,t,i,s={}){this.ensureConnected();let o=await this.list({project:r,environment:t,service:i}),a=new Map(o.map(l=>[l.key,l])),c={added:[],updated:[],deleted:[],unchanged:[],conflicts:[]};for(let[l,d]of Object.entries(e)){let u=a.get(l);u?u.value!==d?(await this.set({key:l,value:d,project:r,environment:t,service:i,metadata:{source:s.source||"sync"}}),c.updated.push(l)):c.unchanged.push(l):(await this.set({key:l,value:d,project:r,environment:t,service:i,metadata:{source:s.source||"sync"}}),c.added.push(l)),a.delete(l)}for(let[l]of a)c.deleted.push(l);return c}async disconnect(){this.db&&(await this.db.disconnect(),this.db=null,this.resource=null,this.initialized=!1)}isConnected(){return this.initialized}getConnectionString(){return this.connectionString}};var v=E(require("node:fs"),1),f=E(require("node:path"),1),A=require("yaml");function se(n){let e=new URL(n);if(e.protocol==="s3:"){let r=e.hostname,t=e.pathname.slice(1),i=e.searchParams.get("region")||void 0;return{bucket:r,key:t,region:i}}if(e.protocol==="http:"||e.protocol==="https:"){let r=`${e.protocol}//${e.host}`,t=e.pathname.slice(1).split("/"),i=t[0],s=t.slice(1).join("/");return{bucket:i,key:s,endpoint:r,accessKeyId:e.username||void 0,secretAccessKey:e.password||void 0}}throw new Error(`Unsupported S3 URL format: ${n}`)}async function oe(n){try{let{S3Client:e,GetObjectCommand:r}=await import("@aws-sdk/client-s3"),t={};n.region&&(t.region=n.region),n.endpoint&&(t.endpoint=n.endpoint,t.forcePathStyle=!0),n.accessKeyId&&n.secretAccessKey&&(t.credentials={accessKeyId:n.accessKeyId,secretAccessKey:n.secretAccessKey});let i=new e(t),s=new r({Bucket:n.bucket,Key:n.key}),o=await i.send(s);if(!o.Body)throw new Error("Empty response from S3");return(await o.Body.transformToString()).trim()}catch(e){throw e.code==="ERR_MODULE_NOT_FOUND"||e.message?.includes("Cannot find module")?new Error("AWS SDK not installed. To use S3 key source, install: pnpm add @aws-sdk/client-s3"):new Error(`Failed to fetch key from S3: ${e.message}`)}}async function T(n){let e=se(n);return oe(e)}var ae=".minienv",O="config.yaml",ce=5,M=10,V={version:"1",project:"",environments:["dev","stg","prd","sbx","dr"],default_environment:"dev",sync:{conflict:"local"},security:{paranoid:!1,confirm_production:!0,auto_encrypt:{patterns:["*_KEY","*_SECRET","*_TOKEN","*_PASSWORD","*_CREDENTIAL","DATABASE_URL","REDIS_URL"]}}};function $(n=process.cwd()){let e=f.default.resolve(n),r=0;for(;r<ce;){let t=f.default.join(e,ae),i=f.default.join(t,O);if(v.default.existsSync(i))return t;let s=f.default.dirname(e);if(s===e)break;e=s,r++}return null}function le(n){if(!v.default.existsSync(n))throw new Error(`Config file not found: ${n}`);let e=v.default.readFileSync(n,"utf-8");return(0,A.parse)(e)||{}}function C(n,e){let r={...n};for(let t of Object.keys(e)){let i=e[t],s=r[t];i!=null&&typeof i=="object"&&!Array.isArray(i)&&s!==null&&s!==void 0&&typeof s=="object"&&!Array.isArray(s)?r[t]=C(s,i):i!==void 0&&(r[t]=i)}return r}function N(n,e=new Set,r=0){if(r>M)throw new Error(`Config inheritance depth exceeded (max ${M})`);let t=f.default.resolve(n);if(e.has(t))throw new Error(`Circular config inheritance detected: ${t}`);e.add(t);let i=le(t);if(i.extends){let s=f.default.resolve(f.default.dirname(t),i.extends),o=N(s,e,r+1),{extends:a,...c}=i;return C(o,c)}return C(V,i)}function w(n){let e=$(n);if(!e)return{...V};let r=f.default.join(e,O);return N(r)}function de(n){return f.default.join(n,"environments")}function D(n,e){return f.default.join(de(n),`${e}.env`)}async function j(n){let e=n.encryption?.key_source||[];for(let r of e)if("env"in r){let t=process.env[r.env];if(t)return t}else if("file"in r){let t=f.default.resolve(r.file);if(v.default.existsSync(t))return v.default.readFileSync(t,"utf-8").trim()}else if("s3"in r)try{let t=await T(r.s3);if(t)return t}catch(t){process.env.MINIENV_VERBOSE&&console.warn(`Failed to load key from S3: ${t.message}`)}return process.env.MINIENV_KEY?process.env.MINIENV_KEY:null}var K=E(require("node:fs"),1),P=100;function F(n,e={}){let r=K.default.readFileSync(n,"utf-8");return ue(r,e)}function ue(n,e={}){let{expand:r=!0,env:t=process.env}=e,i={},s=n.split(/\r?\n/),o=0;for(;o<s.length;){let a=s[o].trim();if(o++,!a||a.startsWith("#")||a.startsWith(";"))continue;let c=a.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/);if(!c)continue;let l=c[1],d=c[2];if(d.startsWith('"')){let u=pe(d,s,o);d=u.value,o=u.nextIndex}else if(d.startsWith("'")){let u=fe(d,s,o);d=u.value,o=u.nextIndex}else if(d.startsWith("`")){let u=me(d,s,o);d=u.value,o=u.nextIndex}else{let u=d.indexOf("#");u>0&&(d=d.substring(0,u).trim())}r&&(d=ge(d,{...t,...i})),i[l]=d}return i}function pe(n,e,r){let t=n.substring(1),i=0,s="",o=0,a=r;for(;i<P;){for(;o<t.length;){let c=t[o];if(c==="\\"&&o+1<t.length){let l=t[o+1];switch(l){case"n":s+=`
|
|
3
|
+
`;break;case"r":s+="\r";break;case"t":s+=" ";break;case'"':s+='"';break;case"\\":s+="\\";break;case"$":s+="$";break;default:s+=c+l}o+=2}else{if(c==='"')return{value:s,nextIndex:a};s+=c,o++}}if(a>=e.length)break;s+=`
|
|
4
|
+
`,t=e[a],a++,o=0,i++}return{value:s,nextIndex:a}}function fe(n,e,r){let t=n.substring(1),i=0,s="",o=0,a=r;for(;i<P;){for(;o<t.length;){let c=t[o];if(c==="'"&&t[o+1]!=="'")return{value:s,nextIndex:a};c==="'"&&t[o+1]==="'"?(s+="'",o+=2):(s+=c,o++)}if(a>=e.length)break;s+=`
|
|
5
|
+
`,t=e[a],a++,o=0,i++}return{value:s,nextIndex:a}}function me(n,e,r){let t=n.substring(1),i=0,s="",o=0,a=r;for(;i<P;){let c=t.indexOf("`",o);if(c!==-1)return s+=t.substring(o,c),{value:s,nextIndex:a};if(s+=t.substring(o),a>=e.length)break;s+=`
|
|
6
|
+
`,t=e[a],a++,o=0,i++}return{value:s,nextIndex:a}}function ge(n,e){return n=n.replace(/\$\{([a-zA-Z_][a-zA-Z0-9_]*):-([^}]*)\}/g,(r,t,i)=>e[t]??i),n=n.replace(/\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g,(r,t)=>e[t]??""),n=n.replace(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,(r,t)=>e[t]??""),n}var L=E(require("node:fs"),1);async function ve(){let n=null;try{n=w()}catch{}let e=n?.backend?.url,r=n?await j(n):void 0;return{client:new g({connectionString:e||void 0,passphrase:r||void 0}),config:n}}function U(){return[{name:"minienv_get",description:"Get the value of an environment variable",inputSchema:{type:"object",properties:{key:{type:"string",description:"The name of the environment variable"},environment:{type:"string",description:"Environment (dev/stg/prd/sbx/dr)",enum:["dev","stg","prd","sbx","dr"],default:"dev"},project:{type:"string",description:"Project name (optional, defaults to config)"},service:{type:"string",description:"Service name for monorepos (optional)"}},required:["key"]}},{name:"minienv_set",description:"Set an environment variable value",inputSchema:{type:"object",properties:{key:{type:"string",description:"The name of the environment variable"},value:{type:"string",description:"The value to set"},environment:{type:"string",description:"Environment (dev/stg/prd/sbx/dr)",enum:["dev","stg","prd","sbx","dr"],default:"dev"},project:{type:"string",description:"Project name (optional, defaults to config)"},service:{type:"string",description:"Service name for monorepos (optional)"}},required:["key","value"]}},{name:"minienv_delete",description:"Delete an environment variable",inputSchema:{type:"object",properties:{key:{type:"string",description:"The name of the environment variable to delete"},environment:{type:"string",description:"Environment (dev/stg/prd/sbx/dr)",enum:["dev","stg","prd","sbx","dr"],default:"dev"},project:{type:"string",description:"Project name (optional, defaults to config)"},service:{type:"string",description:"Service name for monorepos (optional)"}},required:["key"]}},{name:"minienv_list",description:"List all environment variables for a project/environment",inputSchema:{type:"object",properties:{environment:{type:"string",description:"Environment (dev/stg/prd/sbx/dr)",enum:["dev","stg","prd","sbx","dr"],default:"dev"},project:{type:"string",description:"Project name (optional, defaults to config)"},service:{type:"string",description:"Service name for monorepos (optional)"},showValues:{type:"boolean",description:"Show values (default: false for security)",default:!1}}}},{name:"minienv_export",description:"Export environment variables in shell format",inputSchema:{type:"object",properties:{environment:{type:"string",description:"Environment (dev/stg/prd/sbx/dr)",enum:["dev","stg","prd","sbx","dr"],default:"dev"},project:{type:"string",description:"Project name (optional, defaults to config)"},service:{type:"string",description:"Service name for monorepos (optional)"},format:{type:"string",description:"Output format",enum:["shell","env","json","yaml"],default:"shell"}}}},{name:"minienv_sync",description:"Sync local .env file with backend storage",inputSchema:{type:"object",properties:{environment:{type:"string",description:"Environment (dev/stg/prd/sbx/dr)",enum:["dev","stg","prd","sbx","dr"],default:"dev"},project:{type:"string",description:"Project name (optional, defaults to config)"},service:{type:"string",description:"Service name for monorepos (optional)"},dryRun:{type:"boolean",description:"Show what would be changed without making changes",default:!1}}}}]}async function z(n,e){let{client:r,config:t}=await ve(),i=e.project||t?.project||"",s=e.environment||t?.default_environment||"dev",o=e.service;if(!i)return{content:[{type:"text",text:"Error: Project not specified. Either set project in args or run from a directory with .minienv/config.yaml"}]};try{switch(await r.connect(),n){case"minienv_get":{let a=e.key,c=await r.get(a,i,s,o);return{content:[{type:"text",text:c!==null?c.value:`Variable ${a} not found`}]}}case"minienv_set":{let a=e.key,c=e.value;return await r.set({key:a,value:c,project:i,environment:s,service:o,metadata:{source:"manual"}}),{content:[{type:"text",text:`\u2713 Set ${a} in ${i}/${s}`}]}}case"minienv_delete":{let a=e.key;return{content:[{type:"text",text:await r.delete(a,i,s,o)?`\u2713 Deleted ${a}`:`Variable ${a} not found`}]}}case"minienv_list":{let a=e.showValues||!1,c=await r.list({project:i,environment:s,service:o});if(c.length===0)return{content:[{type:"text",text:`No variables found for ${i}/${s}`}]};let l=c.map(d=>a?`${d.key}=${d.value}`:d.key);return{content:[{type:"text",text:`Variables in ${i}/${s}:
|
|
7
|
+
${l.join(`
|
|
8
|
+
`)}`}]}}case"minienv_export":{let a=e.format||"shell",c=await r.export(i,s,o),l;switch(a){case"json":l=JSON.stringify(c,null,2);break;case"yaml":l=Object.entries(c).map(([d,u])=>`${d}: "${u.replace(/"/g,'\\"')}"`).join(`
|
|
9
|
+
`);break;case"env":l=Object.entries(c).map(([d,u])=>`${d}=${u}`).join(`
|
|
10
|
+
`);break;default:l=Object.entries(c).map(([d,u])=>`export ${d}="${u.replace(/"/g,'\\"')}"`).join(`
|
|
11
|
+
`)}return{content:[{type:"text",text:l}]}}case"minienv_sync":{let a=e.dryRun||!1,c=$();if(!c)return{content:[{type:"text",text:"Error: No .minienv directory found"}]};let l=D(c,s);if(!L.default.existsSync(l))return{content:[{type:"text",text:`Error: Environment file not found: ${l}`}]};let d=F(l);if(a){let k=await r.export(i,s,o),x=[],b=[],S=[];for(let[m,Y]of Object.entries(d))m in k?k[m]!==Y&&b.push(m):x.push(m);for(let m of Object.keys(k))m in d||S.push(m);let h=["Dry run - changes that would be made:"];return x.length>0&&h.push(` Add: ${x.join(", ")}`),b.length>0&&h.push(` Update: ${b.join(", ")}`),S.length>0&&h.push(` Delete: ${S.join(", ")}`),x.length===0&&b.length===0&&S.length===0&&h.push(" No changes needed"),{content:[{type:"text",text:h.join(`
|
|
12
|
+
`)}]}}let u=await r.sync(d,i,s,o,{source:"sync"}),y=[`\u2713 Synced ${i}/${s}`];return u.added.length>0&&y.push(` Added: ${u.added.length}`),u.updated.length>0&&y.push(` Updated: ${u.updated.length}`),u.deleted.length>0&&y.push(` Deleted: ${u.deleted.length}`),u.unchanged.length>0&&y.push(` Unchanged: ${u.unchanged.length}`),{content:[{type:"text",text:y.join(`
|
|
13
|
+
`)}]}}default:return{content:[{type:"text",text:`Unknown tool: ${n}`}]}}}finally{await r.disconnect()}}var q=["dev","stg","prd","sbx","dr"];async function W(){let n=null;try{n=w()}catch{}let e=n?.backend?.url,r=n?await j(n):void 0;return{client:new g({connectionString:e||void 0,passphrase:r||void 0}),config:n}}function ye(n){let e=n.match(/^minienv:\/\/([^/]+)\/([^/]+)(?:\/([^/]+))?$/);if(!e)return null;let[,r,t,i]=e;return q.includes(t)?{project:r,environment:t,service:i}:null}async function H(){let{config:n}=await W();if(!n?.project)return[];let e=n.project,r=n.environments||q,t=n.service,i=[];for(let s of r){let o=t?`minienv://${e}/${s}/${t}`:`minienv://${e}/${s}`;i.push({uri:o,name:`${e}/${s}${t?`/${t}`:""}`,description:`Environment variables for ${e} in ${s}`,mimeType:"text/plain"})}return i}async function B(n){let e=ye(n);if(!e)throw new Error(`Invalid resource URI: ${n}. Expected format: minienv://project/environment[/service]`);let{project:r,environment:t,service:i}=e,{client:s}=await W();try{await s.connect();let o=await s.export(r,t,i),a=Object.entries(o);if(a.length===0)return{contents:[{uri:n,mimeType:"text/plain",text:`# No variables found for ${r}/${t}`}]};let c=a.map(([l,d])=>`${l}=${d}`).join(`
|
|
14
|
+
`);return{contents:[{uri:n,mimeType:"text/plain",text:`# Environment: ${r}/${t}${i?`/${i}`:""}
|
|
15
|
+
# Variables: ${a.length}
|
|
16
|
+
|
|
17
|
+
${c}`}]}}finally{await s.disconnect()}}var he="minienv",Ee="0.1.0";function xe(){let n=new G.Server({name:he,version:Ee},{capabilities:{tools:{},resources:{}}});return n.setRequestHandler(p.ListToolsRequestSchema,async()=>({tools:U()})),n.setRequestHandler(p.CallToolRequestSchema,async e=>{let{name:r,arguments:t}=e.params;try{return await z(r,t||{})}catch(i){let s=i instanceof Error?i.message:String(i);throw new p.McpError(p.ErrorCode.InternalError,s)}}),n.setRequestHandler(p.ListResourcesRequestSchema,async()=>({resources:await H()})),n.setRequestHandler(p.ReadResourceRequestSchema,async e=>{let{uri:r}=e.params;try{return await B(r)}catch(t){let i=t instanceof Error?t.message:String(t);throw new p.McpError(p.ErrorCode.InternalError,i)}}),n}async function R(){let n=xe(),e=new Z.StdioServerTransport;await n.connect(e),process.on("SIGINT",async()=>{await n.close(),process.exit(0)}),process.on("SIGTERM",async()=>{await n.close(),process.exit(0)})}(process.argv[1]?.endsWith("server.js")||process.argv[1]?.endsWith("server.ts"))&&R().catch(n=>{console.error("Failed to start MCP server:",n),process.exit(1)});R().catch(n=>{console.error("Failed to start MCP server:",n),process.exit(1)});
|